你們在使用 Laravel 的過程當中,可能會感受到在 Laravel 裏不少神奇的東西會發生。依賴注入彷佛是一個。但它真的很神奇嗎?php
Laravel中的服務容器其實就是一個依賴注入容器和應用程序的註冊表。數組
Laravel Container
是用於管理依賴項和存儲對象的強大工具,可用於各類目的; 能夠存儲對象並在Facades中使用它們。app
Laravel一般使用依賴注入。即便訪問 Request
咱們也可使用注入,好比。ide
public function __construct(Request $request)
當嘗試向類中注入對象時,Container 使用 Reflection API
檢查構造函數方法並檢索依賴項的內容。函數
首先,反射API從深度維度中獲取能量(抽象理解就行了),所以在使用反射API時必須當心。使用它,但不要濫用它。當檢查許多對象時,反射是昂貴的,它有可能擾亂整個宇宙(有點誇張哈)。工具
反射一般被定義爲程序能力,主要是指檢查自身並在執行時修改其邏輯。學習
能夠從官網查看 PHP.net 具體描述。ui
從 PHP5開始 PHP帶有一個完整的反射API,增長了對類,接口,函數,方法和擴展進行逆向工程的能力。另外,反射API提供了檢索函數,類和方法的doc註釋的方法。 發射在PHP中很流行。事實上,有幾種狀況即便不知道它也可使用它。一些PHP的內置函數間接使用了反射API--其中一個是
call_user_func
函數。this
咱們可能常用 get_class
和 get_class_method
來理解別人的代碼。.net
下面咱們來快速看看如何使用 Reflection API
來處理函數。
可使用 ReflectionFunction
獲取函數的信息。
<?php function functionWithParameters($param1){ } $reflectionFunction = new ReflectionFunction('functionWithParameters'); $name = $reflectionFunction->getName(); // functionWithParameters $parameters = $reflectionFunction->getParameters(); /* Array ( [0] => ReflectionParameter Object ( [name] => param1 ) ) */
容器大多數狀況和類一塊兒工做,因此咱們須要學習如何使用 ReflectionClass
。在 ReflectionClass
中 公開了一組用於獲取對象信息的方法。
咱們將使用它來得到依賴關係。但首先咱們須要首先看看 構造函數。
這實際上很簡單,你想知道的一切均可以在 ReflectionClass 上查看。
<?php class OurDependencyClass{} class OurTestClass { public function __construct(OurDependencyClass $anotherClass) { } } $reflectedClass = new ReflectionClass(new OurTestClass()); // or $reflectedClass = new ReflectionClass('OurTestClass');
你能夠將實例或類名稱提供給 ReflectionClass
。它能夠解析給定的參數。
咱們能夠經過調用 getConstructor
方法來檢查構造函數。它返回一個 ReflectionMethod
,它包含咱們須要的幾乎全部東西。
<?php $reflection = new ReflectionClass('OurTestClass'); $constructor = $reflection->getConstructor();
咱們須要檢查參數,前面已經解釋過 ReflectionFuction
。
警告: 若是類沒有構造函數方法,則 $constructor
將被賦值爲 null
。因此你也應該檢查一下。
<?php // now we can retrieve out parameters $parameters = $constructor->getParameters(); /* array(1) { [0]=> object(ReflectionParameter)#3 (1) { ["name"]=> string(10) "otherClass" } } output will be like this */
它返回一個 ReflectionParameter
數組,並檢索有關函數或方法參數的信息。
如今,咱們將檢查全部這些參數並肯定咱們須要解決的問題。
<?php foreach ($parameters as $parameter) { $class = $parameter->getClass(); if(null === $class){ // this parameter doesn't have a class name // we can't resolve it so we will skip for now } $name = $class->name; // we'll use it later }
咱們必須知道類名來解決依賴關係,如今讓咱們停下來一分鐘,弄清楚這個過程:。
如下是不使用Container的代碼大體工做的方式:
Application
依賴類 Foo
, 因此咱們須要這麼作:Application
前建立 Foo
Application
中調用 Foo
Foo
依賴 Bar
(好比一個 service
), 因此:Foo
前,先建立 Bar
Foo
中調用 Bar
Bar
依賴 Bim
(好比多是一個 service
, 也多是 repository
, …), 因此:Bar
前先要建立 Bim
Bar
does something感受如何?
如下是使用Container的代碼大體工做的方式:
Application
依賴 Foo
, Foo
依賴 Bar
, Bar
依賴 Bim
, 因此:Application
直接發現的是 Bim
, 因此直接建立 Bim
Bim
時發現須要 Bar
, 因此 Application
建立 Bar
並返回給 Bim
Bar
時發現須要 Foo
, 因此 Application
建立 Foo
並返回給 Bar
Application
調用 Foo
Foo
調用 Bar
Bar
調用 Bim
Bim
does something這是 控制反轉 的模式。被調用者和被調用者之間的依賴性控制是相反的。
下面咱們在代碼中模擬這種狀況。
讓咱們再看看咱們的代碼。咱們挖掘了構造函數的參數,如今咱們知道咱們須要解決什麼。所須要的是一個遞歸函數,直到沒法解決爲止。讓咱們把全部這些放在一塊兒。
<?php class Container { /** * * @param mixed $class * */ public function make($class) { // pass $class into ReflectionClass // note that: ReflectionClass may throw an Exception if user puts // a class name that doesn't exist. $reflection = new ReflectionClass($class); $constructor = $reflection->getConstructor(); // we'll store the parameters that we resolved $resolvedParameters = []; foreach ($constructor->getParameters() as $parameter){ $parameterClass = $parameter->getClass(); if(null === $parameterClass){ // this parameter is probably is a primitive variable // we can't resolve it so we will skip for now } $parameterName = $parameter->getName(); $className = $parameterClass->name; // this function is becoming resursive now. // it'll continue 'till nothing left. $resolvedParameters[$parameterName] = $this->make($className); // we need to know which value belongs to which parameter // so we'll store as an associative array. } } }
不要試圖運行這個!它確定會失敗的。
咱們也須要解決原始變量,也就是參數。因此咱們只需在咱們的 make
方法中添加一個可選參數。
<?php /* change the method definition as follows; public function make($class, $params = []) */ $parameterName = $parameter->getName(); if(null === $parameterClass){ // if our primitive parameter given by user we'll use it // if not, we'll just throw an Exception if(isset($params[$parameterName])){ // this is just a very simple example // in real world you have to check whether this parameter passed by // reference or not $resolvedParameters[$parameterName]= $params[$parameterName]; }else{ throw new Exception( sprintf('Container could not solve %s parameter', $parameterName) ); } }
警告: 咱們只考慮變量是否存在。但在現實世界中,你必須考慮更多的狀況。如;它是一個可選參數嗎?它有默認值嗎?
咱們將建立一個新的實例來返回它。
<?php // this will create and return a new instance of given class. return $reflection->newInstanceArgs($resolvedParameters);
就是這麼簡單!可是咱們的工做尚未完成。咱們來看看代碼如今的樣子。
<?php class Container { /** * * @param mixed $class * @param array $params * */ public function make($class, array $params = []) { // pass $class into ReflectionClass // note that: ReflectionClass may throw an Exception if user puts // a class name that doesn't exist. $reflection = new ReflectionClass($class); // if object does not have an constructor method, $constructor will be assigned null. // you better have check this too $constructor = $reflection->getConstructor(); // we'll store the parameters that we resolved $resolvedParameters = []; foreach ($constructor->getParameters() as $parameter) { $parameterClass = $parameter->getClass(); $className = $parameterClass->name; $parameterName = $parameter->getName(); if (null === $parameterClass) { // if our primitive parameter given by user we'll use it // if not, we'll just throw an Exception if (isset($params[$parameterName])) { // this is just a very simple example // in real world you have to check whether this parameter passed by // reference or not $resolvedParameters[$parameterName] = $params[$parameterName]; } else { throw new Exception( sprintf('Container could not solve %s parameter', $parameterName) ); } } else { // this function is becoming recursive now. // it'll continue 'till nothing left. $resolvedParameters[$parameterName] = $this->make($className); // we need to know which value belongs to which parameter // so we'll store as an associative array. } } return $reflection->newInstanceArgs($resolvedParameters); } }
到目前爲止,咱們已經學習了什麼是 Reflection API
以及咱們如何使用它來反射函數,參數和類。
讓咱們回到Laravel。看看 Laravel 是如何管理這一進展。讓咱們弄清楚。
<?php // When you call App::make or app()->make it refers to Container::make and it's just a duplication of Container::resolve class Container implements ArrayAccess, ContainerContract { /** * Resolve the given type from the container. * * @param string $abstract * @param array $parameters * @return mixed */ protected function resolve($abstract, $parameters = []) { $this->with[] = $parameters; $concrete = $this->getConcrete($abstract); // We're ready to instantiate an instance of the concrete type registered for // the binding. This will instantiate the types, as well as resolve any of // its "nested" dependencies recursively until all have gotten resolved. if ($this->isBuildable($concrete, $abstract)) { $object = $this->build($concrete); } else { $object = $this->make($concrete); } return $object; } }
原始功能更長,更復雜。我減小了大部分的複雜性。
Laravel檢查對象並肯定它是否能夠輕鬆實例化,或者是否須要首先解決「嵌套」依賴關係。
就像咱們作的同樣?
<?php /** * Instantiate a concrete instance of the given type. * * @param string $concrete * @return mixed * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function build($concrete) { $reflector = new ReflectionClass($concrete); // If the type is not instantiable, the developer is attempting to resolve // an abstract type such as an Interface of Abstract Class and there is // no binding registered for the abstractions so we need to bail out. if (! $reflector->isInstantiable()) { return $this->notInstantiable($concrete); } $this->buildStack[] = $concrete; $constructor = $reflector->getConstructor(); // If there are no constructors, that means there are no dependencies then // we can just resolve the instances of the objects right away, without // resolving any other types or dependencies out of these containers. if (is_null($constructor)) { array_pop($this->buildStack); return new $concrete; } $dependencies = $constructor->getParameters(); // Once we have all the constructor's parameters we can create each of the // dependency instances and then use the reflection instances to make a // new instance of this class, injecting the created dependencies in. $instances = $this->resolveDependencies( $dependencies ); array_pop($this->buildStack); return $reflector->newInstanceArgs($instances); }
繼續研究 Laravel 如何解決依賴關係。因此咱們再深刻一點。
<?php /** * Resolve all of the dependencies from the ReflectionParameters. * * @param array $dependencies * @return array */ protected function resolveDependencies(array $dependencies) { $results = []; foreach ($dependencies as $dependency) { // If this dependency has a override for this particular build we will use // that instead as the value. Otherwise, we will continue with this run // of resolutions and let reflection attempt to determine the result. if ($this->hasParameterOverride($dependency)) { $results[] = $this->getParameterOverride($dependency); continue; } // If the class is null, it means the dependency is a string or some other // primitive type which we can not resolve since it is not a class and // we will just bomb out with an error since we have no-where to go. $results[] = is_null($class = $dependency->getClass()) ? $this->resolvePrimitive($dependency) : $this->resolveClass($dependency); } return $results; }
好的。若是你不明白我會解釋;
Laravel檢查依賴關係是一個原始類仍是一個類,而且基於此進行處理。
<?php /** * Resolve a class based dependency from the container. * * @param \ReflectionParameter $parameter * @return mixed * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ protected function resolveClass(ReflectionParameter $parameter) { try { return $this->make($parameter->getClass()->name); } // If we can not resolve the class instance, we will check to see if the value // is optional, and if it is we will return the optional parameter value as // the value of the dependency, similarly to how we do this with scalars. catch (BindingResolutionException $e) { if ($parameter->isOptional()) { return $parameter->getDefaultValue(); } throw $e; } }
就這樣吧。看完上面的過程你應該對Container和依賴注入的工做原理有了更好的理解。
感謝閱讀 ^_^
歡迎留言討論。
更多PHP知識,能夠前往 PHPCasts