1. 程式人生 > >Lumen開發:lumen原始碼解讀之初始化(2)——門面(Facades)與資料庫(db)

Lumen開發:lumen原始碼解讀之初始化(2)——門面(Facades)與資料庫(db)

緊接上一篇

$app->withFacades();//為應用程式註冊門面。
  
$app->withEloquent();//為應用程式載入功能強大的庫。

先來看看withFacades()

   /**
     * Register the facades for the application.(為應用程式註冊門面。)
     *
     * @param  bool  $aliases
     * @param  array $userAliases
     * @return void
     */
    public function withFacades($aliases = true, $userAliases = [])
    {
        Facade::setFacadeApplication($this);
 
        if ($aliases) {
            $this->withAliases($userAliases);
        }
    }
setFacadeApplication()
/**
 * Set the application instance.(設定應用程式例項。)
 *
 * @param  \Illuminate\Contracts\Foundation\Application  $app
 * @return void
 */
public static function setFacadeApplication($app)
{
    static::$app = $app;
}

將當前例項傳給門面類(Facade)

$this->withAliases($userAliases)

/**
 * Register the aliases for the application.(註冊應用程式的別名。)
 *
 * @param  array  $userAliases
 * @return void
 */
public function withAliases($userAliases = [])
{
    $defaults = [
        'Illuminate\Support\Facades\Auth' => 'Auth',
        'Illuminate\Support\Facades\Cache' => 'Cache',
        'Illuminate\Support\Facades\DB' => 'DB',
        'Illuminate\Support\Facades\Event' => 'Event',
        'Illuminate\Support\Facades\Gate' => 'Gate',
        'Illuminate\Support\Facades\Log' => 'Log',
        'Illuminate\Support\Facades\Queue' => 'Queue',
        'Illuminate\Support\Facades\Schema' => 'Schema',
        'Illuminate\Support\Facades\URL' => 'URL',
        'Illuminate\Support\Facades\Validator' => 'Validator',
    ];
 
    if (! static::$aliasesRegistered) {//判斷是否已註冊類別名。
        static::$aliasesRegistered = true;
 
        $merged = array_merge($defaults, $userAliases);
 
        foreach ($merged as $original => $alias) {
            class_alias($original, $alias);//設定別名
        }
    }
}

然後就是withEloquent()函式

/**
 * Load the Eloquent library for the application.(為應用程式載入功能強大的庫。)
 *
 * @return void
 */
public function withEloquent()
{
    $this->make('db');
}
/**
 * Resolve the given type from the container.(從容器中解析給定型別。)
 *
 * @param  string  $abstract
 * @return mixed
 */
public function make($abstract)
{
    $abstract = $this->getAlias($abstract);
 
    if (array_key_exists($abstract, $this->availableBindings) &&
        ! array_key_exists($this->availableBindings[$abstract], $this->ranServiceBinders)) {
        $this->{$method = $this->availableBindings[$abstract]}(); // 服務繫結方法執行
 
        $this->ranServiceBinders[$method] = true;  // 變數是記錄已執行的服務繫結方法。
    }
 
    return parent::make($abstract);
}

一步一步來,make()函式以後經常用得上。

/**
 * Get the alias for an abstract if available.(如果可用的話,獲取抽象的別名。)
 *
 * @param  string  $abstract
 * @return string
 *
 * @throws \LogicException
 */
public function getAlias($abstract)
{
    if (! isset($this->aliases[$abstract])) {  //$this->aliases是註冊類別名,如果沒有別名,則直接返回$abstract
        return $abstract;
    }
 
    if ($this->aliases[$abstract] === $abstract) {  //如果$abstract是別名本身,則會丟擲異常
        throw new LogicException("[{$abstract}] is aliased to itself.");
    }
 
    return $this->getAlias($this->aliases[$abstract]);
}

這一個的$this->aliases的值來著上一篇文章registerContainerAliases()函式對它的賦值

接下來是這段

