理解了前面部分的機制後,我們開始來閱讀原始碼
首先從Bind繫結開始。
Illuminate\Container
, Container最重要的方法:bind
繫結分為幾種:
1. bind 把介面和其實現類繫結,當make解析介面的時候建立其實現類的例項物件。
2. single 把介面和其實現類繫結,當第一次make解析的時候建立例項,後面都返回該例項不再建立。
3. instance 把介面和其實現類的例項繫結,直接繫結例項物件。
4. 上下文繫結
5. 自動繫結
6. tag繫結
7. extends擴充套件繫結
先大概看bind下原始碼:
public function bind($abstract, $concrete = null, $shared = false)
{
$this->dropStaleInstances($abstract);
if (is_null($concrete)) {
$concrete = $abstract;
}
if (! $concrete instanceof Closure) {
$concrete = $this->getClosure($abstract, $concrete);
}
$this->bindings[$abstract] = compact('concrete', 'shared');
if ($this->resolved($abstract)) {
$this->rebound($abstract);
}
}
引數:
0.1 首先明確第一個引數 $abstruct
簡單說就是id,可以當做是存入容器中的名字。他可以是一個字串,一個類,甚至是一個介面。
0.2 第二個引數 $concrete 簡單說就是真實的值,可以當做是一個真正存入容器的實體。他可以是一個實現類,例項,或者一個閉包(閉包可以返回一個實現類的例項)。
0.3 第三個引數控制Shared的值。
方法體:
1. 繫結前,先清空instances和aliases中存在的同名字的服務。
$this->dropStaleInstances($abstract);
1.1.dropStaleInstances($abstract)
如下,就是清空當前instance中和aliases中存在的$abstruct同名的服務。
protected function dropStaleInstances($abstract)
{
unset($this->instances[$abstract], $this->aliases[$abstract]);
}
2. 然後判斷第二個引數$concrete
是不是空,如果是空,就視$abstruct
和$concrete
一樣。比如: app()->bind(Boss::class)
。
if (is_null($concrete))
{
$concrete = $abstract;
}
3.如果當前這個$concrete
不是一個閉包。就呼叫getClosure,返回一個閉包便於後面的操作。
if (! $concrete instanceof Closure)
{
$concrete = $this->getClosure($abstract, $concrete);
}
3.1.我們去看看這個getClosure方法是怎麼返回一個閉包的。
很簡單程式碼最後直接返回了一個這樣形式的閉包:function($container,$parameters=[])
。
一些細節:
使用use關鍵字呼叫父類就是getClosure傳入的$abstruct
和$concrete
兩個引數。
如果$abstruct
和$concrete
是一樣的,就是如果只有一個引數,或者確實兩個引數一樣,像這樣 app->bind(User::class,User::class)
,那麼就呼叫build方法。
否則使用make方法。(build方法和make方法,參看後面章節)
protected function getClosure($abstract, $concrete)
{
return function ($container, $parameters = []) use ($abstract, $concrete) {
if ($abstract == $concrete) {
return $container->build($concrete);
}
return $container->make($concrete, $parameters);};
}
不管怎麼樣,程式碼最後直接返回了一個這樣形式的閉包:function($container,$parameters=[])
。賦值給變數$concrete
。而這個閉包內返回的是通過build或者make解析的值
4.我們回到bind方法,上面$concrete
得到一個閉包函式後,呼叫compact把$concrete
和$shard
(第三個引數判斷是否shared)組成一個key分別為concrete和shared的陣列,存入binding陣列中,而binding陣列的key是當前的抽象類。
$this->bindings[$abstract] = compact('concrete', 'shared');
處理後結構是這樣的:
$binding[$abstract] => [
'concrete' => function($container,$parameters=[]),//getClosure()得到的
'shared' => true/false,//shared的值是bind的第三個引數
]
5.接下來下一句,如果當前的抽象類曾經被解析過。那再次繫結的時候,我們要使用rebound函式觸發reboundCallbacks陣列中的回撥函式。
關於回撥函式參看前面章節
if ($this->resolved($abstract))
{
$this->rebound($abstract);
}
如何判斷當前的$abstruct
曾經被解析過呢,我們看下resolved函式。兩個條件
1.簡單判斷當前resolved陣列中是否存在$abstruct
。
2.或者instances陣列中是存在對應的值。但我們注意,先前在bind方法的第一句$this->dropStaleInstances($abstract);
的時候我們清空了instances對應的$abstruct
的值,所以這邊主要是考慮$abstruct
的別名在instances中是否存在殘留的情況。
public function resolved($abstract)
{
if ($this->isAlias($abstract)) {
$abstract = $this->getAlias($abstract);
}
return isset($this->resolved[$abstract]) ||
isset($this->instances[$abstract]);
}
總結:
在bind方法中。
1.首先移除舊的例項,如果引數$concrete
不是閉包,是類名,會通過getClosure函式將類名封裝進閉包中,返回這個閉包。總之container就要閉包。
注意:build和make都是在一個閉包函式中,閉包函式不觸發,它是不會建立物件的。也就是所謂的懶載入。關於build和make他是如果操作的,下幾章講解。
2.然後把返回的閉包函式和share的值組合放入$this->bindings陣列中。
3.最後判斷當前這個$abstruct是否以前被解析過,如果是,要觸發對應的回撥函式。
**最簡單的來說
就是原來在容器中,繫結的是一個id和一個閉包函式的組合。你傳入閉包最好,不是閉包,laravel會轉成閉包存起來。
暫時從程式碼來看,我們可以猜想,最後從容器解析出來的物件是執行這個閉包產生返回的。
那我們就會有這樣的猜想了,我們可以通過閉包繫結任何型別的值,因為只要在閉包中返回我們想要的任何型別的值就好了。**
例項測試
測試1:使用閉包函式返回有依賴的物件。
class Money
{
private $amount = 0;
public function __construct($amount)
{
$this->amount = $amount;
}
public function getAmount()
{
return $this->amount;
}
}
//注意閉包的形式
$this->app->bind('money', function($app, $parameters){return new Money($parameters[0]);});
測試2:使用閉包函式返回沒有依賴的物件
class Dollar
{
public function getAmount(){
return 100;
}
}
$this->app->bind('dollar', function(){return new Dollar();});
本作品採用《CC 協議》,轉載必須註明作者和本文連結