Laravel核心——Ioc服務容器源碼解析(服務器解析)

make解析

首先歡迎關注個人博客: www.leoyang90.cnphp

服務容器對對象的自動解析是服務容器的核心功能,make 函數、build 函數是實例化對象重要的核心,先大體看一下代碼:laravel

public function make($abstract)
{
    $abstract = $this->getAlias($abstract);

    if (isset($this->deferredServices[$abstract])) {
        $this->loadDeferredProvider($abstract);
    }

    return parent::make($abstract);
}
public function make($abstract)
    {
        return $this->resolve($abstract);
    }
    
    public function resolve($abstract, $parameters = [])
    {
        $abstract = $this->getAlias($abstract);
        
        $needsContextualBuild = ! empty($parameters) || ! is_null(
            $this->getContextualConcrete($abstract)
        );

        // If an instance of the type is currently being managed as a singleton we'll
        // just return an existing instance instead of instantiating new instances
        // so the developer can keep using the same objects instance every time.
        if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
            return $this->instances[$abstract];
        }

        $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);
        }

        // If we defined any extenders for this type, we'll need to spin through them
        // and apply them to the object being built. This allows for the extension
        // of services, such as changing configuration or decorating the object.
        foreach ($this->getExtenders($abstract) as $extender) {
            $object = $extender($object, $this);
        }

        // If the requested type is registered as a singleton we'll want to cache off
        // the instances in "memory" so we can return it later without creating an
        // entirely new instance of an object on each subsequent request for it.
        if ($this->isShared($abstract) && ! $needsContextualBuild) {
            $this->instances[$abstract] = $object;
        }

        $this->fireResolvingCallbacks($abstract, $object);

        $this->resolved[$abstract] = true;

        return $object;
    }

在講解解析流程以前,咱們先說說使用make函數進行解析的分類:segmentfault

Markdown

咱們詳細的講一下上圖。這裏我把使用make函數進行解析的狀況分爲大體兩種:數組

  • 解析對象沒有綁定過任何類,例如:閉包

$app->make('App\Http\Controllers\HomeController');
  • 解析對象綁定過實現類app

對於綁定過實現類的對象能夠分爲兩種:框架

  • 綁定的是類名,例如:ide

$app->when('App\Http\Controllers\HomeController')
->needs('App\Http\Requests\InRequest')
->give('App\Http\Requests\TestsRequest');
  • 綁定的是閉包函數

對於綁定的是閉包的又能夠分爲:學習

  • 用戶綁定閉包,例如:

$app->singleton('auth',function($app){
    return new AuthManager($app)
});//對象類直接實現方法

$app->singleton(EloquentFactory::class, function ($app) {
    return EloquentFactory::construct(
        $app->make(FakerGenerator::class), database_path('factories')
);//對象類依賴注入
});
  • 服務容器外包一層閉包函數(make/build),例如:

$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);//包裝make

$app->singleton(
    App\ConSole\Kernel::class,
);//包裝build

咱們在這裏先大體講一下服務容器解析的流程,值得注意的是其中 build 函數有可能會遞歸調用 make:

Markdown

  1. 獲取服務名稱。

  2. 加載延遲服務。判斷當前的接口是不是延遲服務提供者,如果延遲服務提供者,那麼還要對服務提供者進行註冊與啓動操做。

  3. 解析單例。若是接口服務是已經被解析過的單例對象,並且並不是上下文綁定,那麼直接取出對象。

  4. 獲取註冊的實現。實現方式多是上下文綁定的,也多是 binding 數組中的閉包,也有可能就是接口自己。

  5. build 解析。首先判斷是否須要遞歸。是,則遞歸 make;否,則調用 build 函數;直到調用 build 爲止

  6. 執行擴展。若當前解析對象存在擴展,運行擴展函數。

  7. 創造單例對象。若 shared 爲真,且不存在上下文綁定,則放入單例數組中

  8. 回調

  9. 標誌解析