if (array_key_exists($abstract, $this->availableBindings) &&
        ! array_key_exists($this->availableBindings[$abstract], $this->ranServiceBinders)) {
        $this->{$method = $this->availableBindings[$abstract]}();  // 服務繫結方法執行
 
        $this->ranServiceBinders[$method] = true;  // 變數是記錄已執行的服務繫結方法。
    }

$this->availableBindings變數在Applilcation類的定義是是

/**
 * The available container bindings and their respective load methods.(可用的容器繫結及其各自的載入方法。)
 *
 * @var array
 */
public $availableBindings = [
    'auth' => 'registerAuthBindings',
    'auth.driver' => 'registerAuthBindings',
    'Illuminate\Auth\AuthManager' => 'registerAuthBindings',
    'Illuminate\Contracts\Auth\Guard' => 'registerAuthBindings',
    'Illuminate\Contracts\Auth\Access\Gate' => 'registerAuthBindings',
    'Illuminate\Contracts\Broadcasting\Broadcaster' => 'registerBroadcastingBindings',
    'Illuminate\Contracts\Broadcasting\Factory' => 'registerBroadcastingBindings',
    'Illuminate\Contracts\Bus\Dispatcher' => 'registerBusBindings',
    'cache' => 'registerCacheBindings',
    'cache.store' => 'registerCacheBindings',
    'Illuminate\Contracts\Cache\Factory' => 'registerCacheBindings',
    'Illuminate\Contracts\Cache\Repository' => 'registerCacheBindings',
    'composer' => 'registerComposerBindings',
    'config' => 'registerConfigBindings',
    'db' => 'registerDatabaseBindings',
    'Illuminate\Database\Eloquent\Factory' => 'registerDatabaseBindings',
    'encrypter' => 'registerEncrypterBindings',
    'Illuminate\Contracts\Encryption\Encrypter' => 'registerEncrypterBindings',
    'events' => 'registerEventBindings',
    'Illuminate\Contracts\Events\Dispatcher' => 'registerEventBindings',
    'files' => 'registerFilesBindings',
    'hash' => 'registerHashBindings',
    'Illuminate\Contracts\Hashing\Hasher' => 'registerHashBindings',
    'log' => 'registerLogBindings',
    'Psr\Log\LoggerInterface' => 'registerLogBindings',
    'queue' => 'registerQueueBindings',
    'queue.connection' => 'registerQueueBindings',
    'Illuminate\Contracts\Queue\Factory' => 'registerQueueBindings',
    'Illuminate\Contracts\Queue\Queue' => 'registerQueueBindings',
    'Psr\Http\Message\ServerRequestInterface' => 'registerPsrRequestBindings',
    'Psr\Http\Message\ResponseInterface' => 'registerPsrResponseBindings',
    'translator' => 'registerTranslationBindings',
    'url' => 'registerUrlGeneratorBindings',
    'validator' => 'registerValidatorBindings',
    'Illuminate\Contracts\Validation\Factory' => 'registerValidatorBindings',
    'view' => 'registerViewBindings',
    'Illuminate\Contracts\View\Factory' => 'registerViewBindings',
];
$this->ranServiceBinders 變數是記錄已執行的服務繫結方法。
當前執行的是make('db'),所以$abstract=‘db’;
$this->availableBindings['db'] = 'registerDatabaseBindings';
$this->{$method = $this->availableBindings[$abstract]}();

會執行到Application的registerDatabaseBindings方法

/**
 * Register container bindings for the application.(為應用程式註冊容器繫結。)
 *
 * @return void
 */
protected function registerDatabaseBindings()
{
    $this->singleton('db', function () {  //這裡是用閉包函式註冊一個db的單例
        return $this->loadComponent(
            'database', [
                'Illuminate\Database\DatabaseServiceProvider',
                'Illuminate\Pagination\PaginationServiceProvider',
            ], 'db'
        );
    });
}


    singleton()函式
    public function singleton($abstract, $concrete = null)
    {   
        $this->bind($abstract, $concrete, true);
    }

這裡是用閉包函式註冊一個db的單例,接著看閉包內執行了什麼

/**
 * Configure and load the given component and provider.(配置並載入給定的元件和提供程式。)
 *
 * @param  string  $config
 * @param  array|string  $providers
 * @param  string|null  $return
 * @return mixed
 */
