「本文主要針對框架內部容器以及門面的實現作爲基準點來實現,php
」
前言
在這以前已經剖析過了類的自動加載、配置文件加載的源碼解析,本文爲第三期的文章,主要針對容器以及門面類的實現,解析源碼。以及學習實現此功能的一些知識點。laravel
-
第一期文章: ThinkPHP自動加載Loader源碼分析 -
第二期文章: ThinkPHP配置文件源碼分析
1、單例模式
在學習容器以及門面以前須要必須瞭解的倆個設計模式,單例模式、註冊樹模式。web
先對單例模式作一個簡單的說明。thinkphp
-
擁有一個構造函數,而且屬性爲private -
擁有一個靜態成員變量來保存類的實例 -
擁有一個靜態方法來訪問這個實例
如下就是咔咔實現的一個簡單的單例模式,對照一下上面的三大特性看是否一致。編程
靜態變量爲instance設計模式
擁有構造而且仍是私有的數組
最後一個就是有一個getInstance這個靜態方法微信
接下來進行一下簡單的測試閉包
仍是在index控制器中作測試,爲了證明其類只被實例化過一次,調用了其四次app
訪問這個方法來看一下
new-class只執行了一次,就直接證實了建立的類只實例化了一次。在這裏咔咔以前有過一個疑問就是,這裏的構造函數爲何要使用私有的屬性。
你以前有過這個疑問嗎?咔咔帶你一塊兒來解答一下
在本類定義私有屬性的構造方法是爲了防止其類在外部被實例化。
當在外部實例化這個類就會報下圖的錯。
那麼爲何會在這裏提一嘴單例模式呢!是由於在接下來的學習容器的源碼中會使用到
例以下圖thinkphp/library/think/Container.php
類中就存在一個獲取當前容器的實例。
截止到這裏單例模式就簡單的瞭解完了,瞭解單例模式也是爲了更好的理解容器。
2、註冊樹模式
爲何在這裏說這個註冊樹模式,由於在框架中註冊樹模式就是一個主導位置,因此必須去了解它!
那什麼是註冊樹模呢!
-
註冊樹模式就是將對象實例註冊到一顆樹上(這裏的樹可不是真的樹啊!就是註冊到一個全局的屬性裏邊) -
而後能夠經過內部方法從全局的樹上獲取對應的對象實例。
這樣說的話確定也不能更好的理解,接下來咔咔帶你們看一個簡單的案例來簡單的瞭解一下。
一個註冊樹模式須要的東西就是四個,註冊樹的池子,將對象掛載到註冊池裏,從註冊池裏獲取對象,從註冊池裏卸載對象。
以下圖是咔咔寫的一個簡單的註冊樹模式。
代碼若是看不懂的就須要去補補基礎了哈!
接下來在到同一目錄建立一個TestTree文件
來到控制器測試寫的註冊樹模式是否有問題
在作測試的時候必定要注意命名空間問題哈!這裏的kaka目錄是以前在類的自動加載那裏配置的,若有不會的能夠去第一期文章查看。
這裏就至關於先把TestTree這個類實例化出來
而後使用註冊樹模式把這個實例註冊到object樹池子中
最後使用get方式將這個類獲取出來就能夠直接調用TestTree中的方法了。
最後看一下最終打印結果,結果就是TestTree類中getTreeContent方法的返回值。
註冊樹模式就是以上咔咔說明的這些內容,就是不去針對源碼學習,這些內容也是咱們必需要去學會使用的。
3、如何理解控制反轉和依賴注入
其實這倆個就是指的一個東西,就是一種編程思想而已,不要想的那麼難以理解和高大上。
那麼什麼是容器,容器直面理解就是裝東西的東西。在編程中,咱們常見的變量、對象屬性都是一個容器。一個容器裏邊可以裝什麼,徹底取決於對該容器的定義。
然而如今咱們討論的是另一種容器,它存儲的既不是文本、數值,而是對象、類、接口經過這種容器,得以實現不少高級功能,最經常使用的就是代碼之間的解耦、依賴注入。
那麼爲何會存在倆種概念,爲何要說控制反轉和依賴注入呢!在上文也提到過,它們其實指的就是一種東西,只是描述的角度不一樣而已。
就跟你是爸爸的兒子,你仍是你爺爺的孫子,無論兒子仍是孫子都指的是一我的。只是站在不一樣的角度看待問題而已。
控制反轉
是站在容器的角度看待問題,容器控制着應用程序,由容器反向的嚮應用程序注入應用程序須要的外部資源。
依賴注入
是站在應用程序的角度看待問題,應用程序依賴容器建立並注入它所須要的外部資源。
做用
主要用來減小代碼之間的耦合程度。
有效的分離對象和應用程序所須要的外部資源。
下面倆幅圖就能夠很清晰的說明問題
給你們整一個簡單的案例
定義倆個類分別爲Person、Car,在Person中實例並調用Car中的pay方法。
而後在控制器中調用,而且打印結果確定就是Car返回的123,這個就不去打印了。
那這個時候咱們把代碼修改一下,把Car類直接傳給Person類,在Person類中直接用傳過來的對象去調用對應的方法。
這只是一個簡單的實現過程,爲了給閱讀框架容器代碼作一個鋪墊,在後文中會詳細說明框架中的容器注入。
4、必會反射機制
不知道你們有沒有了解過GO的反射機制,咔咔在當時看了go的反射機制後說實話有點暈乎乎的。
可是在後來看了PHP的反射以後,不只對go的反射有了必定的深刻了解,而且對於PHP的反射也是更好的理解。
反射這一律念是在PHP5.0被引出來的,在目前使用的框架中咔咔知道的就有thinkphp和laravel都使用了反射來實現依賴注入。
對於反射的理解:其實就是從根獲取根之外的東西,放在編程中講就是隻要知道一個類就能夠知道這個類全部的屬性和方法。
案例
這只是一個簡單的實現案例,獲取類的所有方法和屬性。能夠看下圖中的打印結果跟TestReflection是否一致。
這個也從側面表現出現一個問題,就是會暴露出來一些本不該該暴露出來的信息。
關於反射提供的接口還有不少,這裏就介紹幾個經常使用的,其他的在框架源碼中解析。
使用反射執行一個類的方法
打印出來的結果就是咔咔
使用反射執行一個類中帶參數的方法
使用反射執行一個類中不帶參數的方法
其它的方法大家本身能夠嘗試嘗試,由於這個反射的接口在平時基礎開發是不怎麼用的,這咔咔給你們介紹的都是後邊在閱讀源碼都是能夠用的到的。
既然瞭解到了反射,那麼反射能夠作什麼事情呢!其中有一個功能點自動生成文檔。
反射到這裏就簡單的瞭解一下,至於還想了解更多的接口使用能夠去官方查看對應的接口信息。
在瞭解完反射以後就要開始進入正題了,就須要正式進入咱們的容器環節了。只有上邊的基礎打好接下來的容器才能更好的理解。
5、玩轉本身的容器類
經歷了九九八十一難終於來到了容器這一環節,在這一環節咱們先來實現一個本身的容器,將以前講解的單例模式、註冊樹模式、反射進行一個串聯,從而進行加深印象和更好的理解。
還記得以前在依賴注入裏邊說過這樣一個方法dependency
,這個方法就是進行了依賴注入,從而對代碼進行解耦。
可是此次呢!會使用容器來解決這一問題。
首先先把須要的類定義好,這一個類就使用了單例模式和註冊樹模式,以前的文章沒有好好看的,必定要仔細看一下,不然後文會很難理解的。
<?php
/**
* Created by PhpStorm.
* User: 咔咔
* Date: 2020/9/21
* Time: 19:04
*/
namespace container;
class Container
{
/**
* 存放容器
* @var array
*/
public $instances = [];
/**
* 容器的對象實例
* @var array
*/
protected static $instance;
/**
* 定義一個私有的構造函數防止外部類實例化
* Container constructor.
*/
private function __construct() {
}
/**
* 獲取當前容器的實例(單例模式)
* @return array|Container
*/
public static function getInstance ()
{
if(is_null(self::$instance)){
self::$instance = new self();
}
return self::$instance;
}
public function set ($key,$value)
{
return $this->instances[$key] = $value;
}
public function get ($key)
{
return $this->instances[$key];
}
}
爲了方便之後查看方便,這裏把每節的案例演示都放在對應的控制器中
這裏把以前的依賴注入的代碼移植過來,而且配置上註解路由進行訪問,看最終結果是否爲Car方法返回的123
測試一下打印結果,一切ok
使用單例模式和註冊樹模式配合後修改的這份代碼
修改後打印出其結果,一樣也是car返回的值123。
在這裏須要注意一下就是在同一個方法中set和get方法是不會共存的,這裏只是爲了給你們作一個演示寫到一塊兒的。
後邊在看容器源碼時就知道set和get方法究竟是怎麼使用的,這裏只是讓你們體驗一下單例模式和註冊樹模式。
這裏作一個小修改,修改上文中最後倆行代碼
場景二
此時咱們把Person 的文件修改一下
添加一個構造函數,把參數使用構造函數進行賦值,在buy方法中就不須要在進行傳遞參數,只須要使用this->obj便可。
此時若是仍是直接運行dependency
路由就會報下邊一個錯,那是由於在Person中構造函數有個參數,的可是咱們沒有傳。
此時就須要在修改一處,就是在實例化Person時把Car的實例當參數給傳進去就沒有任何問題了。
可是你會發現上邊這都是什麼代碼,原本簡簡單單的幾行代碼被複雜成這個樣子,這個時候就已經弊大於利了,無論設計模式在好,盲目的使用對項目來講也是一種負擔。
因此這個時候反射就來了,反射在上文中也進行簡單的介紹過,必定要看哈!文章都是一環套着一環的。
反射之戰優化代碼
最終優化完成的代碼就是這樣的,接下來對這段代碼進行簡單的解析。
-
在以前代碼的基礎上只修改了 kaka/container/Container.php
這個類裏邊的get方法 -
判斷這個名person是否在容器中 -
使用反射接口,而後獲取傳進去person類的構造方法 -
若是person沒有構造方法就直接返回person這個實例便可 -
如存person在構造函數,則獲取person構造函數的方法 -
因爲person類裏邊的構造函數的參數不會僅限於一個 -
因此須要循環來獲取每一個參數的對象 -
最後使用反射的 newInstanceArgs接口建立對應的實例
<?php
/**
* Created by PhpStorm.
* User: 咔咔
* Date: 2020/9/21
* Time: 19:04
*/
namespace container;
class Container
{
/**
* 存放容器
* @var array
*/
public $instances = [];
/**
* 容器的對象實例
* @var array
*/
protected static $instance;
/**
* 定義一個私有的構造函數防止外部類實例化
* Container constructor.
*/
private function __construct() {
}
/**
* 獲取當前容器的實例(單例模式)
* @return array|Container
*/
public static function getInstance ()
{
if(is_null(self::$instance)){
self::$instance = new self();
}
return self::$instance;
}
public function set ($key,$value)
{
return $this->instances[$key] = $value;
}
/**
* User : 咔咔
* Notes: 獲取容器裏邊的實例 使用反射
* Time :2020/9/21 22:04
* @param $key
* @return mixed
*/
public function get ($key)
{
if(!empty($this->instances[$key])){
$key = $this->instances[$key];
}
$reflect = new \ReflectionClass($key);
// 獲取類的構造函數
$c = $reflect->getConstructor();
if(!$c){
return new $key;
}
// 獲取構造函數的參數
$params = $c->getParameters();
foreach ($params as $param) {
/**
ReflectionClass Object
(
[name] => container\dependency\Car
)
*/
$class = $param->getClass();
if(!$class){
}else{
// container\dependency\Car
$args[] = $this->get($class->name);
}
}
// 從給出的參數建立一個新的類實例
return $reflect->newInstanceArgs($args);
}
}
文件application/index/controller/Container.php
這裏就是修改以後的變更
問題一:kaka/container/dependency/Person.php
裏邊的參數Car是什麼意思
這個問題其實很簡單,你能夠看到這個Car就是同目錄的Car.php文件。你就能夠直接理解爲同命名空間下的文件。
問題二:文件application/index/controller/Container.php
爲何能夠直接調用buy方法
首先看一下obj的值,返回的這個對象裏邊就已經把Car的類實例化好了,因此無需在實例化,可直接調用buy方法,由於參數會直接傳遞過去
以上就是咔咔實現的一個簡單的容器,若有不明白或者問題能夠直接評論區回覆便可。
接下來就是針對框架裏邊的容器進行剖析,一步一步的追溯到根源。
6、Container容器類剖析之Countable巧用
關於Countable這塊內容一直沒想好是不是文章的形式寫出展示給你們,可是在後期閱讀源碼時大量的出現了Countable的應用。
爲了你們能看懂每個技術點,咔咔仍是寫了出來。
在文件thinkphp/library/think/Container.php
中,就能夠直接看到使用了Countable接口,而且實現了它!
來到Countable
這接口中,咱們只能看到一個方法就是count().
根據代碼中Count elements of an object
這行註釋能夠了解到,這個接口是計算對象的元素
根據PHP文檔的說明在深刻了解一下。
文檔說明當你執行count()方法時就至關於在執行上邊的abstract public Countable::count ( void ) : int
抽象方法。
實戰案例
光說不幹,事事落空;又說又幹,馬到成功。直接開幹
新建文件kaka/container/countableTest.php
,而且添加如下內容
接着在文件application/index/controller/Container.php
中學會使用Countable。
這裏注意一下用法,是直接使用count();
Countable中的count()跟平時使用count()方法有什麼區別
順便看一下PHP源碼中的解釋
能夠看到第一個參數能夠是數組也但是是countable
咔咔的理解是Countable只是重寫了SPL中的count方法,爲了就是方便定製本身須要的統計規則而已。
int count ( mixed $array_or_countable [, int $mode = COUNT_NORMAL ] )
count你不知道的用法
既然說到了這裏,咔咔給你們在普及一個count不是很經常使用的一個用法。
在平時開發的過程當中,這樣的用法是最廣泛的,也是你們最常常見到的一個使用案例。
可是若是這時給你一個多維數組,例以下圖這樣,讓你統計這個多維數組,你該怎麼統計呢!
這個時候估計大多數小夥伴的想法就是循環而後定義一個計數器累計。
其實count()函數在這一塊就已經解決了這個需求。
下方打印結果就是"4----6"
直接使用count()函數一個數組獲得的就是第一層數組的長度。
可是count()函數還有第二個參數,設置爲1就是遞歸地計數數組中元素的數目(計算多維數組中的全部元素)
因此你這時在去看文檔就會發現,count()函數自己就有倆個參數
第一個參數是必須的,選擇是數組
第二個參數默認是0就是不對多維數組中的全部元素進行計數
當第二個參數爲1時就是遞歸的計算多維數組中的全部元素。
7、Container容器類剖析
上文中實現了一個本身建立的容器,接下來看看源碼中的容器,通過了上文容器中出現的技術點都已經囊括完了。
在接下里閱讀容器源碼就不會很吃力,若是以前的文章沒看,必定要大概過一遍哈!
你們無數次打開的一個文件public/index.php
。
曾有多少次打開這個文件想對源碼進行一探究竟,可是看着看着就放棄了。
通過以前的註冊樹模式以後,你確定就會明白這行代碼會返回什麼Container::get('app')
這行代碼返回就是app的實例,能夠進行簡單的斷點一下。
能夠看到返回就是app類裏邊的衆多屬性。
因此說註冊樹模式不會的在繼續返回去看以前寫的,要不越看越迷糊。
那麼框架中的容器是怎麼定義的呢!它究竟是怎麼實現的呢!
也就是隻須要去關注這個get()
方法作的事情就能夠了。
代碼就會追蹤到文件thinkphp/library/think/Container.php
中的get()
方法
這裏的getInstance()
方法不陌生了吧!這就是上文說過的單例模式。
能夠進行代碼追蹤getInstance()
這個方法,你就會在同文件中看到這個單例模式的方法,返回Container實例。
Container實例調用make方法
代碼static::getInstance()
返回了Container的實例後,就會去調用本類的make方法,接下來就是對make方法進行詳解了。
在開始閱讀make方法裏邊的源碼以前,咱們須要先對幾個屬性進行簡單的梳理一下。
這四個屬性必定要有點印象,而且必定要區別instance和instances。
這倆個屬性一個是單例模式返回當前類的實例,一個是容器中的全部的實例。
第一次執行結果
/**
* 建立類的實例
* @access public
* @param string $abstract 類名或者標識
* @param array|true $vars 變量
* @param bool $newInstance 是否每次建立新的實例
* @return object
*/
public function make($abstract, $vars = [], $newInstance = false)
{
// 判斷$vars這個變量是否爲true
if (true === $vars) {
// 老是建立新的實例化對象
$newInstance = true;
$vars = [];
}
// app 這裏就是在容器別名裏獲取傳遞過來的app 若是沒有則就是app
$abstract = isset($this->name[$abstract]) ? $this->name[$abstract] : $abstract;
// 從容器實例中獲取 若是存在則直接返回對應的實例 也就是使用註冊樹模式
if (isset($this->instances[$abstract]) && !$newInstance) {
return $this->instances[$abstract];
}
// think\App 從容器標識中獲取
if (isset($this->bind[$abstract])) {
// 將think\App 複製給$concrete變量
$concrete = $this->bind[$abstract];
// 用於表明匿名函數的類 判斷是否是閉包
if ($concrete instanceof Closure) {
$object = $this->invokeFunction($concrete, $vars);
} else {
// $this->name['app'] = think\App
$this->name[$abstract] = $concrete;
// 在執行一次本類的make方法,也就是本方法
return $this->make($concrete, $vars, $newInstance);
}
} else {
$object = $this->invokeClass($abstract, $vars);
}
if (!$newInstance) {
$this->instances[$abstract] = $object;
}
return $object;
}
這是第二次執行流程
public function make($abstract, $vars = [], $newInstance = false)
{
// 判斷$vars這個變量是否爲true
if (true === $vars) {
// 老是建立新的實例化對象
$newInstance = true;
$vars = [];
}
// app 這裏就是在容器別名裏獲取傳遞過來的app 若是沒有則就是app
// 第二次執行時 $abstract = think\App
$abstract = isset($this->name[$abstract]) ? $this->name[$abstract] : $abstract;
// 從容器實例中獲取 若是存在則直接返回對應的實例 也就是使用註冊樹模式
if (isset($this->instances[$abstract]) && !$newInstance) {
return $this->instances[$abstract];
}
// think\App 從容器標識中獲取
// 第二次執行$this->bind['think\App']不存在走else
if (isset($this->bind[$abstract])) {
// 將think\App 複製給$concrete變量
$concrete = $this->bind[$abstract];
// 用於表明匿名函數的類 判斷是否是閉包
if ($concrete instanceof Closure) {
$object = $this->invokeFunction($concrete, $vars);
} else {
// $this->name['app'] = think\App
$this->name[$abstract] = $concrete;
// 在執行一次本類的make方法,也就是本方法
// think\App
return $this->make($concrete, $vars, $newInstance);
}
} else {
// think\App
$object = $this->invokeClass($abstract, $vars);
}
if (!$newInstance) {
// 把建立的容器存起來
//$this->instances['think\App'] = $object;
$this->instances[$abstract] = $object;
}
return $object;
}
public function invokeClass($class, $vars = [])
{
try {
/**
* ReflectionClass Object
(
[name] => think\App
)
*/
// 這裏就是以前文章提到的反射
$reflect = new ReflectionClass($class);
if ($reflect->hasMethod('__make')) {
$method = new ReflectionMethod($class, '__make');
if ($method->isPublic() && $method->isStatic()) {
$args = $this->bindParams($method, $vars);
return $method->invokeArgs(null, $args);
}
}
// 經過反射獲取think\App的構造函數
$constructor = $reflect->getConstructor();
$args = $constructor ? $this->bindParams($constructor, $vars) : [];
// 從給出的參數建立一個新的類實例
return $reflect->newInstanceArgs($args);
} catch (ReflectionException $e) {
throw new ClassNotFoundException('class not exists: ' . $class, $class);
}
}
執行流程圖
既然把代碼都理清楚了,這時來理一下執行的流程圖能夠看的更清晰。
invokeClass方法詳細解析
無論是閱讀完上邊的代碼流程,仍是上圖的流程圖,確定都知道了最終代碼會走向一個方法invokeClass
,就是這個方法。
這個方法中所有都是利用反射的知識點,不會的在去看上文或者以前的文章吧!
在invokeClass
方法中,最重要的就是綁定參數的這個方法bindParams
,這個方法裏邊也所有運用的是反射。
因此在容器中反射起到的做用有多大就不用在去作過多的說明了。
在這以前須要把這塊說明一下,看到這個__make方法,咔咔是記憶尤深哈!
這個方法在以前學習config源碼配置那一篇文章中咔咔說暫時略過,由於當時所儲備的知識點和框架代碼執行流程還沒到說明__make這個方法的階段。
爲了就是在容器這裏詳細的說明__make這個方法的做用。
當你打印reflect這個變量的值時會返回倆個反射類的對象,以下圖。
代碼$reflect->hasMethod('__make')
就是判斷此反射類裏邊是否存在__make函數
代碼$method = new ReflectionMethod($class, '__make');
就是執行反射類的一個方法 這裏就指的是__make方法
當斷點這個method就會返回倆個存在__make反射類,這裏是由於斷點了只有顯示了倆個反射類。
這裏主要談論think\Config.
最後一行代碼$method->isPublic() && $method->isStatic()
就是判斷方法是否是公公共的 判斷方法是否是靜態的
直到運行到$args = $this->bindParams($method, $vars);
這行纔會進入到bindParams
方法,這個方法也會在下文給出詳細的解析。
解析bindParams方法
接下來就解析一下bindParams
這個方法。
關於參數傳遞的就是一個反射類 第二個參數暫時不作說明,目前尚未遇到響應的場景。
第一個參數值$reflect
使用反射方法$reflect->getNumberOfParameters()
獲取反射類中對應的方法中的參數數目。按照上文的就是__make方法。容器代碼中只獲取過倆個方法的參數數目,一個是__make方法,一個是就是反射類中的構造函數。
因爲目前尚未傳遞vars變量的場景,因此這塊的內容暫時不去研究它直接略過。
代碼$params = $reflect->getParameters();
也是使用反射獲取方法的參數。
打印出來能夠看到的結果是倆組數據。
那麼這這組數據是從哪裏來的呢!往上翻一下,看一下$reflect
這個參數是什麼就明白了。
think\App這個反射類是沒有__make方法的,因此會獲取構造函數中的參數。
而後think\Log反射類中存在__make方法,因而就會返回__make的參數,以下圖。
就像相似於think\Log這樣的類,既有__make方法,也存在構造函數,就會走倆次bindParams
方法,這個應該都明白,正是下圖邏輯。
在接下來就是循環反射類中獲取的參數。
獲取參數名、和獲取對應的反射類
最後將獲取出來的反射類傳遞給getObjectParam
方法。
在這個getObjectParam
方法中並無多少內容。
因爲$vars
從頭至尾都是空數組因此去除數組第一個的操做和判斷是否爲閉包都不會執行。
最終會在返回去執行make方法
而後make方法會直接從容器中返回這個實例
當一個反射類存在__make方法時,最終就會執行return $method->invokeArgs(null, $args);
,帶參數執行反射類方法
使用容器來調用配置類
既然已經把容器源碼讀了一次了,可不可使用容器來實現呢!
那固然是能夠的了,這裏須要注意一下咔咔的命名空間,這裏因爲爲了之後回顧方便把類名也起成了Container了,因此給加了一個別名,大家在使用的時候是不須要的哈!
截止到這裏容器的源碼就講解的差很少了,後邊咔咔會作一個完整的流程圖,提供改你們查看。
8、容器源碼閱讀後總結
註冊模式
本文先從倆個設計模式開頭,分別爲單例模式和註冊樹模式。
單例模式簡單理解就是在應用程序聲明週期內只會返回一個實例對象,不會再去建立新的對象。
註冊樹模式理解就是會把程序中使用的對象都會存放在一顆樹上,使用的時候直接從樹上獲取對象直接使用便可。
控制反轉依賴注入
控制反轉和依賴注入千萬不要讓名字把人虎住了,倆個看待一個事件的問題不一樣,一個是站在容器角度,一個是站在應用程序角度。
從容器角度來看,容器控制着應用程序,由容器反向的嚮應用程序注入外部資源
從應用程序的角度來看,應用程序依賴容器建立並注入它所需的外部資源。
反射
反射沒有什麼須要總結的,打開文檔看一下就明白了,重要的要學會使用而且知道各自什麼意思學會靈活運用便可。
容器源碼解析
容器的源碼看完後你會發現用的東西就是上邊說的三個知識點造成的,運用註冊模式來對容器中的對象管理。
對於這個圖須要緊緊記住,在源碼中就使用的這四個屬性走來走去的。
在一個就是代碼的執行流程
在容器中最重要的方法就是invokeClass和bindParams
這倆個方法跟這咔咔的思路走就沒有什麼問題,跟這斷點的流程一點一點執行。
這塊看的時候估計有點繞,可是仔細看完以後你會發現能夠學到不少東西
「堅持學習、堅持寫博、堅持分享是咔咔從業以來一直所秉持的信念。但願在偌大互聯網中咔咔的文章能帶給你一絲絲幫助。我是咔咔,下期見。
」
本文分享自微信公衆號 - PHP初學者必看(PHP0022)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。