下面咱們開始詳細分解代碼邏輯。因爲 getAlias 函數已經在 上一篇 講過,這裏不會再說。而loadDeferredProvider 函數做用是加載延遲服務,與容器解析關係不大,咱們放在之後再說。

獲取註冊的實現

獲取解析類的真正實現,函數優先去獲取上下文綁定的實現,不然獲取 binding 數組中的實現,獲取不到就是直接返回本身做爲實現:

protected function getConcrete($abstract)
{
    if (! is_null($concrete = $this->getContextualConcrete($abstract))) {
        return $concrete;
    }

    if (isset($this->bindings[$abstract])) {
        return $this->bindings[$abstract]['concrete'];
    }

    return $abstract;
}

通常來講,上下文綁定的服務是經過依賴注入來實現的:

$this->app->when(PhotoController::class)
          ->needs(Filesystem::class)
          ->give(function () {
              return Storage::disk('local');
          });
       
class PhotoController{
    protected $file;

    public function __construct(Filesystem $file){
      $this->file = $file;
    }
}

服務容器會在解析 PhotoController 的時候,經過放射獲取參數類型 Filesystem,而且會把 Filesystem 自動解析爲 Storage::disk('local')。如何實現的呢?首先,從 上一篇 文章咱們知道,當進行上下文綁定的時候,其實是維護 contextual 數組,經過上下文綁定,這個數組中存在:

contextual[PhotoController][Filesystem] = function () { return Storage::disk('local'); }

如果服務容器試圖構造 PhotoController 類,那麼因爲其構造函數依賴於 Filesystem,因此容器必須先生成 Filesystem 類,而後再注入到 PhotoController 中。

在構造 Filesystem 以前,服務容器會先把 PhotoController 放入 buildStack 中,繼而再去解析 Filesystem。

解析 Filesystem 時,運行 getContextualConcrete 函數:

protected function getContextualConcrete($abstract)
{
    if (! is_null($binding = $this->findInContextualBindings($abstract))) {
        return $binding;
    }
     
    if (empty($this->abstractAliases[$abstract])) {
        return;
    }

    foreach ($this->abstractAliases[$abstract] as $alias) {
        if (! is_null($binding = $this->findInContextualBindings($alias))) {
            return $binding;
        }
    }
}
 
protected function findInContextualBindings($abstract)
{
    if (isset($this->contextual[end($this->buildStack)][$abstract])) {
        return $this->contextual[end($this->buildStack)][$abstract];
    }
}

從上面能夠看出,getContextualConcrete 函數把當前解析的類(Filesystem)做爲 abstract,buildStack 最後一個類(PhotoController)做爲 concrete,尋找 this->contextual[concrete] [abstract] (contextual[PhotoController] [Filesystem])中的值,在這個例子裏面這個數組值就是那個匿名函數。

build 解析

對於服務容器來講,綁定是能夠遞歸的,例如:

$app->bind('a','b');
$app->bind('b','c');
$app->bind('c',function(){
    return new C;
  })

遇到這樣的狀況,bind 綁定中 getClosure 函數開始發揮做用,該函數會給類包一層閉包,閉包內調用 make 函數,服務容器會不斷遞歸調用 make 函數,直到最後一層,也就是綁定 c 的匿名函數。可是另外一方面,有一些綁定方式並無調用 bind 函數,例如上下文綁定 context:

$this->app->when(E::class)
          ->needs(F::class)
          ->give(A::class);

當make(E::class)的時候,getConcrete 返回 A 類,而不是調用 make 函數的閉包,因此並不會啓動遞歸流程獲得 C 的匿名函數,因此形成 A 類徹底沒法解析,isBuildable 函數就是解決這種問題的,當發現須要解析構造的對象頗有多是遞歸的,那麼就遞歸調用 make 函數,不然纔會調用build。

...
if ($this->isBuildable($concrete, $abstract)) {
        $object = $this->build($concrete);
    } else {
        $object = $this->make($concrete);
    }