public function loadComponent($config, $providers, $return = null)
{
    $this->configure($config);//將配置檔案載入到應用程式中。
 
    foreach ((array) $providers as $provider) {
        $this->register($provider);//註冊傳過來的服務供應類
    }
 
    return $this->make($return ?: $config);//
}

載入配置檔案和註冊服務的內容

    configure() //將配置檔案載入到應用程式中。
    public function configure($name)
    {    
        if (isset($this->loadedConfigurations[$name])) {
            return;
        }

        $this->loadedConfigurations[$name] = true;

        $path = $this->getConfigurationPath($name);
 
        if ($path) {
            $this->make('config')->set($name, require $path);
        }
    }
    // 載入配置檔案路徑,若無檔名,返回配置資料夾路徑
    public function getConfigurationPath($name = null)
    {  
        if (! $name) {
            $appConfigDir = $this->basePath('config').'/';  // 獲取檔案真實路徑

            if (file_exists($appConfigDir)) {
                return $appConfigDir;
            } elseif (file_exists($path = __DIR__.'/../config/')) {
                return $path;
            }
        } else {
            $appConfigPath = $this->basePath('config').'/'.$name.'.php';  // 獲取檔案真實路徑

            if (file_exists($appConfigPath)) {
                return $appConfigPath;
            } elseif (file_exists($path = __DIR__.'/../config/'.$name.'.php')) {
                return $path;
            }
        }
    }
    // 獲取檔案真實路徑
    public function basePath($path = null)
    {
        if (isset($this->basePath)) {
            return $this->basePath.($path ? '/'.$path : $path);
        }

        if ($this->runningInConsole()) {
            $this->basePath = getcwd();
        } else {
            $this->basePath = realpath(getcwd().'/../');
        }

        return $this->basePath($path);
    }
    // PHP執行環境檢測
    public function runningInConsole()
    {
        return php_sapi_name() == 'cli';
    }



    //註冊傳過來的服務供應類
    public function register($provider)
    {    
        if (! $provider instanceof ServiceProvider) {
            $provider = new $provider($this);
        }

        if (array_key_exists($providerName = get_class($provider), $this->loadedProviders)) {
            return;
        }

        $this->loadedProviders[$providerName] = true;

        if (method_exists($provider, 'register')) {
            $provider->register();
        }

        if (method_exists($provider, 'boot')) {
            return $this->call([$provider, 'boot']);
        }
    }

因為這裡第一次初始化後,$this->ranServiceBinders[$method]=true,
所以以後呼叫db時,都會直接呼叫父類(Container)的make()函式。
講遠了的感覺,不過剛好可以一起講一下最後一步return parent::make($abstract);
/**
 * Resolve the given type from the container.
 *
 * @param  string  $abstract
 * @return mixed
 */
public function make($abstract)
{
    return $this->resolve($abstract);
}

Container類的make()只調用了$this->resolve()函式,馬不停蹄,我們來看看這個函式

