laravel源碼閱讀(綁定與解析服務)

1. 環境配置

  1. laravel5.1.46的源碼
  2. PHPstorm開發工具
  3. 服務器集成環境(mamp、wamp)安裝xdebug擴展

2. 關鍵點

  1. 使用PHPstorm+xdebug進行源碼閱讀時需點擊(F7按鈕)進入到下一個執行代碼的方法內;
  2. laravel框架中綁定服務是將抽象的服務提供者和一個回調函數綁定在服務容器的bingings屬性中;
  3. laravel框架解析服務是將bindings屬性中綁定的服務提供者的具體服務解析出來並實例化。

下面我將一一解析上面的關鍵點。php

3. 綁定服務

1. 綁定服務laravel

laravel框架中的綁定服務是使用bind方法,將一個抽象類(接口)和一個具體類綁定綁定在服務容器內,並記錄在服務容器的bindings屬性中,代碼以下:bootstrap

文件Illuminate\Container\Container.php
public function bind($abstract, $concrete = null, $shared = false)
{
    // 判斷抽象類是不是數組
    if (is_array($abstract)) {
        list($abstract, $alias) = $this->extractAlias($abstract);
        $this->alias($abstract, $alias);
    }
    // 刪除舊的實例和別名
    $this->dropStaleInstances($abstract);
    // 判斷具體類是否爲空
    if (is_null($concrete)) {
        $concrete = $abstract;
    }
    // 判斷具體類是不是回調函數的實例
    if (! $concrete instanceof Closure) {
        $concrete = $this->getClosure($abstract, $concrete);
    }
    // 將抽象類和具體類綁定到服務容器中
    $this->bindings[$abstract] = compact('concrete', 'shared');
    // 判斷抽象類是否已被解析
    if ($this->resolved($abstract)) {
        $this->rebound($abstract);
    }
}
複製代碼

固然我只是寫出了這個方法的執行代碼,因爲篇幅有限,並未將此方法中調用的方法的代碼列出來,請讀者自行查看源碼。數組

從上面的代碼中能夠看出綁定服務就是將一個抽象類和一個具體類記錄在服務容器中的bindings屬性中,其中抽象類做爲鍵名,鍵值爲一個數組,數組中的鍵值分別爲concrete和shared,鍵值爲concrete和shared,固然這樣說是很籠統的,不如直接展現那麼明顯,下面展現代碼執行時用xdebug查看bindings屬性的值,如圖:bash

綁定服務時記錄的bindings屬性的數據格式

2. 解析服務服務器

laravel框架解析服務使用的是:make方法,經過傳入一個抽象類(接口),獲取具體的實例,在解析時經過shared的屬性判斷是否將得到的實例共享到服務容器的單例屬性instances數組中,並已經解析的抽象類記錄在resolved屬性數組中,先看具體實現的代碼:app

文件Illuminate\Container\Container.php
public function make($abstract, $parameters = [])
{
    // 獲取別名
    $abstract = $this->getAlias($abstract);
    // 檢測抽象類的實例是否在單例數組中
    if (isset($this->instances[$abstract])) {
        return $this->instances[$abstract];
    }
    // 根據抽象類名獲取綁定的具體類
    $concrete = $this->getConcrete($abstract);
    // 實例化具體類
    if ($this->isBuildable($concrete, $abstract)) {
        $object = $this->build($concrete, $parameters);
    } else {
        $object = $this->make($concrete, $parameters);
    }
    // 執行抽象類的擴展
    foreach ($this->getExtenders($abstract) as $extender) {
        $object = $extender($object, $this);
    }
    // 判斷抽象類是否可共享
    if ($this->isShared($abstract)) {
        $this->instances[$abstract] = $object;
    }
    // 解析抽象類的全部回調
    $this->fireResolvingCallbacks($abstract, $object);
    // 將解析的抽象類記錄在已解析數組中
    $this->resolved[$abstract] = true;
    // 返回實例對象
    return $object;
}
複製代碼

理論須要通過實踐驗證,下面我將一個比較複雜的服務綁定過程和解析過程,(Illuminate\Contracts\Http\Kernel和App\Http\Kernel的綁定與解析)框架

3. 具體事例ide

仍是按照上面的代碼執行順序,先看綁定服務部分,接着再說解析服務。函數

3.1)綁定服務

首先是在app.php文件中綁定服務的,代碼以下:

文件bootstrap\app.php
$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);
# 這時有人就會說,這兒沒有綁定動做,接着往下看

文件Illuminate\Container\Container.php
public function singleton($abstract, $concrete = null)
{
    $this->bind($abstract, $concrete, true);
}

# bind方法就是上面代碼中綁定服務的方法
複製代碼

從上面的代碼能夠看出,使用Singleton綁定服務時,在bindings數組中的Illuminate\Contracts\Http\Kernel的shared值是true,也就是說最後會存入到共享單例數組中的。

下面說說解析服務,這部分比較複雜

3.2)解析服務

文件public\index.php
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

文件Illuminate\Foundation\Application.php
public function make($abstract, array $parameters = [])
{
    // 獲取別名
    $abstract = $this->getAlias($abstract);
    // 判斷是否在延遲加載的服務提供者數組中
    if (isset($this->deferredServices[$abstract])) {
        $this->loadDeferredProvider($abstract);
    }
    // 調用父類的make方法
    return parent::make($abstract, $parameters);
}
複製代碼