...
     
protected function isBuildable($concrete, $abstract)
{
    return $concrete === $abstract || $concrete instanceof Closure;
}

執行擴展

獲取擴展閉包,並運行擴展函數:

protected function getExtenders($abstract)
{
    $abstract = $this->getAlias($abstract);

    if (isset($this->extenders[$abstract])) {
        return $this->extenders[$abstract];
    }

    return [];
}

回調

前後啓動全局的解析事件回調函數,再啓動針對類型的事件回調函數:

protected function fireResolvingCallbacks($abstract, $object)
{
    $this->fireCallbackArray($object, $this->globalResolvingCallbacks);

    $this->fireCallbackArray(
        $object, $this->getCallbacksForType($abstract, $object, $this->resolvingCallbacks)
    );

    $this->fireAfterResolvingCallbacks($abstract, $object);
}

protected function getCallbacksForType($abstract, $object, array $callbacksPerType)
{
    $results = [];

    foreach ($callbacksPerType as $type => $callbacks) {
        if ($type === $abstract || $object instanceof $type) {
            $results = array_merge($results, $callbacks);
        }
    }

    return $results;
}
 
protected function fireAfterResolvingCallbacks($abstract, $object)
{
    $this->fireCallbackArray($object, $this->globalAfterResolvingCallbacks);

    $this->fireCallbackArray(
        $object, $this->getCallbacksForType($abstract, $object, $this->afterResolvingCallbacks)
    );

build 解析


make 函數承擔瞭解析的大體框架,build 主要的職責就是利用反射將類構造出來,先看看主要代碼:

public function build($concrete)
{
    // If the concrete type is actually a Closure, we will just execute it and
    // hand back the results of the functions, which allows functions to be
    // used as resolvers for more fine-tuned resolution of these objects.
    if ($concrete instanceof Closure) {
         return $concrete($this, $this->getLastParameterOverride());
    }

    $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);
}

咱們下面詳細的說一下各個部分:

閉包函數執行

if ($concrete instanceof Closure) {
     return $concrete($this, $this->getLastParameterOverride());
}

這段代碼很簡單,可是做用很大。前面說過閉包函數有不少種類:

  • 用戶綁定時提供的直接提供實現類的方式:

$app->singleton('auth',function($app){
    return new AuthManager($app)
});//對象類直接實現方法

這種狀況 concrete(this) 直接就能夠解析構造出具體實現類,服務容器解析完畢。

  • 用戶綁定時提供的帶有依賴注入的實現:

$app->singleton(EloquentFactory::class, function ($app) {
    return EloquentFactory::construct(
        $app->make(FakerGenerator::class), database_path('factories')
);//對象類依賴注入

這種狀況下,concrete(this) 會轉而去解析 FakerGenerator::class,遞歸調用 make 函數。

  • bind函數使用 getClosure 包裝而來:

function($container, $parameters = []){
    method = make/build;
    return $container->$method($concrete, $parameters);
}

這種狀況,concrete(this) 將會繼續遞歸調用 make 或者 build。

反射

當 build 的參數是類名而不是閉包的時候,就要利用反射構建類對象,若是構建的類對象不須要依賴任何其餘參數,那麼:

$reflector = new ReflectionClass($concrete);
$constructor = $reflector->getConstructor();
if (is_null($constructor)) {
    return new $concrete;
}

若是須要依賴注入,那麼就要用反射機制來獲取 __construct 函數所須要注入的依賴,若是在make的時候帶入參數值,那麼直接利用傳入的參數值;若是依賴是類對像,那麼遞歸調用 make 函數;若是依賴是變量值,那麼就從上下文中或者參數默認值中去獲取:

...
$dependencies = $constructor->getParameters();
$instances = $this->resolveDependencies($dependencies);
...
     
protected function resolveDependencies(array $dependencies)
{
    $results = [];

    foreach ($dependencies as $dependency) {
      
      if ($this->hasParameterOverride($dependency)) {
          $results[] = $this->getParameterOverride($dependency);
               
          continue;
      }
      
      $results[] = is_null($class = $dependency->getClass())
                            ? $this->resolvePrimitive($dependency)
                            : $this->resolveClass($dependency);
      }

    return $results;
}

解析變量值參數,若是變量值在上下文綁定中設置過,則去取上下文綁定的值,不然經過反射去取參數默認值,若是沒有默認值,那麼就要終止報錯:

protected function resolvePrimitive(ReflectionParameter $parameter)
{
      if (! is_null($concrete = $this->getContextualConcrete('$'.$parameter->name))) {
          return $concrete instanceof Closure ? $concrete($this) : $concrete;
      }

      if ($parameter->isDefaultValueAvailable()) {
          return $parameter->getDefaultValue();
      }

      $this->unresolvablePrimitive($parameter);
}

protected function hasParameterOverride($dependency)
{
    return array_key_exists(
        $dependency->name, $this->getLastParameterOverride()
    );
}

protected function getParameterOverride($dependency)
{
    return $this->getLastParameterOverride()[$dependency->name];
}

protected function getLastParameterOverride()
{
    return count($this->with) ? end($this->with) : [];
}

解析類參數,利用服務容器進行依賴注入:

protected function resolveClass(ReflectionParameter $parameter)
{
      try {
          return $this->make($parameter->getClass()->name);
      }
      catch (BindingResolutionException $e) {
          if ($parameter->isOptional()) {
              return $parameter->getDefaultValue();
          }

        throw $e;
      }
}

buildstack 解析棧

值的注意的是服務容器裏面有個 buildStack,每次利用反射對參數進行依賴注入的時候,都要向這個數組中壓入當前的解析對象,前面說過這部分是爲了上下文綁定而設計的:

...
$this->buildStack[] = $concrete;//壓入數組棧中
...
$instances = $this->resolveDependencies($dependencies);//解析依賴注入的參數
array_pop($this->buildStack);//彈出數組棧
...

解析標籤


使用標籤綁定的類,將會使用 tagged 來解析:

public function tagged($tag)
{
    $results = [];

    if (isset($this->tags[$tag])) {
        foreach ($this->tags[$tag] as $abstract) {
            $results[] = $this->make($abstract);
        }
    }

    return $results;
}

call方法注入


服務容器中,咱們直接使用或者間接的使用 make 來構造服務對象,可是在實際的應用場景中,會有這樣的需求:咱們擁有一個對象或者閉包函數,想要調用它的一個函數,可是它函數裏面卻有其餘類的參數,這個就須要進行 call 方法注入

public function call($callback, array $parameters = [], $defaultMethod = null)
{
    return BoundMethod::call($this, $callback, $parameters, $defaultMethod);
}

上一篇 文章中,咱們說過,call 函數中的 callback 參數有如下幾種形式:

  • 閉包 Closure

  • class@method

  • 類靜態函數,class::method

  • 類靜態函數: [ className/classObj, method ];類非靜態函數: [ classObj, method ]

  • 若 defaultMethod 不爲空,className
    首先,咱們先看看 call 方法注入的流程圖:

Markdown

從流程圖中咱們能夠看出來,雖然調用 call 的形式有 5 種,可是實際最終的形式是三種,第二種和第五種被轉化爲了第四種。
接下來,咱們詳細的解析源碼:

call

先看一下 call 方法的主體:

public static function call($container, $callback, array $parameters = [], $defaultMethod = null)
{
    if (static::isCallableWithAtSign($callback) || $defaultMethod) {
        return static::callClass($container, $callback, $parameters, $defaultMethod);
    }

    return static::callBoundMethod($container, $callback, function () use ($container, $callback, $parameters) {
        return call_user_func_array(
            $callback, static::getMethodDependencies($container, $callback, $parameters)
        );
    });
}

能夠看出來,call 方法注入主要有 4 個大的步驟:

  1. 對於 className@method 和 className-defaultMethod,實例化 className 爲類對象,轉化爲 [ classObj, method ]。

  2. 判斷 [ classObj / classname, method ] 是否存在被綁定的方法,若是有則調用。

  3. 利用服務容器解析依賴的參數。

  4. 調用 call_user_func_array。

實例化類對象

在這裏 className@method 和 className-defaultMethod 兩種狀況被轉化爲 [ classObj, method ], className會被實例化爲類對象,並從新調用 call:

protected static function isCallableWithAtSign($callback)
{
    return is_string($callback) && strpos($callback, '@') !== false;
}
    
protected static function callClass($container, $target, array $parameters = [], $defaultMethod = null)
{
    $segments = explode('@', $target);

    $method = count($segments) == 2
                    ? $segments[1] : $defaultMethod;

    if (is_null($method)) {
        throw new InvalidArgumentException('Method not provided.');
    }

    return static::call(
        $container, [$container->make($segments[0]), $method], $parameters
    );
}

執行綁定方法

針對 [ className/classObj, method ], 調用被綁定的方法:

protected static function callBoundMethod($container, $callback, $default)
{
    if (! is_array($callback)) {
        return value($default);
    }

    $method = static::normalizeMethod($callback);

    if ($container->hasMethodBinding($method)) {
        return $container->callMethodBinding($method, $callback[0]);
    }

    return value($default);
}

protected static function normalizeMethod($callback)
{
    $class = is_string($callback[0]) ? $callback[0] : get_class($callback[0]);

    return "{$class}@{$callback[1]}";
}
    
public function hasMethodBinding($method)
{
    return isset($this->methodBindings[$method]);
}        

public function callMethodBinding($method, $instance)
{
    return call_user_func($this->methodBindings[$method], $instance, $this);
}

那麼這個被綁定的方法 methodBindings 從哪裏來呢,就是 上一篇 文章提的 bindMethod:

public function bindMethod($method, $callback)
{
    $this->methodBindings[$method] = $callback;
}

從上面能夠看出來,methodBindings 中 callback 參數必定是 classname@method 形式的。

實例化依賴

這一步就要經過反射來獲取函數方法須要注入的參數類型,而後利用服務容器對參數類型進行解析構建:

protected static function getMethodDependencies($container, $callback, array $parameters = [])
{
    $dependencies = [];

    foreach (static::getCallReflector($callback)->getParameters() as $parameter) {
        static::addDependencyForCallParameter($container, $parameter, $parameters, $dependencies);
    }

    return array_merge($dependencies, $parameters);
}

getCallReflector 函數利用反射來獲取參數類型,值得注意的是class::method是須要拆分處理的:

protected static function getCallReflector($callback)
{
    if (is_string($callback) && strpos($callback, '::') !== false) {
        $callback = explode('::', $callback);
    }

    return is_array($callback)
                    ? new ReflectionMethod($callback[0], $callback[1])
                    : new ReflectionFunction($callback);
}

利用傳入的參數,利用服務容器構建解析參數類型,或者獲取參數默認值:

protected static function addDependencyForCallParameter($container, $parameter,
                                                            array &$parameters, &$dependencies)
{
    if (array_key_exists($parameter->name, $parameters)) {
        $dependencies[] = $parameters[$parameter->name];

        unset($parameters[$parameter->name]);
    } elseif ($parameter->getClass()) {
        $dependencies[] = $container->make($parameter->getClass()->name);
    } elseif ($parameter->isDefaultValueAvailable()) {
        $dependencies[] = $parameter->getDefaultValue();
    }
}

call_user_func_array

關於這個函數能夠參考 Laravel學習筆記之Callback Type

call_user_func_array(
            $callback, static::getMethodDependencies($container, $callback, $parameters)
        );

Written with StackEdit.

相關文章
相關標籤/搜索