/**
     * Resolve the given type from the container.(從容器中解析給定型別。)
     *
     * @param  string  $abstract
     * @param  array  $parameters
     * @return mixed
     */
    protected function resolve($abstract, $parameters = [])
    {
        $abstract = $this->getAlias($abstract);//取實際類名
 
        $needsContextualBuild = ! empty($parameters) || ! is_null(
            $this->getContextualConcrete($abstract)
        );
 
       //如果該型別的例項目前作為單例管理,
       //我們將只返回一個現有的例項而不是例項化新的例項,
       //所以開發人員每次都可以繼續使用同一個物件例項。
        if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {  //單例已存在直接返回
            return $this->instances[$abstract];
        }
 
        $this->with[] = $parameters;
 
        $concrete = $this->getConcrete($abstract);  //返回給定抽象的具體型別(包括一些關於上下文的繫結操作)
 
        //我們已經準備好例項化繫結註冊的具體型別的例項。
        //這將例項化型別,以及遞迴地解析所有的“巢狀”依賴關係,直到所有問題都得到解決為止。
        if ($this->isBuildable($concrete, $abstract)) {  //判斷是否為遞迴
            $object = $this->build($concrete);  //遞迴用build(),用make()會死迴圈
        } else {
            $object = $this->make($concrete);   //非遞迴用make()
        }
 
        //如果我們定義了這種型別的擴充套件程式,
        //我們需要將它們旋轉並將它們應用到正在構建的物件中。
        //這允許擴充套件服務,例如更改配置或裝飾物件。
        foreach ($this->getExtenders($abstract) as $extender) {  //執行擴充套件
            $object = $extender($object, $this);
        }
 
        //如果請求的型別被註冊為單例,我們將快取“記憶體”中的例項,
        //以便稍後返回它,而不必為每一個後續請求建立一個物件的全新例項。
        if ($this->isShared($abstract) && ! $needsContextualBuild) {
            $this->instances[$abstract] = $object;
        }
 
        $this->fireResolvingCallbacks($abstract, $object);  //回撥
 
        //返回之前,我們還將解析的標誌設定為“true”,
        //並彈出此構建的引數重寫。完成這兩件事後,我們將準備返回完全構造的類例項。
        $this->resolved[$abstract] = true;
 
        array_pop($this->with);  //移除最後一個鍵值,也就是$this->with[] = $parameters;

	return $object;
}

上下文具體繫結


    // 獲取給定抽象的上下文具體繫結。
    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])) { // $this->buildStack[] = $concrete;  build();
            return $this->contextual[end($this->buildStack)][$abstract];
        }
    }

    //為給定的抽象獲取具體型別
    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;
    }


    // 確定給定的實現是否可建造
    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 [];
    }

    // 確定給定型別是否被共享
    public function isShared($abstract)
    {  
        return isset($this->instances[$abstract]) ||
              (isset($this->bindings[$abstract]['shared']) &&
               $this->bindings[$abstract]['shared'] === true);
    }