解析服務是經過make方法解析的,下面看看具體的操做,首先,make中傳入的是一個接口,運行到Application文件中的make方法獲取這個接口的別名,判斷是否在延遲加載的服務提供者數組中,最後調用了父類的make方法,也就是Container文件中的make方法;

父類的make方法中也是先獲取別名,判斷是否已經實例化過,獲取與Illuminate\Contracts\Http\Kernel綁定的具體類,前面已經知道,與它綁定的是App\Http\Kernel這個類,下面就是判斷是否可實例化,可實例化時調用build方法,build方法的代碼以下:

文件Illuminate\Container\Container.php
public function build($concrete, array $parameters = [])
{
    // 判斷具體類是不是回調函數的實例
    if ($concrete instanceof Closure) {
        return $concrete($this, $parameters);
    }
    // 實例化反射類
    $reflector = new ReflectionClass($concrete);
    // 判斷具體類是否可實例化
    if (! $reflector->isInstantiable()) {
        $message = "Target [$concrete] is not instantiable.";
        throw new BindingResolutionContractException($message);
    }
    // 將具體類記錄在實例化棧中
    $this->buildStack[] = $concrete;
    // 經過反射類獲取具體類的構造函數
    $constructor = $reflector->getConstructor();
    // 判斷構造函數是否爲空
    if (is_null($constructor)) {
        array_pop($this->buildStack);
        return new $concrete;
    }
    // 獲取構造函數的參數
    $dependencies = $constructor->getParameters();
     // 經過參數名從新寫入參數
    $parameters = $this->keyParametersByArgument(
        $dependencies, $parameters
    );
    // 獲取依賴的參數
    $instances = $this->getDependencies(
        $dependencies, $parameters
    );
    // 將具體類推出棧
    array_pop($this->buildStack);
    // 經過反射類的參數實例化具體類
    return $reflector->newInstanceArgs($instances);
}
複製代碼

如今咱們知道須要調用build方法,傳入的是回調函數,也就是App\Http\Kernel類封裝的一個回調函數,在執行上面build方法的代碼時,會執行concrete(this, $parameters);,而這個是在綁定具體類時得到的回調函數,代碼以下:

文件Illuminate\Container\Container.php
protected function getClosure($abstract, $concrete)
{
    return function ($c, $parameters = []) use ($abstract, $concrete) {
        $method = ($abstract == $concrete) ? 'build' : 'make';

        return $c->$method($concrete, $parameters);
    };
}
複製代碼

根據以前的代碼知道$abstract的值是:Illuminate\Contracts\Http\Kernel,而$concrete的值是:App\Http\Kernel,因此調用make方法,而如今調用make方法時傳入的是App\Http\Kernel,接着運行Illuminate\Foundation\Application.php文件中的make方法,獲取App\Http\Kernel的別名,此類沒有別名,調用父類的make方法,判斷是否在單例數組中,接着是獲取與此類綁定的具體類,沒有就返回本身自己,下面就是調用build方法實例化此類,判斷此類不是回調函數的實例,實例化反射類,並傳入此類類名,判斷此類是否可實例化,得到構造函數,並獲取構造函數的參數,app和router,根據反射類的參數解決全部的依賴(獲取依賴類的實例),代碼以下:

文件Illuminate\Container\Container.php
public function getDependencies(array $parameters, array $primitives = [])
{
    $dependencies = [];

    foreach ($parameters as $parameter) {
        $dependency = $parameter->getClass();
  
        if (array_key_exists($parameter->name, $primitives)) {
            $dependencies[] = $primitives[$parameter->name];
        } elseif (is_null($dependency)) {
            $dependencies[] = $this->resolveNonClass($parameter);
        } else {
            $dependencies[] = $this->resolveClass($parameter);
        }
    }

    return (array) $dependencies;
}

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

        throw $e;
    }
}
複製代碼

上面的代碼是爲了解決一個類的依賴問題,而App\Http\Kernel的依賴是Illuminate\Contracts\Foundation\Application和Illuminate\Routing\Router,因此經過resolveClass方法傳入$app來獲取Illuminate\Contracts\Foundation\Application的實例,傳入$router獲取Illuminate\Routing\Router的實例。

這裏的實例化也是經過的是make方法進行實例化,$app->getClass()->name以後得到的值是:Illuminate\Contracts\Foundation\Application,而這個接口是有別名的,別名是app,而app以前已經實例化了,存儲在instances數組中,獲取了app的實例存入到依賴數組中,同理得到router的實例也存入到依賴數組中,接着返回到Container中make方法裏的instances變量中,在build方法最後一行代碼中實例化了App\Http\Kernel類,那就返回到Container的make方法,並將App\Http\Kernel記錄在已解析的數組中,返回App\Http\Kernel的實例對象,仍是返回到make方法(此處是執行解析Illuminate\Contracts\Http\Kernel的make方法中),並將Illuminate\Contracts\Http\Kernel也記錄在已解析的數組中。

##4. 總結

上面描述了服務的綁定與解析的核心過程,在綁定和解析的過程當中牽扯到了下面幾個方法:

1). 綁定服務的方法bind,還有Singleton方法,使用Singleton綁定的服務最後解析以後是共享在單例數組中的; 2). 解析服務的方法make,固然主要的解析過程是在父類(Container類)中make方法,具體實例化是在build方法,是經過反射類進行實例化的,這也就是傳說中的控制反轉的具體事例

相關文章
相關標籤/搜索