從零開始理解 Laravel 依賴注入

你們在使用 Laravel 的過程當中,可能會感受到在 Laravel 裏不少神奇的東西會發生。依賴注入彷佛是一個。但它真的很神奇嗎?php

Laravel 容器(Container)

Laravel中的服務容器其實就是一個依賴注入容器和應用程序的註冊表。數組

Laravel Container 是用於管理依賴項和存儲對象的強大工具,可用於各類目的; 能夠存儲對象並在Facades中使用它們。app

Laravel一般使用依賴注入。即便訪問 Request 咱們也可使用注入,好比。ide

public function __construct(Request $request)

當嘗試向類中注入對象時,Container 使用 Reflection API 檢查構造函數方法並檢索依賴項的內容。函數

Reflection Api 是什麼

首先,反射API從深度維度中獲取能量(抽象理解就行了),所以在使用反射API時必須當心。使用它,但不要濫用它。當檢查許多對象時,反射是昂貴的,它有可能擾亂整個宇宙(有點誇張哈)。工具

反射一般被定義爲程序能力,主要是指檢查自身並在執行時修改其邏輯。學習

能夠從官網查看 PHP.net 具體描述。ui

從 PHP5開始 PHP帶有一個完整的反射API,增長了對類,接口,函數,方法和擴展進行逆向工程的能力。另外,反射API提供了檢索函數,類和方法的doc註釋的方法。 發射在PHP中很流行。事實上,有幾種狀況即便不知道它也可使用它。一些PHP的內置函數間接使用了反射API--其中一個是call_user_func 函數。this

咱們可能常用 get_classget_class_method 來理解別人的代碼。.net

下面咱們來快速看看如何使用 Reflection API 來處理函數。

ReflectionFunction

可使用 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 中 公開了一組用於獲取對象信息的方法。

咱們將使用它來得到依賴關係。但首先咱們須要首先看看 構造函數

這實際上很簡單,你想知道的一切均可以在 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
}

咱們必須知道類名來解決依賴關係,如今讓咱們停下來一分鐘,弄清楚這個過程:。

經典的PHP代碼

如下是不使用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。看看 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

相關文章
相關標籤/搜索