回撥

    //  激發所有解析回撥
    protected function fireResolvingCallbacks($abstract, $object)
    {   // $this->globalResolvingCallbacks[] = $abstract;  所有全域性解析回撥
        $this->fireCallbackArray($object, $this->globalResolvingCallbacks);  

        $this->fireCallbackArray(
            $object, $this->getCallbacksForType($abstract, $object, $this->resolvingCallbacks) 
            // $this->resolvingCallbacks[$abstract][] = $callback;  所有類的解析回撥
        );

        $this->fireAfterResolvingCallbacks($abstract, $object);
    }
    // 啟用後面的所有解析回撥
    protected function fireAfterResolvingCallbacks($abstract, $object)
    {  // $this->globalAfterResolvingCallbacks[] = $abstract;  所有全域性解析後回撥
        $this->fireCallbackArray($object, $this->globalAfterResolvingCallbacks);
        $this->fireCallbackArray(
            $object, $this->getCallbacksForType($abstract, $object, $this->afterResolvingCallbacks)
            // $this->afterResolvingCallbacks[$abstract][] = $callback;  所有類的解析後回撥
        );
    }

    // 啟用物件的回撥陣列
    protected function fireCallbackArray($object, array $callbacks)
    {   
        foreach ($callbacks as $callback) {
            $callback($object, $this);
        }
    }

    // 獲取物件的全部回撥
    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;
    }

  $object = $this->build($concrete);  //遞迴用build(),用make()會死迴圈
    // 例項化給定型別的具體例項
    public function build($concrete)
    {   
        // 如果具體型別實際上是一個閉包,我們將只執行它並返回函式的結果,
        // 這允許函式用作解析器,以便更精細地分析這些物件。
        if ($concrete instanceof Closure) {
            return $concrete($this, end($this->with));
        }

        $reflector = new ReflectionClass($concrete);

        // 如果型別是不可例項化的,那麼開發人員將嘗試解析抽象型別,比如抽象類的介面,並且沒有為抽象註冊的繫結,
        // 因此我們需要退出。
        if (! $reflector->isInstantiable()) {
            return $this->notInstantiable($concrete);  //   丟擲一個異常,即$concrete不是例項化的。
        }

        $this->buildStack[] = $concrete;

        $constructor = $reflector->getConstructor();

        //如果沒有建構函式,這意味著沒有依賴項,那麼我們可以立即解析物件的例項,
        // 而不用從這些容器中解析任何其他型別或依賴項。
        if (is_null($constructor)) {
            array_pop($this->buildStack);

            return new $concrete;
        }

        $dependencies = $constructor->getParameters();

        // 一旦有了建構函式的所有引數,我們就可以建立每個依賴例項,
        // 然後使用反射例項來建立這個類的新例項,將建立的依賴注入其中。
        $instances = $this->resolveDependencies(
            $dependencies
        );

        array_pop($this->buildStack);

        return $reflector->newInstanceArgs($instances);
    }
    // 解析所有來自反射引數的依賴項
    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;
    }

    // 確定給定的依賴項是否具有從$this->with中的引數重寫
    protected function hasParameterOverride($dependency)
    {
        return array_key_exists($dependency->name, end($this->with));
    }

    // 獲取依賴項的引數重寫
    protected function getParameterOverride($dependency)
    {
        return end($this->with)[$dependency->name];
    }

    // 解析非類的基元依賴關係
    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 resolveClass(ReflectionParameter $parameter)
    {
        try {
            return $this->make($parameter->getClass()->name);
        }

        // 如果我們不能解析類例項,我們將檢查該值是否是可選的,
        // 如果是,我們將返回可選的引數值作為依賴項的值,類似於我們如何處理標量。
        catch (BindingResolutionException $e) {
            if ($parameter->isOptional()) {
                return $parameter->getDefaultValue();
            }

            throw $e;
        }
    }

    public function singleton($abstract, $concrete = null)
    { 
        $this->bind($abstract, $concrete, true);
    }

    // 與容器註冊繫結
    public function bind($abstract, $concrete = null, $shared = false)
    {   
        // 如果沒有給出具體型別,我們將簡單地將具體型別設定為抽象型別。
        // 之後,要註冊為共享的具體型別,而不必在兩個引數中宣告它們的類。
        $this->dropStaleInstances($abstract);

        if (is_null($concrete)) {
            $concrete = $abstract;
        }

        // 如果$concrete不是閉包,則意味著它只是一個類名,繫結到這個容器中的抽象型別中,
        // 我們將把它包裝在自己的閉包中,以便擴充套件時更加方便。
        if (! $concrete instanceof Closure) {
            $concrete = $this->getClosure($abstract, $concrete);
        }

        $this->bindings[$abstract] = compact('concrete', 'shared');

        // 如果在此容器中已經解析了抽象型別,我們將觸發回彈偵聽器,
        // 以便已經解析的任何物件都可以通過偵聽器回撥更新它們的物件副本。
        if ($this->resolved($abstract)) {
            $this->rebound($abstract);
        }
    }


    // 丟棄舊的例項和別名
    protected function dropStaleInstances($abstract)
    {   
        unset($this->instances[$abstract], $this->aliases[$abstract]);
    }

    // 在構建型別時獲得閉包
    protected function getClosure($abstract, $concrete)
    {   
        return function ($container, $parameters = []) use ($abstract, $concrete) {
            $method = ($abstract == $concrete) ? 'build' : 'make';
            return $container->$method($concrete, $parameters);
        };
    }

    // 為給定的抽象型別激發“rebound”回撥
    protected function rebound($abstract)
    {  
        $instance = $this->make($abstract);

        foreach ($this->getReboundCallbacks($abstract) as $callback) {
            call_user_func($callback, $this, $instance);
        }
    }

    // 獲取給定型別的"rebound"回撥
    protected function getReboundCallbacks($abstract)
    {   
        if (isset($this->reboundCallbacks[$abstract])) {  // 所有註冊的rebound回撥
            return $this->reboundCallbacks[$abstract];
        }

        return [];
    }

這裡是整篇文比較難的地方,部分註釋是直譯的,講了這麼久,其實也執行了$app->withFacades()$app->withEloquent();

其實後面的一些註冊和繫結與前面也是類似的,希望能幫到大家!