$bindings用于存儲(chǔ)提供服務(wù)的回調(diào)函數(shù)
$instances用于存儲(chǔ)程序中共享的實(shí)例意狠,也可以稱為單例。
服務(wù)容器的生成
bootstrap/app.php 文件實(shí)現(xiàn)了服務(wù)容器的實(shí)例化典阵,同時(shí)綁定了核心處理類。至此,已經(jīng)獲得了一個(gè)全局的服務(wù)容器實(shí)例酝静,即$app辩棒。
Register an existing instance as shared in the container
綁定當(dāng)前 Application 對(duì)象進(jìn)容器狼忱,綁定的是同一對(duì)象,但給了兩個(gè)名字
$this->registerBaseBindings();
app
Illuminate\Container\Container
$this->registerBaseServiceProviders();
$this->register(new EventServiceProvider($this));
$this->register(new LogServiceProvider($this));
$this->register(new RoutingServiceProvider($this));
注冊(cè)別名
$this->registerCoreContainerAliases();
服務(wù)綁定
綁定實(shí)際上可以簡(jiǎn)單地理解為一個(gè)關(guān)鍵字和一個(gè)服務(wù)進(jìn)行綁定一睁,可以簡(jiǎn)單看做是一種鍵值對(duì)形式钻弄,即一個(gè)“key”對(duì)應(yīng)一個(gè)服務(wù)。對(duì)于綁定服務(wù)的不同者吁,需要服務(wù)容器中不同的綁定函數(shù)來實(shí)現(xiàn)窘俺,主要包括回調(diào)函數(shù)服務(wù)綁定和實(shí)例對(duì)象服務(wù)綁定。
- 普通綁定
普通綁定每次生成該服務(wù)的實(shí)例對(duì)象時(shí)都會(huì)生成一個(gè)新的實(shí)例對(duì)象复凳,也就是說在程序的生命周期中瘤泪,可以同時(shí)生成很多個(gè)這種實(shí)例對(duì)象。 - 單例綁定
單例綁定在生成一個(gè)實(shí)例對(duì)象后育八,如果再次生成就會(huì)返回第一次生成的實(shí)例對(duì)象对途,也就是說在程序的生命周期中,只能生成一個(gè)這樣的實(shí)例對(duì)象髓棋,如果想使用就會(huì)獲取之前生成的掀宋,也就是設(shè)計(jì)模式中的單例模式。
回調(diào)函數(shù)服務(wù)綁定的就是一個(gè)回調(diào)函數(shù)
實(shí)例對(duì)象服務(wù)綁定的是一個(gè)實(shí)例對(duì)象
服務(wù)解析
當(dāng)服務(wù)已經(jīng)綁定到服務(wù)容器中后仲锄,就可以在之后的時(shí)間隨時(shí)獲取劲妙,也可以稱為服務(wù)解析。
服務(wù)解析需要兩個(gè)步驟
- 一個(gè)是獲取服務(wù)容器對(duì)象儒喊,在Laravel框架中因?yàn)樗械墓δ苣K都是通過服務(wù)容器實(shí)例黏合在一起镣奋,所以大部分功能類中都記錄服務(wù)容器實(shí)例的屬性,通常為$app屬性怀愧,也可以通過Facades中的App外觀或app()全局函數(shù)來獲惹染薄余赢;
- 另一個(gè)是通過服務(wù)容器實(shí)現(xiàn)對(duì)應(yīng)服務(wù)的解析。
singleton()函數(shù)實(shí)現(xiàn)的是單例綁定哈垢,即程序中如果沒有服務(wù)名稱對(duì)應(yīng)的實(shí)例對(duì)象妻柒,則通過服務(wù)容器實(shí)例化一個(gè)后并進(jìn)行記錄,如果在后續(xù)程序中還需要同名的服務(wù)時(shí)則返回先前創(chuàng)建的服務(wù)實(shí)例對(duì)象耘分。該函數(shù)相當(dāng)于bind()函數(shù)的一個(gè)特例举塔,即參數(shù)$shared值為true的情況。對(duì)于bind()函數(shù)實(shí)現(xiàn)的服務(wù)綁定功能求泰,在忽略$shared參數(shù)的情況下央渣,即不討論單例還是普通的服務(wù),可以分為兩種情況渴频,
如果參數(shù)$concrete為一個(gè)回調(diào)函數(shù)芽丹,則直接將回調(diào)函數(shù)與服務(wù)名稱$abstract進(jìn)行綁定;
如果參數(shù)$concrete為一個(gè)名稱卜朗,則首先需要通過getClosure()函數(shù)創(chuàng)建服務(wù)回調(diào)函數(shù)拔第,然后將該回調(diào)函數(shù)與服務(wù)名稱綁定,總之需要實(shí)現(xiàn)一個(gè)可以生成相應(yīng)服務(wù)實(shí)例對(duì)象的回調(diào)函數(shù)與服務(wù)名稱進(jìn)行綁定场钉。
首先介紹服務(wù)查找過程蚊俺,即由make()函數(shù)實(shí)現(xiàn)的功能。該函數(shù)需要提供兩個(gè)參數(shù)惹悄,分別是$abstract和$parameters春叫,
$abstract可以看做是服務(wù)名稱肩钠,
$parameters是創(chuàng)建實(shí)例化對(duì)象需要的參數(shù)泣港,即一個(gè)類實(shí)例化時(shí)的依賴。
對(duì)于服務(wù)的查找是根據(jù)服務(wù)名稱$abstract來進(jìn)行的:
首先通過getAlias()函數(shù)來查找服務(wù)名稱是否有別名价匠,對(duì)于服務(wù)別名的管理是通過服getAlias()函數(shù)來查找服務(wù)名稱是否有別名当纱,對(duì)于服務(wù)別名的管理是通過服務(wù)容器類中的$aliases數(shù)組屬性實(shí)現(xiàn)的,而內(nèi)容基本是通過Illuminate\Foundation\Application類中的registerCoreContainerAliases()函數(shù)注冊(cè)的踩窖,如一個(gè)簡(jiǎn)單的實(shí)例坡氯,Illuminate\Contracts\Container\Container抽象類的別名為“app”,如果查找到了別名洋腮,將查找該別名對(duì)應(yīng)的服務(wù)箫柳,如果該抽象類沒有別名,則繼續(xù)進(jìn)行查找啥供。
然后在服務(wù)容器的共享實(shí)例數(shù)組($instances屬性)中查找服務(wù)名稱的實(shí)例悯恍,如果查找到則說明該服務(wù)名稱對(duì)應(yīng)為單例,直接返回先前實(shí)例化的對(duì)象伙狐,否則繼續(xù)查詢涮毫。
接下來瞬欧,會(huì)通過getConcrete()獲取服務(wù)名稱的實(shí)體,在服務(wù)綁定時(shí)罢防,一個(gè)服務(wù)名稱一般綁定一個(gè)回調(diào)函數(shù)用于生成實(shí)例對(duì)象艘虎,而這個(gè)回調(diào)函數(shù)就相當(dāng)于服務(wù)名稱的實(shí)體。這個(gè)實(shí)體的查找就是通過容器中的$bindings數(shù)組屬性實(shí)現(xiàn)的咒吐,如果查找到則返回實(shí)體野建,否則修改服務(wù)名稱的形式繼續(xù)下一次的查找。
然后渤滞,會(huì)通過isBuildable()函數(shù)判斷服務(wù)實(shí)體能否創(chuàng)建實(shí)例化對(duì)象贬墩,如果可以則轉(zhuǎn)到下一個(gè)步驟,否則繼續(xù)通過make()函數(shù)來查找妄呕。
在完成實(shí)例對(duì)象的創(chuàng)建后陶舞,通過isShared()判斷該服務(wù)是否為單例,如果是需要在共享實(shí)例對(duì)象數(shù)組($instances)中記錄绪励。
在通過make()函數(shù)查找到服務(wù)實(shí)體后肿孵,會(huì)將其傳遞給build()函數(shù)用于對(duì)象的創(chuàng)建
- 如果服務(wù)實(shí)體就是一個(gè)閉包函數(shù),則直接調(diào)用該閉包函數(shù)完成服務(wù)實(shí)例化對(duì)象的創(chuàng)建
- 如果服務(wù)實(shí)體只是一個(gè)具體類的類名疏魏,則需要通過反射機(jī)制來完成實(shí)例化對(duì)象的創(chuàng)建停做。
通過反射機(jī)制完成對(duì)象實(shí)例化的過程,首先是根據(jù)將要實(shí)例化的類名稱獲取反射類(ReflectionClass)實(shí)例大莫,然后獲取該類在實(shí)例化過程中的依賴蛉腌,即構(gòu)造函數(shù)需要的參數(shù),在build()函數(shù)中只厘,通過getDependencies()函數(shù)來實(shí)現(xiàn)依賴的生成烙丛,如果在服務(wù)解析時(shí)提供了相應(yīng)的參數(shù),即通過$parameters參數(shù)提供羔味,則直接使用提供的參數(shù)河咽,如果沒有提供,則通過服務(wù)容器中的resolveNonClass()函數(shù)來獲取默認(rèn)參數(shù)赋元,或者通過resolveClass()函數(shù)來創(chuàng)建忘蟹,而創(chuàng)建的方式也是通過服務(wù)容器,所以服務(wù)容器解決依賴注入的問題就是通過這部分代碼實(shí)現(xiàn)的搁凸。在解決了依賴的問題后媚值,可以直接通過反射機(jī)制完成服務(wù)實(shí)例對(duì)象的創(chuàng)建。
bind
綁定接口和生成相應(yīng)的回調(diào)函數(shù)
如果參數(shù) $concrete 為一個(gè)回調(diào)函數(shù)护糖,則直接將回調(diào)函數(shù)與服務(wù)名稱$abstract 進(jìn)行綁定
如果參數(shù) $concrete 為一個(gè)名稱褥芒,則首先需要通過 getClosure() 函數(shù)創(chuàng)建服務(wù)回調(diào)函數(shù),然后將回調(diào)函數(shù)與服務(wù)名稱綁定椅文,總之需要實(shí)現(xiàn)一個(gè)可以生成相應(yīng)服務(wù)實(shí)例對(duì)象的回調(diào)函數(shù)與服務(wù)名稱進(jìn)行綁定喂很。
make 服務(wù)解析
解析順序
- getAlias() 別名
- $instances 共享實(shí)例
- getConcrete( $abstract ) 獲取服務(wù)名稱實(shí)體
- 找到實(shí)體后惜颇,如果是回調(diào)函數(shù)直接返回; 如果是類名則通過反射機(jī)制生成實(shí)例對(duì)象
abstract 服務(wù)名稱
concrete 服務(wù)名稱的實(shí)體 (對(duì)象 or 回調(diào)函數(shù))
可以將其分為兩個(gè)步驟來完成:
- 一個(gè)是完成對(duì)應(yīng)服務(wù)的查找
- 另一個(gè)是完成服務(wù)的實(shí)現(xiàn)少辣,一般是指完成實(shí)例化對(duì)象的創(chuàng)建凌摄。
兩個(gè)步驟分別由make() 和 build() 函數(shù)完成。
首先介紹服務(wù)查找過程漓帅,即由 make() 函數(shù)實(shí)現(xiàn)的功能锨亏。該函數(shù)需要提供兩個(gè)參數(shù),分別是 $abstract 和 $parameters, $abstract 可以看做是服務(wù)名稱忙干,而 $parameters 是創(chuàng)建實(shí)例化對(duì)象需要的參數(shù)器予,即一個(gè)類實(shí)例化時(shí)的依賴。
對(duì)于服務(wù)的查找是根據(jù)服務(wù)名稱 $abstract 來進(jìn)行的捐迫,首先通過 getAlias() 函數(shù)來查找服務(wù)名稱是否有別名乾翔,對(duì)于服務(wù)別名的管理是通過服務(wù)容器類的 $alias 數(shù)組屬性實(shí)現(xiàn)的,而內(nèi)容基本是通過 Illuminate\Foundation\Application 類中的 registerCoreContainerAliases() 函數(shù)注冊(cè)的施戴,如一個(gè)簡(jiǎn)單的實(shí)例反浓, Illuminate\Contracts\Container\Container 抽象類的別名為 "app",如果查到了別名赞哗,將查找該別名對(duì)應(yīng)的服務(wù)雷则,如果該抽象類沒有別名,則繼續(xù)進(jìn)行查找肪笋。然后在服務(wù)容器的共享實(shí)例數(shù)組( $instances 屬性) 中查找服務(wù)名稱的實(shí)例月劈,如果查找到則說明該服務(wù)名稱對(duì)應(yīng)為單例,直接返回先前實(shí)例化的對(duì)象藤乙,否則繼續(xù)查詢猜揪。
接下來,通過 getConcrete() 獲取服務(wù)名稱的實(shí)體湾盒,在服務(wù)綁定時(shí)湿右,一個(gè)服務(wù)名稱一般綁定一個(gè)回調(diào)函數(shù)用于生成實(shí)例對(duì)象诅妹,而這個(gè)回調(diào)函數(shù)就相當(dāng)于服務(wù)名稱的實(shí)體罚勾。這個(gè)實(shí)體的查找就是通過容器中的 $bindings 數(shù)組屬性實(shí)現(xiàn)的,如果查找到則返回實(shí)體吭狡,否則修改服務(wù)名稱的形式繼續(xù)下一次的查找尖殃。然后通過 isBuildable() 函數(shù)判斷服務(wù)實(shí)體能否創(chuàng)建實(shí)例化對(duì)象,如果可以則轉(zhuǎn)到下一個(gè)步驟划煮,否則繼續(xù)轉(zhuǎn)到make() 函數(shù)來查找送丰。在完成實(shí)例化對(duì)象的創(chuàng)建后,通過 isShared() 判斷該服務(wù)是否為單例弛秋,如果是需要在共享實(shí)例函數(shù)組 ($instances) 中記錄器躏。
在通過 make() 函數(shù)查找到服務(wù)實(shí)體后俐载,會(huì)將其傳遞給 build() 函數(shù)用于對(duì)象的創(chuàng)建,如果服務(wù)實(shí)體就是一個(gè)閉包函數(shù)登失,則直接調(diào)用該閉包函數(shù)完成服務(wù)實(shí)例化對(duì)象的創(chuàng)建遏佣,如果服務(wù)實(shí)體只是一個(gè)具體類的類名,則需要通過反射機(jī)制來完成實(shí)例化對(duì)象的創(chuàng)建揽浙。通過反射機(jī)制完成對(duì)象實(shí)例化的過程状婶,首先是根據(jù)將要實(shí)例化的類名稱獲取反射類(ReflectionClass)實(shí)例,然后獲取該類在實(shí)例化過程中的依賴馅巷,即構(gòu)造函數(shù)需要的參數(shù)膛虫,在 build() 函數(shù)中,通過getDependencies() 函數(shù)來實(shí)現(xiàn)依賴的生成钓猬,如果在服務(wù)解析時(shí)提供了相應(yīng)的參數(shù)稍刀,即通過 $parameters 參數(shù)提供,則直接使用提供的參數(shù)敞曹,如果沒有提供掉丽,則通過服務(wù)容器中的 resoleveNonClass() 函數(shù)來獲取默認(rèn)參數(shù),或者通過 resolveClass() 函數(shù)來創(chuàng)建异雁,而創(chuàng)建的方式也是通過服務(wù)容器捶障,所以服務(wù)容器解決依賴注入的問題就是通過這部分代碼實(shí)現(xiàn)的。在解決了依賴的問題后纲刀,可以直接通過反射機(jī)制完成服務(wù)實(shí)例對(duì)象的創(chuàng)建项炼。
build
反射機(jī)制實(shí)例化類
$reflector = new ReflectionClass( $concrete );
$constructor = $reflector->getConstructor();
$dependencies = $constructor->getDepencies();
# 解決通過反射機(jī)制實(shí)例化類時(shí)的依賴
$instances = $this->getDependencies( $dependencies );
return $reflector->newInstanceArgs( $instances );
- 反射機(jī)制是指在PHP運(yùn)行狀態(tài)中,擴(kuò)展分析PHP程序示绊,導(dǎo)出或提取出關(guān)于類锭部、方法、屬性面褐、參數(shù)等的詳細(xì)信息拌禾,包括注釋。這種動(dòng)態(tài)獲取的信息以及動(dòng)態(tài)調(diào)用對(duì)象的方法的功能稱為反射API展哭。
- 反射是操縱面向?qū)ο蠓缎椭性P偷腁PI湃窍,其功能十分強(qiáng)大,可幫助我們構(gòu)建復(fù)雜匪傍,可擴(kuò)展的應(yīng)用您市。 其用途如:自動(dòng)加載插件,自動(dòng)生成文檔役衡,甚至可用來擴(kuò)充PHP語言茵休。
實(shí)例化對(duì)象(對(duì)象的創(chuàng)建)
如果服務(wù)實(shí)體就是一個(gè)閉包函數(shù),則直接調(diào)用該閉包函數(shù)完成服務(wù)實(shí)例化對(duì)象的創(chuàng)建
如果服務(wù)實(shí)體只是一個(gè)具體類的類名,則需要通過反射機(jī)制來完成實(shí)例化對(duì)象的創(chuàng)建榕莺。
在解決了依賴的問題后俐芯,可以直接通過反射機(jī)制完成服務(wù)實(shí)例對(duì)象的創(chuàng)建。
<?php
//設(shè)計(jì)容器類, 容器類裝實(shí)例或提供實(shí)例的回調(diào)函數(shù)
class Container
{
//用于裝提供實(shí)例的回調(diào)函數(shù), 真正的容器還會(huì)裝實(shí)例等其他內(nèi)容
//從而實(shí)現(xiàn)單例等高級(jí)功能
protected $bindings = [];
//綁定接口和生成相應(yīng)實(shí)例的回調(diào)函數(shù)
public function bind($abstract, $concrete = null, $shared = false)
{
if ( ! $concrete instanceof Closure){
//如果提供的參數(shù)不是回調(diào)函數(shù), 則產(chǎn)生默認(rèn)的回調(diào)函數(shù)
$concrete = $this->getClosure($abstract, $concrete);
}
$this->bindings[$abstract] = compact('concrete', 'shared');
}
//默認(rèn)生成實(shí)例的回調(diào)函數(shù)
protected function getClosure($abstract, $concrete)
{
//生成實(shí)例的回調(diào)函數(shù), $c一般為IoC容器對(duì)象, 在調(diào)用回調(diào)生成實(shí)例時(shí)提供
//即build函數(shù)中的 $concrete($this)
return function($c) use ($abstract, $concrete)
{
$method = ($abstract == $concrete) ? 'build' : 'make';
//調(diào)用的是容器的build或make方法生成實(shí)例
return $c->$method($concrete);
};
}
//生成實(shí)例對(duì)象, 首先解決接口和要實(shí)例化類之間的依賴關(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'];
}
//實(shí)例化對(duì)象
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);
}
//解決通過反射機(jī)制實(shí)例化對(duì)象時(shí)的依賴
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();