
$bindings用于存儲提供服務的回調(diào)函數(shù)
$instances用于存儲程序中共享的實例,也可以稱為單例。
服務容器的生成
bootstrap/app.php 文件實現(xiàn)了服務容器的實例化,同時綁定了核心處理類。至此,已經(jīng)獲得了一個全局的服務容器實例,即$app。
Register an existing instance as shared in the container
綁定當前 Application 對象進容器,綁定的是同一對象,但給了兩個名字
$this->registerBaseBindings();
app
Illuminate\Container\Container
$this->registerBaseServiceProviders();
$this->register(new EventServiceProvider($this));
$this->register(new LogServiceProvider($this));
$this->register(new RoutingServiceProvider($this));
注冊別名
$this->registerCoreContainerAliases();
服務綁定
綁定實際上可以簡單地理解為一個關(guān)鍵字和一個服務進行綁定,可以簡單看做是一種鍵值對形式,即一個“key”對應一個服務。對于綁定服務的不同,需要服務容器中不同的綁定函數(shù)來實現(xiàn),主要包括回調(diào)函數(shù)服務綁定和實例對象服務綁定。
- 普通綁定
普通綁定每次生成該服務的實例對象時都會生成一個新的實例對象,也就是說在程序的生命周期中,可以同時生成很多個這種實例對象。 - 單例綁定
單例綁定在生成一個實例對象后,如果再次生成就會返回第一次生成的實例對象,也就是說在程序的生命周期中,只能生成一個這樣的實例對象,如果想使用就會獲取之前生成的,也就是設計模式中的單例模式。
回調(diào)函數(shù)服務綁定的就是一個回調(diào)函數(shù)
實例對象服務綁定的是一個實例對象
服務解析
當服務已經(jīng)綁定到服務容器中后,就可以在之后的時間隨時獲取,也可以稱為服務解析。
服務解析需要兩個步驟
- 一個是獲取服務容器對象,在Laravel框架中因為所有的功能模塊都是通過服務容器實例黏合在一起,所以大部分功能類中都記錄服務容器實例的屬性,通常為$app屬性,也可以通過Facades中的App外觀或app()全局函數(shù)來獲取;
- 另一個是通過服務容器實現(xiàn)對應服務的解析。
singleton()函數(shù)實現(xiàn)的是單例綁定,即程序中如果沒有服務名稱對應的實例對象,則通過服務容器實例化一個后并進行記錄,如果在后續(xù)程序中還需要同名的服務時則返回先前創(chuàng)建的服務實例對象。該函數(shù)相當于bind()函數(shù)的一個特例,即參數(shù)$shared值為true的情況。對于bind()函數(shù)實現(xiàn)的服務綁定功能,在忽略$shared參數(shù)的情況下,即不討論單例還是普通的服務,可以分為兩種情況,
如果參數(shù)$concrete為一個回調(diào)函數(shù),則直接將回調(diào)函數(shù)與服務名稱$abstract進行綁定;
如果參數(shù)$concrete為一個名稱,則首先需要通過getClosure()函數(shù)創(chuàng)建服務回調(diào)函數(shù),然后將該回調(diào)函數(shù)與服務名稱綁定,總之需要實現(xiàn)一個可以生成相應服務實例對象的回調(diào)函數(shù)與服務名稱進行綁定。
首先介紹服務查找過程,即由make()函數(shù)實現(xiàn)的功能。該函數(shù)需要提供兩個參數(shù),分別是$abstract和$parameters,
$abstract可以看做是服務名稱,
$parameters是創(chuàng)建實例化對象需要的參數(shù),即一個類實例化時的依賴。
對于服務的查找是根據(jù)服務名稱$abstract來進行的:
首先通過getAlias()函數(shù)來查找服務名稱是否有別名,對于服務別名的管理是通過服getAlias()函數(shù)來查找服務名稱是否有別名,對于服務別名的管理是通過服務容器類中的$aliases數(shù)組屬性實現(xiàn)的,而內(nèi)容基本是通過Illuminate\Foundation\Application類中的registerCoreContainerAliases()函數(shù)注冊的,如一個簡單的實例,Illuminate\Contracts\Container\Container抽象類的別名為“app”,如果查找到了別名,將查找該別名對應的服務,如果該抽象類沒有別名,則繼續(xù)進行查找。
然后在服務容器的共享實例數(shù)組($instances屬性)中查找服務名稱的實例,如果查找到則說明該服務名稱對應為單例,直接返回先前實例化的對象,否則繼續(xù)查詢。
接下來,會通過getConcrete()獲取服務名稱的實體,在服務綁定時,一個服務名稱一般綁定一個回調(diào)函數(shù)用于生成實例對象,而這個回調(diào)函數(shù)就相當于服務名稱的實體。這個實體的查找就是通過容器中的$bindings數(shù)組屬性實現(xiàn)的,如果查找到則返回實體,否則修改服務名稱的形式繼續(xù)下一次的查找。
然后,會通過isBuildable()函數(shù)判斷服務實體能否創(chuàng)建實例化對象,如果可以則轉(zhuǎn)到下一個步驟,否則繼續(xù)通過make()函數(shù)來查找。
在完成實例對象的創(chuàng)建后,通過isShared()判斷該服務是否為單例,如果是需要在共享實例對象數(shù)組($instances)中記錄。
在通過make()函數(shù)查找到服務實體后,會將其傳遞給build()函數(shù)用于對象的創(chuàng)建
- 如果服務實體就是一個閉包函數(shù),則直接調(diào)用該閉包函數(shù)完成服務實例化對象的創(chuàng)建
- 如果服務實體只是一個具體類的類名,則需要通過反射機制來完成實例化對象的創(chuàng)建。
通過反射機制完成對象實例化的過程,首先是根據(jù)將要實例化的類名稱獲取反射類(ReflectionClass)實例,然后獲取該類在實例化過程中的依賴,即構(gòu)造函數(shù)需要的參數(shù),在build()函數(shù)中,通過getDependencies()函數(shù)來實現(xiàn)依賴的生成,如果在服務解析時提供了相應的參數(shù),即通過$parameters參數(shù)提供,則直接使用提供的參數(shù),如果沒有提供,則通過服務容器中的resolveNonClass()函數(shù)來獲取默認參數(shù),或者通過resolveClass()函數(shù)來創(chuàng)建,而創(chuàng)建的方式也是通過服務容器,所以服務容器解決依賴注入的問題就是通過這部分代碼實現(xiàn)的。在解決了依賴的問題后,可以直接通過反射機制完成服務實例對象的創(chuàng)建。
bind
綁定接口和生成相應的回調(diào)函數(shù)
如果參數(shù) $concrete 為一個回調(diào)函數(shù),則直接將回調(diào)函數(shù)與服務名稱$abstract 進行綁定
如果參數(shù) $concrete 為一個名稱,則首先需要通過 getClosure() 函數(shù)創(chuàng)建服務回調(diào)函數(shù),然后將回調(diào)函數(shù)與服務名稱綁定,總之需要實現(xiàn)一個可以生成相應服務實例對象的回調(diào)函數(shù)與服務名稱進行綁定。
make 服務解析
解析順序
- getAlias() 別名
- $instances 共享實例
- getConcrete( $abstract ) 獲取服務名稱實體
- 找到實體后,如果是回調(diào)函數(shù)直接返回; 如果是類名則通過反射機制生成實例對象
abstract 服務名稱
concrete 服務名稱的實體 (對象 or 回調(diào)函數(shù))
可以將其分為兩個步驟來完成:
- 一個是完成對應服務的查找
- 另一個是完成服務的實現(xiàn),一般是指完成實例化對象的創(chuàng)建。
兩個步驟分別由make() 和 build() 函數(shù)完成。
首先介紹服務查找過程,即由 make() 函數(shù)實現(xiàn)的功能。該函數(shù)需要提供兩個參數(shù),分別是 $abstract 和 $parameters, $abstract 可以看做是服務名稱,而 $parameters 是創(chuàng)建實例化對象需要的參數(shù),即一個類實例化時的依賴。
對于服務的查找是根據(jù)服務名稱 $abstract 來進行的,首先通過 getAlias() 函數(shù)來查找服務名稱是否有別名,對于服務別名的管理是通過服務容器類的 $alias 數(shù)組屬性實現(xiàn)的,而內(nèi)容基本是通過 Illuminate\Foundation\Application 類中的 registerCoreContainerAliases() 函數(shù)注冊的,如一個簡單的實例, Illuminate\Contracts\Container\Container 抽象類的別名為 "app",如果查到了別名,將查找該別名對應的服務,如果該抽象類沒有別名,則繼續(xù)進行查找。然后在服務容器的共享實例數(shù)組( $instances 屬性) 中查找服務名稱的實例,如果查找到則說明該服務名稱對應為單例,直接返回先前實例化的對象,否則繼續(xù)查詢。
接下來,通過 getConcrete() 獲取服務名稱的實體,在服務綁定時,一個服務名稱一般綁定一個回調(diào)函數(shù)用于生成實例對象,而這個回調(diào)函數(shù)就相當于服務名稱的實體。這個實體的查找就是通過容器中的 $bindings 數(shù)組屬性實現(xiàn)的,如果查找到則返回實體,否則修改服務名稱的形式繼續(xù)下一次的查找。然后通過 isBuildable() 函數(shù)判斷服務實體能否創(chuàng)建實例化對象,如果可以則轉(zhuǎn)到下一個步驟,否則繼續(xù)轉(zhuǎn)到make() 函數(shù)來查找。在完成實例化對象的創(chuàng)建后,通過 isShared() 判斷該服務是否為單例,如果是需要在共享實例函數(shù)組 ($instances) 中記錄。
在通過 make() 函數(shù)查找到服務實體后,會將其傳遞給 build() 函數(shù)用于對象的創(chuàng)建,如果服務實體就是一個閉包函數(shù),則直接調(diào)用該閉包函數(shù)完成服務實例化對象的創(chuàng)建,如果服務實體只是一個具體類的類名,則需要通過反射機制來完成實例化對象的創(chuàng)建。通過反射機制完成對象實例化的過程,首先是根據(jù)將要實例化的類名稱獲取反射類(ReflectionClass)實例,然后獲取該類在實例化過程中的依賴,即構(gòu)造函數(shù)需要的參數(shù),在 build() 函數(shù)中,通過getDependencies() 函數(shù)來實現(xiàn)依賴的生成,如果在服務解析時提供了相應的參數(shù),即通過 $parameters 參數(shù)提供,則直接使用提供的參數(shù),如果沒有提供,則通過服務容器中的 resoleveNonClass() 函數(shù)來獲取默認參數(shù),或者通過 resolveClass() 函數(shù)來創(chuàng)建,而創(chuàng)建的方式也是通過服務容器,所以服務容器解決依賴注入的問題就是通過這部分代碼實現(xiàn)的。在解決了依賴的問題后,可以直接通過反射機制完成服務實例對象的創(chuàng)建。
build
反射機制實例化類
$reflector = new ReflectionClass( $concrete );
$constructor = $reflector->getConstructor();
$dependencies = $constructor->getDepencies();
# 解決通過反射機制實例化類時的依賴
$instances = $this->getDependencies( $dependencies );
return $reflector->newInstanceArgs( $instances );
- 反射機制是指在PHP運行狀態(tài)中,擴展分析PHP程序,導出或提取出關(guān)于類、方法、屬性、參數(shù)等的詳細信息,包括注釋。這種動態(tài)獲取的信息以及動態(tài)調(diào)用對象的方法的功能稱為反射API。
- 反射是操縱面向?qū)ο蠓缎椭性P偷腁PI,其功能十分強大,可幫助我們構(gòu)建復雜,可擴展的應用。 其用途如:自動加載插件,自動生成文檔,甚至可用來擴充PHP語言。
實例化對象(對象的創(chuàng)建)
如果服務實體就是一個閉包函數(shù),則直接調(diào)用該閉包函數(shù)完成服務實例化對象的創(chuàng)建
如果服務實體只是一個具體類的類名,則需要通過反射機制來完成實例化對象的創(chuàng)建。
在解決了依賴的問題后,可以直接通過反射機制完成服務實例對象的創(chuàng)建。
<?php
//設計容器類, 容器類裝實例或提供實例的回調(diào)函數(shù)
class Container
{
//用于裝提供實例的回調(diào)函數(shù), 真正的容器還會裝實例等其他內(nèi)容
//從而實現(xiàn)單例等高級功能
protected $bindings = [];
//綁定接口和生成相應實例的回調(diào)函數(shù)
public function bind($abstract, $concrete = null, $shared = false)
{
if ( ! $concrete instanceof Closure){
//如果提供的參數(shù)不是回調(diào)函數(shù), 則產(chǎn)生默認的回調(diào)函數(shù)
$concrete = $this->getClosure($abstract, $concrete);
}
$this->bindings[$abstract] = compact('concrete', 'shared');
}
//默認生成實例的回調(diào)函數(shù)
protected function getClosure($abstract, $concrete)
{
//生成實例的回調(diào)函數(shù), $c一般為IoC容器對象, 在調(diào)用回調(diào)生成實例時提供
//即build函數(shù)中的 $concrete($this)
return function($c) use ($abstract, $concrete)
{
$method = ($abstract == $concrete) ? 'build' : 'make';
//調(diào)用的是容器的build或make方法生成實例
return $c->$method($concrete);
};
}
//生成實例對象, 首先解決接口和要實例化類之間的依賴關(guān)系
public function make($abstract)
{
$concrete = $this->getConcrete($abstract);
if ($this->isBuildable($concrete, $abstract)) {
$object = $this->build($concrete);
}
else
{
$object = $this->make($concrete);
}
return $object;
}
protected function isBuildable($concrete, $abstract)
{
return $concrete === $abstract || $concrete instanceof Closure;
}
//獲取綁定的回調(diào)函數(shù)
protected function getConcrete($abstract)
{
if ( ! isset($this->bindings[$abstract]))
{
return $abstract;
}
return $this->bindings[$abstract]['concrete'];
}
//實例化對象
public function build($concrete)
{
if ($concrete instanceof Closure){
return $concrete($this);
}
$reflector = new ReflectionClass($concrete);
if ( ! $reflector->isInstantiable())
{
echo $message = "Target [$concrete] is not instantiable.";
}
$constructor = $reflector->getConstructor();
if (is_null($constructor))
{
return new $concrete;
}
$dependencies = $constructor->getParameters();
$instances = $this->getDependencies($dependencies);
return $reflector->newInstanceArgs($instances);
}
//解決通過反射機制實例化對象時的依賴
protected function getDependencies($parameters)
{
$dependencies = [];
foreach ($parameters as $parameter)
{
$dependency = $parameter->getClass();
if (is_null($dependency))
{
$dependencies[] = NULL;
}
else
{
$dependencies[] = $this->resolveClass($parameter);
}
}
return (array) $dependencies;
}
protected function resolveClass(ReflectionParameter $parameter)
{
return $this->make($parameter->getClass()->name);
}
}
class Traveller
{
protected $tool;
public function __construct(Visit $tool)
{
$this->tool = $tool;
}
public function visitTibet()
{
$this->tool->go();
}
}
interface Visit
{
public function go();
}
class Train implements Visit
{
public function go()
{
echo "train. <br>";
}
}
$app = new Container();
$app->bind("Visit", "Train");
$app->bind("traveller", "Traveller");
$tra = $app->make("traveller");
$tra->visitTibet();