詳解如何實(shí)現(xiàn)Laravel的服務(wù)容器的方法示例
1. 容器的本質(zhì)
- 服務(wù)容器本身就是一個(gè)數(shù)組,鍵名就是服務(wù)名,值就是服務(wù)。
- 服務(wù)可以是一個(gè)原始值,也可以是一個(gè)對象,可以說是任意數(shù)據(jù)。
- 服務(wù)名可以是自定義名,也可以是對象的類名,也可以是接口名。
// 服務(wù)容器 $container = [ // 原始值 'text' => '這是一個(gè)字符串', // 自定義服務(wù)名 'customName' => new StdClass(), // 使用類名作為服務(wù)名 'StdClass' => new StdClass(), // 使用接口名作為服務(wù)名 'Namespace\\StdClassInterface' => new StdClass(), ]; // ----------- ↓↓↓↓示例代碼↓↓↓↓ ----------- // // 綁定服務(wù)到容器 $container['standard'] = new StdClass(); // 獲取服務(wù) $standard = $container['standard']; var_dump($standard);
2. 封裝成類
為了方便維護(hù),我們把上面的數(shù)組封裝到類里面。
$instances還是上面的容器數(shù)組。我們增加兩個(gè)方法,instance用來綁定服務(wù),get用來從容器中獲取服務(wù)。
class BaseContainer { // 已綁定的服務(wù) protected $instances = []; // 綁定服務(wù) public function instance($name, $instance) { $this->instances[$name] = $instance; } // 獲取服務(wù) public function get($name) { return isset($this->instances[$name]) ? $this->instances[$name] : null; } } // ----------- ↓↓↓↓示例代碼↓↓↓↓ ----------- // $container = new BaseContainer(); // 綁定服務(wù) $container->instance('StdClass', new StdClass()); // 獲取服務(wù) $stdClass = $container->get('StdClass'); var_dump($stdClass);
3. 按需實(shí)例化
現(xiàn)在我們在綁定一個(gè)對象服務(wù)的時(shí)候,就必須要先把類實(shí)例化,如果綁定的服務(wù)沒有被用到,那么類就會(huì)白白實(shí)例化,造成性能浪費(fèi)。
為了解決這個(gè)問題,我們增加一個(gè)bind函數(shù),它支持綁定一個(gè)回調(diào)函數(shù),在回調(diào)函數(shù)中實(shí)例化類。這樣一來,我們只有在使用服務(wù)時(shí),才回調(diào)這個(gè)函數(shù),這樣就實(shí)現(xiàn)了按需實(shí)例化。
這時(shí)候,我們獲取服務(wù)時(shí),就不只是從數(shù)組中拿到服務(wù)并返回了,還需要判斷如果是回調(diào)函數(shù),就要執(zhí)行回調(diào)函數(shù)。所以我們把get方法的名字改成make。意思就是生產(chǎn)一個(gè)服務(wù),這個(gè)服務(wù)可以是已綁定的服務(wù),也可以是已綁定的回調(diào)函數(shù),也可以是一個(gè)類名,如果是類名,我們就直接實(shí)例化該類并返回。
然后,我們增加一個(gè)新數(shù)組$bindings,用來存儲(chǔ)綁定的回調(diào)函數(shù)。然后我們把bind方法改一下,判斷下$instance如果是一個(gè)回調(diào)函數(shù),就放到$bindings數(shù)組,否則就用make方法實(shí)例化類。
class DeferContainer extend BaseContainer { // 已綁定的回調(diào)函數(shù) protected $bindings = []; // 綁定服務(wù) public function bind($name, $instance) { if ($instance instanceof Closure) { // 如果$instance是一個(gè)回調(diào)函數(shù),就綁定到bindings。 $this->bindings[$name] = $instance; } else { // 調(diào)用make方法,創(chuàng)建實(shí)例 $this->instances[$name] = $this->make($name); } } // 獲取服務(wù) public function make($name) { if (isset($this->instances[$name])) { return $this->instances[$name]; } if (isset($this->bindings[$name])) { // 執(zhí)行回調(diào)函數(shù)并返回 $instance = call_user_func($this->bindings[$name]); } else { // 還沒有綁定到容器中,直接new. $instance = new $name(); } return $instance; } } // ----------- ↓↓↓↓示例代碼↓↓↓↓ ----------- // $container = new DeferContainer(); // 綁定服務(wù) $container->bind('StdClass', function () { echo "我被執(zhí)行了\n"; return new StdClass(); }); // 獲取服務(wù) $stdClass = $container->make('StdClass'); var_dump($stdClass);
StdClass這個(gè)服務(wù)綁定的是一個(gè)回調(diào)函數(shù),在回調(diào)函數(shù)中才會(huì)真正的實(shí)例化類。如果沒有用到這個(gè)服務(wù),那回調(diào)函數(shù)就不會(huì)被執(zhí)行,類也不會(huì)被實(shí)例化。
4. 單例
從上面的代碼中可以看出,每次調(diào)用make方法時(shí),都會(huì)執(zhí)行一次回調(diào)函數(shù),并返回一個(gè)新的類實(shí)例。但是在某些情況下,我們希望這個(gè)實(shí)例是一個(gè)單例,無論make多少次,只實(shí)例化一次。
這時(shí)候,我們給bind方法增加第三個(gè)參數(shù)$shared,用來標(biāo)記是否是單例,默認(rèn)不是單例。然后把回調(diào)函數(shù)和這個(gè)標(biāo)記都存到$bindings數(shù)組里。
為了方便綁定單例服務(wù),再增加一個(gè)新的方法singleton,它直接調(diào)用bind,并且$shared參數(shù)強(qiáng)制為true。
對于make方法,我們也要做修改。在執(zhí)行$bindings里的回調(diào)函數(shù)以后,做一個(gè)判斷,如果之前綁定時(shí)標(biāo)記的shared是true,就把回調(diào)函數(shù)返回的結(jié)果存儲(chǔ)到$instances里。由于我們是先從$instances里找服務(wù),所以這樣下次再make的時(shí)候就會(huì)直接返回,而不會(huì)再次執(zhí)行回調(diào)函數(shù)。這樣就實(shí)現(xiàn)了單例的綁定。
class SingletonContainer extends DeferContainer { // 綁定服務(wù) public function bind($name, $instance, $shared = false) { if ($instance instanceof Closure) { // 如果$instance是一個(gè)回調(diào)函數(shù),就綁定到bindings。 $this->bindings[$name] = [ 'callback' => $instance, // 標(biāo)記是否單例 'shared' => $shared ]; } else { // 調(diào)用make方法,創(chuàng)建實(shí)例 $this->instances[$name] = $this->make($name); } } // 綁定一個(gè)單例 public function singleton($name, $instance) { $this->bind($name, $instance, true); } // 獲取服務(wù) public function make($name) { if (isset($this->instances[$name])) { return $this->instances[$name]; } if (isset($this->bindings[$name])) { // 執(zhí)行回調(diào)函數(shù)并返回 $instance = call_user_func($this->bindings[$name]['callback']); if ($this->bindings[$name]['shared']) { // 標(biāo)記為單例時(shí),存儲(chǔ)到服務(wù)中 $this->instances[$name] = $instance; } } else { // 還沒有綁定到容器中,直接new. $instance = new $name(); } return $instance; } } // ----------- ↓↓↓↓示例代碼↓↓↓↓ ----------- // $container = new SingletonContainer(); // 綁定服務(wù) $container->singleton('anonymous', function () { return new class { public function __construct() { echo "我被實(shí)例化了\n"; } }; }); // 無論make多少次,只會(huì)實(shí)例化一次 $container->make('anonymous'); $container->make('anonymous'); // 獲取服務(wù) $anonymous = $container->make('anonymous'); var_dump($anonymous)
上面的代碼用singleton綁定了一個(gè)名為anonymous的服務(wù),回調(diào)函數(shù)里返回了一個(gè)匿名類的實(shí)例。這個(gè)匿名類在被實(shí)例化時(shí)會(huì)輸出一段文字。無論我們make多少次anonymous,這個(gè)回調(diào)函數(shù)只會(huì)被執(zhí)行一次,匿名類也只會(huì)被實(shí)例化一次。
5. 自動(dòng)注入
自動(dòng)注入是Ioc容器的核心,沒有自動(dòng)注入就無法做到控制反轉(zhuǎn)。
自動(dòng)注入就是指,在實(shí)例化一個(gè)類時(shí),用反射類來獲取__construct所需要的參數(shù),然后根據(jù)參數(shù)的類型,從容器中找到已綁定的服務(wù)。我們只要有了__construct方法所需的所有參數(shù),就能自動(dòng)實(shí)例化該類,實(shí)現(xiàn)自動(dòng)注入。
現(xiàn)在,我們增加一個(gè)build方法,它只接收一個(gè)參數(shù),就是類名。build方法會(huì)用反射類來獲取__construct方法所需要的參數(shù),然后返回實(shí)例化結(jié)果。
另外一點(diǎn)就是,我們之前在調(diào)用make方法時(shí),如果傳的是一個(gè)未綁定的類,我們直接new了這個(gè)類?,F(xiàn)在我們把未綁定的類交給build方法來構(gòu)建,因?yàn)樗С肿詣?dòng)注入。
class InjectionContainer extends SingletonContainer { // 獲取服務(wù) public function make($name) { if (isset($this->instances[$name])) { return $this->instances[$name]; } if (isset($this->bindings[$name])) { // 執(zhí)行回調(diào)函數(shù)并返回 $instance = call_user_func($this->bindings[$name]['callback']); if ($this->bindings[$name]['shared']) { // 標(biāo)記為單例時(shí),存儲(chǔ)到服務(wù)中 $this->instances[$name] = $instance; } } else { // 使用build方法構(gòu)建此類 $instance = $this->build($name); } return $instance; } // 構(gòu)建一個(gè)類,并自動(dòng)注入服務(wù) public function build($class) { $reflector = new ReflectionClass($class); $constructor = $reflector->getConstructor(); if (is_null($constructor)) { // 沒有構(gòu)造函數(shù),直接new return new $class(); } $dependencies = []; // 獲取構(gòu)造函數(shù)所需的參數(shù) foreach ($constructor->getParameters() as $dependency) { if (is_null($dependency->getClass())) { // 參數(shù)類型不是類時(shí),無法從容器中獲取依賴 if ($dependency->isDefaultValueAvailable()) { // 查找參數(shù)的默認(rèn)值,如果有就使用默認(rèn)值 $dependencies[] = $dependency->getDefaultValue(); } else { // 無法提供類所依賴的參數(shù) throw new Exception('找不到依賴參數(shù):' . $dependency->getName()); } } else { // 參數(shù)類型是類時(shí),就用make方法構(gòu)建該類 $dependencies[] = $this->make($dependency->getClass()->name); } } return $reflector->newInstanceArgs($dependencies); } } // ----------- ↓↓↓↓示例代碼↓↓↓↓ ----------- // class Redis { } class Cache { protected $redis; // 構(gòu)造函數(shù)中依賴Redis服務(wù) public function __construct(Redis $redis) { $this->redis = $redis; } } $container = new InjectionContainer(); // 綁定Redis服務(wù) $container->singleton(Redis::class, function () { return new Redis(); }); // 構(gòu)建Cache類 $cache = $container->make(Cache::class); var_dump($cache);
6. 自定義依賴參數(shù)
現(xiàn)在有個(gè)問題,如果類依賴的參數(shù)不是類或接口,只是一個(gè)普通變量,這時(shí)候就無法從容器中獲取依賴參數(shù)了,也就無法實(shí)例化類了。
那么接下來我們就支持一個(gè)新功能,在調(diào)用make方法時(shí),支持傳第二個(gè)參數(shù)$parameters,這是一個(gè)數(shù)組,無法從容器中獲取的依賴,就從這個(gè)數(shù)組中找。
當(dāng)然,make方法是用不到這個(gè)參數(shù)的,因?yàn)樗回?fù)責(zé)實(shí)例化類,它直接傳給build方法。在build方法尋找依賴的參數(shù)時(shí),就先從$parameters中找。這樣就實(shí)現(xiàn)了自定義依賴參數(shù)。
需要注意的一點(diǎn)是,build方法是按照參數(shù)的名字來找依賴的,所以parameters中的鍵名也必須跟__construct中參數(shù)名一致。
class ParametersContainer extends InjectionContainer { // 獲取服務(wù) public function make($name, array $parameters = []) { if (isset($this->instances[$name])) { return $this->instances[$name]; } if (isset($this->bindings[$name])) { // 執(zhí)行回調(diào)函數(shù)并返回 $instance = call_user_func($this->bindings[$name]['callback']); if ($this->bindings[$name]['shared']) { // 標(biāo)記為單例時(shí),存儲(chǔ)到服務(wù)中 $this->instances[$name] = $instance; } } else { // 使用build方法構(gòu)建此類 $instance = $this->build($name, $parameters); } return $instance; } // 構(gòu)建一個(gè)類,并自動(dòng)注入服務(wù) public function build($class, array $parameters = []) { $reflector = new ReflectionClass($class); $constructor = $reflector->getConstructor(); if (is_null($constructor)) { // 沒有構(gòu)造函數(shù),直接new return new $class(); } $dependencies = []; // 獲取構(gòu)造函數(shù)所需的參數(shù) foreach ($constructor->getParameters() as $dependency) { if (isset($parameters[$dependency->getName()])) { // 先從自定義參數(shù)中查找 $dependencies[] = $parameters[$dependency->getName()]; continue; } if (is_null($dependency->getClass())) { // 參數(shù)類型不是類或接口時(shí),無法從容器中獲取依賴 if ($dependency->isDefaultValueAvailable()) { // 查找默認(rèn)值,如果有就使用默認(rèn)值 $dependencies[] = $dependency->getDefaultValue(); } else { // 無法提供類所依賴的參數(shù) throw new Exception('找不到依賴參數(shù):' . $dependency->getName()); } } else { // 參數(shù)類型是類時(shí),就用make方法構(gòu)建該類 $dependencies[] = $this->make($dependency->getClass()->name); } } return $reflector->newInstanceArgs($dependencies); } } // ----------- ↓↓↓↓示例代碼↓↓↓↓ ----------- // class Redis { } class Cache { protected $redis; protected $name; protected $default; // 構(gòu)造函數(shù)中依賴Redis服務(wù)和name參數(shù),name的類型不是類,無法從容器中查找 public function __construct(Redis $redis, $name, $default = '默認(rèn)值') { $this->redis = $redis; $this->name = $name; $this->default = $default; } } $container = new ParametersContainer(); // 綁定Redis服務(wù) $container->singleton(Redis::class, function () { return new Redis(); }); // 構(gòu)建Cache類 $cache = $container->make(Cache::class, ['name' => 'test']); var_dump($cache);
提示:實(shí)際上,Laravel容器的build方法并沒有第二個(gè)參數(shù)$parameters,它是用類屬性來維護(hù)自定義參數(shù)。原理都是一樣的,只是實(shí)現(xiàn)方式不一樣。這里為了方便理解,不引入過多概念。
7. 服務(wù)別名
別名可以理解成小名、外號。服務(wù)別名就是給已綁定的服務(wù)設(shè)置一些外號,使我們通過外號也能找到該服務(wù)。
這個(gè)就比較簡單了,我們增加一個(gè)新的數(shù)組$aliases,用來存儲(chǔ)別名。再增加一個(gè)方法alias,用來讓外部注冊別名。
唯一需要我們修改的地方,就是在make時(shí),要先從$aliases中找到真實(shí)的服務(wù)名。
class AliasContainer extends ParametersContainer { // 服務(wù)別名 protected $aliases = []; // 給服務(wù)綁定一個(gè)別名 public function alias($alias, $name) { $this->aliases[$alias] = $name; } // 獲取服務(wù) public function make($name, array $parameters = []) { // 先用別名查找真實(shí)服務(wù)名 $name = isset($this->aliases[$name]) ? $this->aliases[$name] : $name; return parent::make($name, $parameters); } } // ----------- ↓↓↓↓示例代碼↓↓↓↓ ----------- // $container = new AliasContainer(); // 綁定服務(wù) $container->instance('text', '這是一個(gè)字符串'); // 給服務(wù)注冊別名 $container->alias('string', 'text'); $container->alias('content', 'text'); var_dump($container->make('string')); var_dump($container->make('content'));
8. 擴(kuò)展綁定
有時(shí)候我們需要給已綁定的服務(wù)做一個(gè)包裝,這時(shí)候就用到擴(kuò)展綁定了。我們先看一個(gè)實(shí)際的用法,理解它的作用后,才看它是如何實(shí)現(xiàn)的。
// 綁定日志服務(wù) $container->singleton('log', new Log()); // 對已綁定的服務(wù)再次包裝 $container->extend('log', function(Log $log){ // 返回了一個(gè)新服務(wù) return new RedisLog($log); });
現(xiàn)在我們看它是如何實(shí)現(xiàn)的。增加一個(gè)$extenders數(shù)組,用來存放擴(kuò)展器。再增加一個(gè)extend方法,用來注冊擴(kuò)展器。
然后在make方法返回$instance之前,按順序依次調(diào)用之前注冊的擴(kuò)展器。
class ExtendContainer extends AliasContainer { // 存放擴(kuò)展器的數(shù)組 protected $extenders = []; // 給服務(wù)綁定擴(kuò)展器 public function extend($name, $extender) { if (isset($this->instances[$name])) { // 已經(jīng)實(shí)例化的服務(wù),直接調(diào)用擴(kuò)展器 $this->instances[$name] = $extender($this->instances[$name]); } else { $this->extenders[$name][] = $extender; } } // 獲取服務(wù) public function make($name, array $parameters = []) { $instance = parent::make($name, $parameters); if (isset($this->extenders[$name])) { // 調(diào)用擴(kuò)展器 foreach ($this->extenders[$name] as $extender) { $instance = $extender($instance); } } return $instance; } } // ----------- ↓↓↓↓示例代碼↓↓↓↓ ----------- // class Redis { public $name; public function __construct($name = 'default') { $this->name = $name; } public function setName($name) { $this->name = $name; } } $container = new ExtendContainer(); // 綁定Redis服務(wù) $container->singleton(Redis::class, function () { return new Redis(); }); // 給Redis服務(wù)綁定一個(gè)擴(kuò)展器 $container->extend(Redis::class, function (Redis $redis) { $redis->setName('擴(kuò)展器'); return $redis; }); $redis = $container->make(Redis::class); var_dump($redis->name);
9. 上下文綁定
有時(shí)侯我們可能有兩個(gè)類使用同一個(gè)接口,但希望在每個(gè)類中注入不同的實(shí)現(xiàn),例如兩個(gè)控制器,分別為它們注入不同的Log服務(wù)。
class ApiController { public function __construct(Log $log) { } } class WebController { public function __construct(Log $log) { } }
最終我們要用以下方式實(shí)現(xiàn):
// 當(dāng)ApiController依賴Log時(shí),給它一個(gè)RedisLog $container->addContextualBinding('ApiController','Log',new RedisLog()); // 當(dāng)WebController依賴Log時(shí),給它一個(gè)FileLog $container->addContextualBinding('WebController','Log',new FileLog());
為了更直觀更方便更語義化的使用,我們把這個(gè)過程改成鏈?zhǔn)讲僮鳎?/p>
$container->when('ApiController') ->needs('Log') ->give(new RedisLog());
我們增加一個(gè)$context數(shù)組,用來存儲(chǔ)上下文。同時(shí)增加一個(gè)addContextualBinding方法,用來注冊上下文綁定。以ApiController為例,$context的真實(shí)模樣是:
$context['ApiController']['Log'] = new RedisLog();
然后build方法實(shí)例化類時(shí),先從上下文中查找依賴參數(shù),就實(shí)現(xiàn)了上下文綁定。
接下來,看看鏈?zhǔn)讲僮魇侨绾螌?shí)現(xiàn)的。
首先定義一個(gè)類Context,這個(gè)類有兩個(gè)方法,needs和give。
然后在容器中,增加一個(gè)when方法,它返回一個(gè)Context對象。在Context對象的give方法中,我們已經(jīng)具備了注冊上下文所需要的所有參數(shù),所以就可以在give方法中調(diào)用addContextualBinding來注冊上下文了。
class ContextContainer extends ExtendContainer { // 依賴上下文 protected $context = []; // 構(gòu)建一個(gè)類,并自動(dòng)注入服務(wù) public function build($class, array $parameters = []) { $reflector = new ReflectionClass($class); $constructor = $reflector->getConstructor(); if (is_null($constructor)) { // 沒有構(gòu)造函數(shù),直接new return new $class(); } $dependencies = []; // 獲取構(gòu)造函數(shù)所需的參數(shù) foreach ($constructor->getParameters() as $dependency) { if (isset($this->context[$class]) && isset($this->context[$class][$dependency->getName()])) { // 先從上下文中查找 $dependencies[] = $this->context[$class][$dependency->getName()]; continue; } if (isset($parameters[$dependency->getName()])) { // 從自定義參數(shù)中查找 $dependencies[] = $parameters[$dependency->getName()]; continue; } if (is_null($dependency->getClass())) { // 參數(shù)類型不是類或接口時(shí),無法從容器中獲取依賴 if ($dependency->isDefaultValueAvailable()) { // 查找默認(rèn)值,如果有就使用默認(rèn)值 $dependencies[] = $dependency->getDefaultValue(); } else { // 無法提供類所依賴的參數(shù) throw new Exception('找不到依賴參數(shù):' . $dependency->getName()); } } else { // 參數(shù)類型是一個(gè)類時(shí),就用make方法構(gòu)建該類 $dependencies[] = $this->make($dependency->getClass()->name); } } return $reflector->newInstanceArgs($dependencies); } // 綁定上下文 public function addContextualBinding($when, $needs, $give) { $this->context[$when][$needs] = $give; } // 支持鏈?zhǔn)椒绞浇壎ㄉ舷挛? public function when($when) { return new Context($when, $this); } } class Context { protected $when; protected $needs; protected $container; public function __construct($when, ContextContainer $container) { $this->when = $when; $this->container = $container; } public function needs($needs) { $this->needs = $needs; return $this; } public function give($give) { // 調(diào)用容器綁定依賴上下文 $this->container->addContextualBinding($this->when, $this->needs, $give); } } // ----------- ↓↓↓↓示例代碼↓↓↓↓ ----------- // class Dog { public $name; public function __construct($name) { $this->name = $name; } } class Cat { public $name; public function __construct($name) { $this->name = $name; } } $container = new ContextContainer(); // 給Dog類設(shè)置上下文綁定 $container->when(Dog::class) ->needs('name') ->give('小狗'); // 給Cat類設(shè)置上下文綁定 $container->when(Cat::class) ->needs('name') ->give('小貓'); $dog = $container->make(Dog::class); $cat = $container->make(Cat::class); var_dump('Dog:' . $dog->name); var_dump('Cat:' . $cat->name);
10. 完整代碼
class Container { // 已綁定的服務(wù) protected $instances = []; // 已綁定的回調(diào)函數(shù) protected $bindings = []; // 服務(wù)別名 protected $aliases = []; // 存放擴(kuò)展器的數(shù)組 protected $extenders = []; // 依賴上下文 protected $context = []; // 綁定服務(wù)實(shí)例 public function instance($name, $instance) { $this->instances[$name] = $instance; } // 綁定服務(wù) public function bind($name, $instance, $shared = false) { if ($instance instanceof Closure) { // 如果$instance是一個(gè)回調(diào)函數(shù),就綁定到bindings。 $this->bindings[$name] = [ 'callback' => $instance, // 標(biāo)記是否單例 'shared' => $shared ]; } else { // 調(diào)用make方法,創(chuàng)建實(shí)例 $this->instances[$name] = $this->make($name); } } // 綁定一個(gè)單例 public function singleton($name, $instance) { $this->bind($name, $instance, true); } // 給服務(wù)綁定一個(gè)別名 public function alias($alias, $name) { $this->aliases[$alias] = $name; } // 給服務(wù)綁定擴(kuò)展器 public function extend($name, $extender) { if (isset($this->instances[$name])) { // 已經(jīng)實(shí)例化的服務(wù),直接調(diào)用擴(kuò)展器 $this->instances[$name] = $extender($this->instances[$name]); } else { $this->extenders[$name][] = $extender; } } // 獲取服務(wù) public function make($name, array $parameters = []) { // 先用別名查找真實(shí)服務(wù)名 $name = isset($this->aliases[$name]) ? $this->aliases[$name] : $name; if (isset($this->instances[$name])) { return $this->instances[$name]; } if (isset($this->bindings[$name])) { // 執(zhí)行回調(diào)函數(shù)并返回 $instance = call_user_func($this->bindings[$name]['callback']); if ($this->bindings[$name]['shared']) { // 標(biāo)記為單例時(shí),存儲(chǔ)到服務(wù)中 $this->instances[$name] = $instance; } } else { // 使用build方法構(gòu)建此類 $instance = $this->build($name, $parameters); } if (isset($this->extenders[$name])) { // 調(diào)用擴(kuò)展器 foreach ($this->extenders[$name] as $extender) { $instance = $extender($instance); } } return $instance; } // 構(gòu)建一個(gè)類,并自動(dòng)注入服務(wù) public function build($class, array $parameters = []) { $reflector = new ReflectionClass($class); $constructor = $reflector->getConstructor(); if (is_null($constructor)) { // 沒有構(gòu)造函數(shù),直接new return new $class(); } $dependencies = []; // 獲取構(gòu)造函數(shù)所需的參數(shù) foreach ($constructor->getParameters() as $dependency) { if (isset($this->context[$class]) && isset($this->context[$class][$dependency->getName()])) { // 先從上下文中查找 $dependencies[] = $this->context[$class][$dependency->getName()]; continue; } if (isset($parameters[$dependency->getName()])) { // 從自定義參數(shù)中查找 $dependencies[] = $parameters[$dependency->getName()]; continue; } if (is_null($dependency->getClass())) { // 參數(shù)類型不是類或接口時(shí),無法從容器中獲取依賴 if ($dependency->isDefaultValueAvailable()) { // 查找默認(rèn)值,如果有就使用默認(rèn)值 $dependencies[] = $dependency->getDefaultValue(); } else { // 無法提供類所依賴的參數(shù) throw new Exception('找不到依賴參數(shù):' . $dependency->getName()); } } else { // 參數(shù)類型是一個(gè)類時(shí),就用make方法構(gòu)建該類 $dependencies[] = $this->make($dependency->getClass()->name); } } return $reflector->newInstanceArgs($dependencies); } // 綁定上下文 public function addContextualBinding($when, $needs, $give) { $this->context[$when][$needs] = $give; } // 支持鏈?zhǔn)椒绞浇壎ㄉ舷挛? public function when($when) { return new Context($when, $this); } } class Context { protected $when; protected $needs; protected $container; public function __construct($when, Container $container) { $this->when = $when; $this->container = $container; } public function needs($needs) { $this->needs = $needs; return $this; } public function give($give) { // 調(diào)用容器綁定依賴上下文 $this->container->addContextualBinding($this->when, $this->needs, $give); } }
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
CI框架實(shí)現(xiàn)遞歸生成文件路徑并重新生成圖片功能
這篇文章主要介紹了CI框架實(shí)現(xiàn)遞歸生成文件路徑并重新生成圖片功能,涉及CodeIgniter框架自定義圖片控制器類實(shí)現(xiàn)文件目錄遞歸以及調(diào)用圖片處理擴(kuò)展類進(jìn)行圖片生成相關(guān)操作技巧,需要的朋友可以參考下2018-06-06php寫一個(gè)函數(shù),實(shí)現(xiàn)掃描并打印出自定目錄下(含子目錄)所有jpg文件名
下面小編就為大家?guī)硪黄猵hp寫一個(gè)函數(shù),實(shí)現(xiàn)掃描并打印出自定目錄下(含子目錄)所有jpg文件名。2017-05-05php簡單的留言板與回復(fù)功能具體實(shí)現(xiàn)
留言板是在剛接觸php時(shí)用來學(xué)習(xí)的一個(gè)簡單的應(yīng)用例子了,今天我再給初學(xué)php的朋友提供一個(gè)完整的php留言板的全部制作過程,希望對你會(huì)有幫助2014-02-02