Laravel 源碼分析---Container

Laravel 源碼分析---Container

標簽: laravel 源碼分析


Container 簡介

Container 是 laravel 框架的核心之一殴泰,laravel 框架中類的實例化、存儲和管理都是由 Container 來負責(zé)的忘巧。laravel 里面的 Container 本質(zhì)上是一個 IOC (Inversion of Control/控制反轉(zhuǎn)) 容器,是用來實現(xiàn)依賴注入(DI/Dependency Injection)的啤咽。也有人把這種設(shè)計成為服務(wù)定位模式奥洼。簡單的來說就是在 容器中綁定并保存各個類的抽象以及實例化的方法,在需要這個類的實例時搀擂,通過抽象訪問類的實例化的方法,由容器自動實例化類卷玉,并返回哨颂。用到的技術(shù)主要是 PHP 中的反射類。

下面是兩篇關(guān)于 IOC 容器和服務(wù)定位模式的文檔相种,感興趣可以參考一下:

  1. laravel 學(xué)習(xí)筆記 —— 神奇的服務(wù)容器
  2. PHP 設(shè)計模式系列 —— 服務(wù)定位器模式(Service Locator)

Container 核心變量與設(shè)計

在介紹分析 Container 的源碼之前威恼,我們先介紹一下 Container 類里面幾個主要的變量和設(shè)計,理解這些,對于我們理解 Container 會有比較大的幫助沃测。

  1. $abstract
    Container 容器的主要作用是自動化類的實例化缭黔,所以我們首先要給要實例化的類起一個名字食茎。一般我們會用類的帶有命名空間的類名作為要實例化的類的名字蒂破。但是 laravel 的 Container 提供了更加靈活的描述:$abstract 。$abstract 是要實例化類的抽象别渔,他可以是類的全局名稱附迷,也可以是接口的全局名稱,還可以是你給類起的一個名字哎媚,總之非常靈活喇伯。

  2. $concrete
    描述一個類如何實例化的信息,它可以是一個返回一個類實例的匿名函數(shù)拨与,也可以是一個可實例化類的全局名稱稻据,Container 會利用反射類自動創(chuàng)建其構(gòu)造函數(shù)所需參數(shù),并實例化這個類买喧。

  3. binding
    將一個 $abstract 和 $concrete 映射到一起就構(gòu)成了一個 binding 捻悯。laravel 中 $abstract 到 $concrete 之間的綁定非常靈活,比如可以將一個接口綁定到一個實現(xiàn)了接口的類的實例淤毛,不過一般相互綁定的 $abstract 和 $concrete 也都是抽象和實例描述這樣的關(guān)系今缚。

  4. alias
    Container 允許用戶為 $abstract 起多個別名,甚至允許 a 是 b 別名低淡,b 是 c 的別名這樣的操作姓言。在 laravel 常見的別名有:對于某個擁有父類或者實現(xiàn)某個接口的類的 $abstract , 將其父類和實現(xiàn)的接口都起成其別名。

  5. extender
    Container 支持為一個 binding 添加 extender(擴展器/裝飾器)蔗蹋。extender 本質(zhì)上是一個閉包函數(shù)何荚,其接收一個 $abstract 的實例作為參數(shù),對此實例進行包裝擴展后返回猪杭。 用戶可以在 Container 上注冊 $abstract 的 extender餐塘。這樣在實例化的時候, Container 會使用這些 extender 對 $abstract 的實例進行裝飾擴展胁孙。
    Container 通過 extender 的設(shè)計唠倦,可以實現(xiàn)相當(dāng)豐富的功能,比如實現(xiàn)裝飾器模式涮较,對 $abstract 的實例添加裝飾稠鼻;實現(xiàn)代理模式,為 $abstract 的實例構(gòu)造代理狂票;實現(xiàn)適配器模式候齿,基于 $abstract 的實例構(gòu)造適配器等。

  6. contextual binding
    Container 支持創(chuàng)建基于上下文的 binding。先來解釋一下什么叫做上下文慌盯,因為 Container 在構(gòu)造 $abstract 的實例的時候周霉,是利用 $abstract 對應(yīng)的 $concrete 的反射類,通過解析反射類來構(gòu)造 $concrete 的亚皂,如果 $concrete 的構(gòu)造函數(shù)不存在參數(shù)俱箱,則可以直接構(gòu)造出 $concrete; 如果 $concrete 的構(gòu)造函數(shù)存在參數(shù)灭必,且這些參數(shù)也為某些實例對象的時候狞谱,Container 會遞歸構(gòu)造出這些實例參數(shù),然后構(gòu)造出 $concrete禁漓。在 Container 構(gòu)造 $concrete 構(gòu)造函數(shù)的參數(shù)對象的時候跟衅,就處于 $concrete 的上下文中。Container 通過使用 $buildStack 這個私有屬性記錄當(dāng)前正在構(gòu)造 $concrete 的堆棧來實現(xiàn)這個功能播歼。
    下面我們在解釋什么叫做基于下上文的 binding伶跷。我們先來看一下官方文檔給出的說明:

有時侯我們可能有兩個類使用同一個接口,但我們希望在每個類中注入不同實現(xiàn)秘狞,例如叭莫,兩個控制器依賴 Illuminate\Contracts\Filesystem\Filesystem 契約的不同實現(xiàn)。Laravel 為此定義了簡單谒撼、平滑的接口:

use Illuminate\Support\Facades\Storage;
use App\Http\Controllers\VideoController;
use App\Http\Controllers\PhotoControllers;
use Illuminate\Contracts\Filesystem\Filesystem;

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

$this->app->when(VideoController::class)
    ->needs(Filesystem::class)
    ->give(function () {
        return Storage::disk('s3');
    });

由于控制器類在 Laravel 框架中確實是有 Container 來實例化的食寡,這樣在上面的例子中,在不同控制器的構(gòu)造函數(shù)中聲明同一接口的參數(shù)廓潜,會得到不同的實例對象抵皱。

Container 源碼分析

下面我們開始分析 Container 的源碼。Container 類位于 laravel 框架 Illuminate\Container 命名空間下辩蛋。大家也可以自己打開源碼跟著分析呻畸。

Container 類的接口

下面我們來開始開始看一下 laravel 是如何實現(xiàn) Container 的。首先我們來看一下 Container 類的定義

class Container implements ArrayAccess, ContainerContract
{
}

我們看到 Container 主要實現(xiàn)了兩個接口悼院,一個是 ArrayAccess 接口伤为,這是一個 PHP 的內(nèi)置接口,這個接口的定義如下:

interface ArrayAccess {
    public function offsetExists($offset);
    public function offsetGet($offset);
    public function offsetSet($offset, $value);
    public function offsetUnset($offset);
}

實現(xiàn)這個接口類的對象据途,我們就可以像訪問數(shù)組一樣訪問對象了绞愚,這里我們不在詳述。

Container 類實現(xiàn)的第二個接口是 ContainerContract 颖医,這個接口主要定義了 Container 作為 IOC 所需要實現(xiàn)的方法位衩,下面我們來看一下這個接口的定義。

interface Container
{
    /**
     * Determine if the given abstract type has been bound.
     * 判斷給定 $abstract 是否已經(jīng)被綁定 
     */
    public function bound($abstract);

    /**
     * Alias a type to a different name.
     * 給一下 $abstract 起一個別名
     */
    public function alias($abstract, $alias);

    /**
     * Assign a set of tags to a given binding.
     * 給一組 $abstracts 打上一組tag
     */
    public function tag($abstracts, $tags);

    /**
     * Resolve all of the bindings for a given tag.
     * 創(chuàng)建給定 tag 下所有 $abstract 的實例
     */
    public function tagged($tag);

    /**
     * Register a binding with the container.
     * 注冊一個 $abstract 到 $concrete 的綁定到容器熔萧。
     */
    public function bind($abstract, $concrete = null, $shared = false);

    /**
     * Register a binding if it hasn't already been registered.
     * 如果 $abstract 沒有被注冊的話糖驴,注冊一個 $abstract 到 $concrete 的綁定到容器僚祷。
     */
    public function bindIf($abstract, $concrete = null, $shared = false);

    /**
     * Register a shared binding in the container.
     * 注冊一個可共享的綁定到容器
     */
    public function singleton($abstract, $concrete = null);

    /**
     * "Extend" an abstract type in the container.
     * 使用 $closure 擴展容器中的 $abstract
     */
    public function extend($abstract, Closure $closure);

    /**
     * Register an existing instance as shared in the container.
     * 注冊一個實例到容器中
     */
    public function instance($abstract, $instance);

    /**
     * Define a contextual binding.
     * 定義一個上下文的綁定
     */
    public function when($concrete);

    /**
     * Resolve the given type from the container.
     * 根據(jù)容器中的綁定,給出 $abstract 的實例贮缕。
     */
    public function make($abstract, array $parameters = []);

    /**
     * Call the given Closure / class@method and inject its dependencies.
     * 調(diào)用給定匿名函數(shù)或者 class@method 描述的類的方法辙谜,并且自動注入依賴參數(shù)
     */
    public function call($callback, array $parameters = [], $defaultMethod = null);

    /**
     * Determine if the given abstract type has been resolved.
     * 判斷一個 $abstract 是否實例化過
     */
    public function resolved($abstract);

    /**
     * Register a new resolving callback.
     * 注冊一個 $abstract 實例化的回調(diào)函數(shù)
     */
    public function resolving($abstract, Closure $callback = null);

    /**
     * Register a new after resolving callback.
     * 注冊一個 $abstract 實例化之后的回調(diào)函數(shù)
     */
    public function afterResolving($abstract, Closure $callback = null);
}

以上是 Container 接口中聲明的方法,而我們接下來 Container 源碼的分析也主要針對 Container 接口中的方法感昼。

Container 類的主要屬性和方法

在了解了 Container 的幾個主要變量和概念的設(shè)計后装哆,我們來看一下 Container 的主要屬性和方法。

class Contner implements ArrayAccess, ContainerContract
{
    /**
     * An array of the types that have been resolved.
     * 記錄實例化過的 $abstract 抑诸,key 為 $abstract烂琴,velue 為布爾值
     */
    protected $resolved = [];

    /**
     * The container's bindings.
     * 容器的 bindings (綁定關(guān)系),key 為 $abstract 蜕乡,value 為程序處理過的 $concrete,為一個關(guān)聯(lián)數(shù)組梗夸,模型如下:
     * [
     *  'concrete' => Closure,
     *  'shared' => bool
     * ]
     */
    protected $bindings = [];

    /**
     * The container's shared instances.
     * 可共享的 $abstract 的實例, 鍵為可共享的 $abstract , 值為 $abstract 對應(yīng)的實例
     */
    protected $instances = [];

    /**
     * The registered type aliases.
     * $abstract 的別名, key 是別名, value 是別名對應(yīng)的$abstract
     * @var array
     */
    protected $aliases = [];

    /**
     * The extension closures for services.
     * 擴展的 $abstract 层玲,為一個二維數(shù)組,key 為 $abstract, vaule 為 Closure 組成的數(shù)組 
     */
    protected $extenders = [];

    /**
     * All of the registered tags.
     * 注冊的 $abstract tags
     * @var array
     */
    protected $tags = [];

   /**
     * The stack of concretions currently being built.
     * 當(dāng)前正在創(chuàng)建的 concretions 的堆棧
     * @var array
     */
    protected $buildStack = [];
    
    /**
     * The contextual binding map.
     * 
     * @var array
     */
    public $contextual = [];

    /**
     * All of the resolving callbacks by class type.
     * resolving 回調(diào)函數(shù)
     */
    protected $resolvingCallbacks = [];

    /**
     * All of the after resolving callbacks by class type.
     * Resolving 之后的回調(diào)函數(shù)
     */
    protected $afterResolvingCallbacks = [];
    
    /**
     * Define a contextual binding.
     * 定義一個上下文的綁定
     */
    public function when($concrete);

    /**
     * Determine if the given abstract type has been bound.
     * 判斷 $abstract 是否綁定過
     */
    public function bound($abstract);
    
    /**
     * Determine if the given abstract type has been resolved.
     * $abstract 是否實例化過
     */
    public function resolved($abstract);
    
    /**
     * Register a binding with the container.
     * 在容器上反症,將 $abstract 綁定到 $concrete
     */
    public function bind($abstract, $concrete = null, $shared = false);
    
    /**
     * Register a shared binding in the container.
     * 注冊一個可共享的綁定到容器
     */
    public function singleton($abstract, $concrete = null);
    
    /**
     * "Extend" an abstract type in the container.
     * 在容器上擴展一個 $abstract
     */
    public function extend($abstract, Closure $closure);

    /**
     * Register an existing instance as shared in the container.
     * 注冊一個可共享的實例到容器
     * @return void
     */
    public function instance($abstract, $instance);
    
    /**
     * Assign a set of tags to a given binding.
     * 給一組 $abstract 打上一組 tag
     */
    public function tag($abstracts, $tags);

    /**
     * Resolve all of the bindings for a given tag.
     * 實例化 $tag 下的 $abstract
     */
    public function tagged($tag);
    
    /**
     * Alias a type to a different name.
     * 給 $abstract 起別名 $alias
     */
    public function alias($abstract, $alias);
    
    
    /**
     * Call the given Closure / class@method and inject its dependencies.
     * 調(diào)用給定匿名函數(shù)或者 class@method 描述的類的方法辛块,并且自動注入依賴參數(shù)
     */
    public function call($callback, array $parameters = [], $defaultMethod = null);

    /**
     * Resolve the given type from the container.
     * 實例化 $abstract 綁定的 $concrete
     */
    public function make($abstract, array $parameters = []);
    
    /**
     * Get the alias for an abstract if available.
     * 返回 $abstract 作為別名對應(yīng)的真正的 $abstract。
     */
    public function getAlias($abstract);
}

上面的這些方法是 Container 實現(xiàn)其功能的主要方法铅碍。下面我會按照一定的順序分析這些方法源代碼润绵。

Container 方法之 alias

因為 alias 是 Container 的一個基礎(chǔ)功能,所以我們先來介紹一下 Container alias 的相關(guān)方法

    /**
     * Alias a type to a different name.
     * 給 $abstract 添加別名 $alias
     * @param  string  $abstract 抽象
     * @param  string  $alias 別名
     * @return void
     */
    public function alias($abstract, $alias)
    {
        $this->aliases[$alias] = $this->normalize($abstract);
    }
    
    /**
     * Get the alias for an abstract if available.
     * 如果 $abstract 屬于一個別名胞谈,則返回其此別名對應(yīng)的值 
     * @param  string  $abstract
     * @return string
     *
     * @throws \LogicException
     */
    public function getAlias($abstract)
    {
        // 如果$abstract 不屬于別名尘盼,直接返回
        if (! isset($this->aliases[$abstract])) {
            return $abstract;
        }

        if ($this->aliases[$abstract] === $abstract) {
            throw new LogicException("[{$abstract}] is aliased to itself.");
        }

        // 地柜調(diào)用局待,返回別名 $abstract 最終對應(yīng)的值
        return $this->getAlias($this->aliases[$abstract]);
    }
    
    /**
     * Extract the type and alias from a given definition.
     *
     * @param  array  $definition
     * @return array
     */
    protected function extractAlias(array $definition)
    {
        return [key($definition), current($definition)];
    }
    
    /**
     * Normalize the given class name by removing leading slashes.
     *
     * @param  mixed  $service
     * @return mixed
     */
    protected function normalize($service)
    {
        return is_string($service) ? ltrim($service, '\\') : $service;
    }

Container 方法之 bind

下面埂伦,我們來介紹一下 Container 注冊 binding 的一系列方法

    /**
     * Register a binding with the container.
     * 在 Container 注冊一個 binding
     * @param  string|array  $abstract
     * @param  \Closure|string|null  $concrete
     * @param  bool  $shared
     * @return void
     */
    public function bind($abstract, $concrete = null, $shared = false)
    {
        $abstract = $this->normalize($abstract);

        $concrete = $this->normalize($concrete);

        // If the given types are actually an array, we will assume an alias is being
        // defined and will grab this "real" abstract class name and register this
        // alias with the container so that it can be used as a shortcut for it.
        if (is_array($abstract)) {
            //如果 array 是一個數(shù)組, 按照 key 為 $abstract, value 為 $alias 提取出來并設(shè)置到容器
            list($abstract, $alias) = $this->extractAlias($abstract);

            $this->alias($abstract, $alias);
        }

        // If no concrete type was given, we will simply set the concrete type to the
        // abstract type. After that, the concrete type to be registered as shared
        // without being forced to state their classes in both of the parameters.
        // 刪除 $abstract 對應(yīng)的 的實例名党,并且如果$abstract是一個別名的話债鸡,也刪除這個別名的對應(yīng)關(guān)系
        $this->dropStaleInstances($abstract);

        // 如果 $concrete 為空, 默認為 $abstract
        if (is_null($concrete)) {
            $concrete = $abstract;
        }

        // If the factory is not a Closure, it means it is just a class name which is
        // bound into this container to the abstract type and we will just wrap it
        // up inside its own Closure to give us more convenience when extending.
        // 如果 $concrete 不是閉包的話, 將其封裝成一個閉包
        if (! $concrete instanceof Closure) {
            $concrete = $this->getClosure($abstract, $concrete);
        }

        // 注冊 binding饲漾,函數(shù) compact 以變量名作為數(shù)組的鍵, 值作為數(shù)組的值構(gòu)造數(shù)組
        $this->bindings[$abstract] = compact('concrete', 'shared');

        // If the abstract type was already resolved in this container we'll fire the
        // rebound listener so that any objects which have already gotten resolved
        // can have their copy of the object updated via the listener callbacks.
        // 判斷 $abstract 是否是已經(jīng)已經(jīng)實例化, 如果是重新實例化, 并調(diào)用$abstract rebound 時的回調(diào)函數(shù)
        if ($this->resolved($abstract)) {
            $this->rebound($abstract);
        }
    }
    
    /**
     * Get the Closure to be used when building a type.
     * 創(chuàng)建關(guān)于 $abstract 和 $concrete 的閉包钾恢,此閉包函數(shù)封裝了創(chuàng)建  $concrete 對應(yīng)對象的方法
     * @param  string  $abstract
     * @param  string  $concrete
     * @return \Closure
     */
    protected function getClosure($abstract, $concrete)
    {
        return function ($container, $parameters = []) use ($abstract, $concrete) {
            $method = ($abstract == $concrete) ? 'build' : 'make';
            //Note : 這個函數(shù)調(diào)用的第一個參數(shù)是 $concrete 废岂,而不是 $abstract,需要注意
            return $container->$method($concrete, $parameters);
        };
    }
    
   /**
     * Drop all of the stale instances and aliases.
     * 刪除 $abstract 對應(yīng)的 的實例浑娜,并且如果 $abstract 是一個別名的話享扔,也刪除這個別名的對應(yīng)關(guān)系
     * @param  string  $abstract
     * @return void
     */
    protected function dropStaleInstances($abstract)
    {
        unset($this->instances[$abstract], $this->aliases[$abstract]);
    }

    /**
     * Determine if the given abstract type has been resolved.
     * 判斷抽象是否被實例化(make)過
     * @param  string  $abstract
     * @return bool
     */
    public function resolved($abstract)
    {
        $abstract = $this->normalize($abstract);

        if ($this->isAlias($abstract)) {
            $abstract = $this->getAlias($abstract);
        }

        return isset($this->resolved[$abstract]) || isset($this->instances[$abstract]);
    }
    
    /**
     * Fire the "rebound" callbacks for the given abstract type.
     * make $abstract, 并調(diào)用$abstract rebound 時的回調(diào)函數(shù)
     * @param  string  $abstract
     * @return void
     */
    protected function rebound($abstract)
    {
        $instance = $this->make($abstract);

        foreach ($this->getReboundCallbacks($abstract) as $callback) {
            call_user_func($callback, $this, $instance);
        }
    }
    
    /**
     * Register a shared binding in the container.
     * 在 Container 上注冊一個可共享的 binding
     * @param  string|array  $abstract
     * @param  \Closure|string|null  $concrete
     * @return void
     */
    public function singleton($abstract, $concrete = null)
    {
        $this->bind($abstract, $concrete, true);
    }


    /**
     * Register an existing instance as shared in the container.
     * 注冊一個可共享的實例到 Container
     * @param  string  $abstract
     * @param  mixed   $instance
     * @return void
     */
    public function instance($abstract, $instance)
    {
        $abstract = $this->normalize($abstract);

        // First, we will extract the alias from the abstract if it is an array so we
        // are using the correct name when binding the type. If we get an alias it
        // will be registered with the container so we can resolve it out later.
        if (is_array($abstract)) {
            list($abstract, $alias) = $this->extractAlias($abstract);

            $this->alias($abstract, $alias);
        }

        unset($this->aliases[$abstract]);

        // We'll check to determine if this type has been bound before, and if it has
        // we will fire the rebound callbacks registered with the container and it
        // can be updated with consuming classes that have gotten resolved here.
        $bound = $this->bound($abstract);

        $this->instances[$abstract] = $instance;

        if ($bound) {
            $this->rebound($abstract);
        }
    }
    
    /**
     * Register a binding if it hasn't already been registered.
     *
     * @param  string  $abstract
     * @param  \Closure|string|null  $concrete
     * @param  bool  $shared
     * @return void
     */
    public function bindIf($abstract, $concrete = null, $shared = false)
    {
        if (! $this->bound($abstract)) {
            $this->bind($abstract, $concrete, $shared);
        }
    }
    
    /**
     * "Extend" an abstract type in the container.
     * 在 Container 為 $abstract 注冊 extender
     * @param  string    $abstract
     * @param  \Closure  $closure
     * @return void
     *
     * @throws \InvalidArgumentException
     */
    public function extend($abstract, Closure $closure)
    {
        $abstract = $this->normalize($abstract);

        if (isset($this->instances[$abstract])) {
            $this->instances[$abstract] = $closure($this->instances[$abstract], $this);

            $this->rebound($abstract);
        } else {
            $this->extenders[$abstract][] = $closure;
        }
    }

Container 方法之 contextual binding

Container 支持 contextual binding底桂,我們來看一下這個功能相關(guān)的源代碼。

 /**
     * Define a contextual binding.
     * 定義一個 contextual binding
     * @param  string  $concrete
     * @return \Illuminate\Contracts\Container\ContextualBindingBuilder
     */
    public function when($concrete)
    {
        $concrete = $this->normalize($concrete);

        return new ContextualBindingBuilder($this, $concrete);
    }

    /**
     * Add a contextual binding to the container.
     * 添加一個 contextual binding 到容器
     * @param  string  $concrete
     * @param  string  $abstract
     * @param  \Closure|string  $implementation
     * @return void
     */
    public function addContextualBinding($concrete, $abstract, $implementation)
    {
        $this->contextual[$this->normalize($concrete)][$this->normalize($abstract)] = $this->normalize($implementation);
    }

我們看到惧眠,在 when 方法里面返回了一個 ContextualBindingBuilder 類的實例籽懦,下面我們來看一下 ContextualBindingBuilder 類的源碼

namespace Illuminate\Container;

use Illuminate\Contracts\Container\ContextualBindingBuilder as ContextualBindingBuilderContract;

class ContextualBindingBuilder implements ContextualBindingBuilderContract
{
    /**
     * The underlying container instance.
     *
     * @var \Illuminate\Container\Container
     */
    protected $container;

    /**
     * The concrete instance.
     *
     * @var string
     */
    protected $concrete;

    /**
     * The abstract target.
     *
     * @var string
     */
    protected $needs;

    /**
     * Create a new contextual binding builder.
     *
     * @param  \Illuminate\Container\Container  $container
     * @param  string  $concrete
     * @return void
     */
    public function __construct(Container $container, $concrete)
    {
        $this->concrete = $concrete;
        $this->container = $container;
    }

    /**
     * Define the abstract target that depends on the context.
     *
     * @param  string  $abstract
     * @return $this
     */
    public function needs($abstract)
    {
        $this->needs = $abstract;

        return $this;
    }

    /**
     * Define the implementation for the contextual binding.
     *
     * @param  \Closure|string  $implementation
     * @return void
     */
    public function give($implementation)
    {
        $this->container->addContextualBinding($this->concrete, $this->needs, $implementation);
    }
}

ContextualBindingBuilder 的源碼非常簡單,通過以上分析,我們可以知道 Container 如何添加一個 contextual binding锉试。

Container 方法之 make

我們介紹過了如何注冊一個 binding 到 Container, 現(xiàn)在我們來介紹如何創(chuàng)建一個 $abstract 對應(yīng)的實例猫十。

    /**
     * Resolve the given type from the container.
     * 創(chuàng)建一個 $abstract 對應(yīng)的實例并返回
     * @param  string  $abstract
     * @param  array   $parameters
     * @return mixed
     */
    public function make($abstract, array $parameters = [])
    {
        $abstract = $this->getAlias($this->normalize($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.
        // 如果 Instance 數(shù)組里面存儲有 $abstract 里面對應(yīng)的對象览濒,則直接返回
        if (isset($this->instances[$abstract])) {
            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, $parameters);
        } else {
            $object = $this->make($concrete, $parameters);
        }

        // 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.
        // 如果 $abstract 存在 extender , 則利用這些 extender 對 $abstract 實例進行擴展裝飾
        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.
        // 如果 $abstract 是可共享的,則將其放入 instances 數(shù)組中
        if ($this->isShared($abstract)) {
            $this->instances[$abstract] = $object;
        }
        
        //觸發(fā) resolving 的回調(diào)方法
        $this->fireResolvingCallbacks($abstract, $object);

        //標記 $abstract 實例化過
        $this->resolved[$abstract] = true;

        return $object;
    }

    /** 
     * Get the concrete type for a given abstract.
     * 返回 $abstract 的 $concrete
     * @param  string  $abstract
     * @return mixed   $concrete
     */
    protected function getConcrete($abstract)
    {
        if (! is_null($concrete = $this->getContextualConcrete($abstract))) {
            return $concrete;
        }

        // If we don't have a registered resolver or concrete for the type, we'll just
        // assume each type is a concrete name and will attempt to resolve it as is
        // since the container should be able to resolve concretes automatically.
        // 如果不存在拖云,說明 $abstract 沒有注冊過贷笛,直接返回 $abstract 本身
        if (! isset($this->bindings[$abstract])) {
            return $abstract;
        }

        return $this->bindings[$abstract]['concrete'];
    }

  /**
     * Get the contextual concrete binding for the given abstract.
     *
     * @param  string  $abstract
     * @return string|null
     */
    protected function getContextualConcrete($abstract)
    {
        if (isset($this->contextual[end($this->buildStack)][$abstract])) {
            return $this->contextual[end($this->buildStack)][$abstract];
        }
    }
    
    /**
     * Determine if the given concrete is buildable.
     * 
     * @param  mixed   $concrete
     * @param  string  $abstract
     * @return bool
     */
    protected function isBuildable($concrete, $abstract)
    {
        return $concrete === $abstract || $concrete instanceof Closure;
    }
    
    /**
     * Instantiate a concrete instance of the given type.
     * 構(gòu)建 $concrete 對應(yīng)的對象
     * @param  string  $concrete
     * @param  array   $parameters
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    public function build($concrete, array $parameters = [])
    {
        // 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.
        // 如果 $concrete 是一個閉包,則直接調(diào)用這個閉包方法創(chuàng)建對象并返回
        if ($concrete instanceof Closure) {
            return $concrete($this, $parameters);
        }
        
        //創(chuàng)建 $concrete 的反射類宙项,并解析反射類創(chuàng)建對象
        $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()) {
            if (! empty($this->buildStack)) {
                $previous = implode(', ', $this->buildStack);

                $message = "Target [$concrete] is not instantiable while building [$previous].";
            } else {
                $message = "Target [$concrete] is not instantiable.";
            }

            throw new BindingResolutionException($message);
        }

        $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.
        $parameters = $this->keyParametersByArgument(
            $dependencies, $parameters
        );

        $instances = $this->getDependencies(
            $dependencies, $parameters
        );

        array_pop($this->buildStack);

        return $reflector->newInstanceArgs($instances);
    }

Contaner 方法之 tag

我們知道乏苦,Container 支持對 $abstarct 打上 tag, 并根據(jù) tag 進行實例化,下面我們來看一下部分代碼的實現(xiàn)

 /**
     * Assign a set of tags to a given binding.
     *
     * @param  array|string  $abstracts
     * @param  array|mixed   ...$tags
     * @return void
     */
    public function tag($abstracts, $tags)
    {
        $tags = is_array($tags) ? $tags : array_slice(func_get_args(), 1);

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

            foreach ((array) $abstracts as $abstract) {
                $this->tags[$tag][] = $this->normalize($abstract);
            }
        }
    }
    
    
    /**
     * Resolve all of the bindings for a given tag.
     *
     * @param  string  $tag
     * @return array
     */
    public function tagged($tag)
    {
        $results = [];

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

        return $results;
    }

說明

現(xiàn)在我們看完了 Container 的主要源代碼尤筐,現(xiàn)在我們主要來看一下 bind 和 make 這兩個方法汇荐。

我們知道 bind 方法的 $concrete 參數(shù)可以為空,當(dāng) $concrete 為空時盆繁,框架會將 $concrete 設(shè)置成 $abstruct, 并根據(jù) $abstruct 和 $concrete 的值構(gòu)造閉包作為最終的 $concrete掀淘。

make 方法支持對沒有綁定過的 $abstruct 進行構(gòu)建,在 getConcrete 方法里面油昂,如果 $abstruct 不存在綁定的 $concrete 的話革娄,會直接返回 $abstruct。這樣在 make 函數(shù)里面會調(diào)用 build 方法進行構(gòu)建冕碟。

下面我們來分析一下對于綁定了字符串類型的 $concrete 且 $abstruct != $concrete 時拦惋, make 的執(zhí)行流程:

  1. bind($abstruct, $concrete)
    添加 $abstruct 到 $concrete 的 binding。由于 $concrete 為字符串且 $abstruct != $concrete, 則 Container 構(gòu)造關(guān)于 $abstruct 和 $concrete 的閉包安寺,設(shè)為函數(shù) f1厕妖,并且 $method 為 make,參數(shù)為字符串 $concrete(getClosure 方法的代碼執(zhí)行)挑庶。

  2. make($abstruct)
    創(chuàng)建 $abstruct 對應(yīng)的對象言秸,獲取 $abstruct 對應(yīng)的 $concrete 為閉包函數(shù) f1,則 f1 是可構(gòu)建的(isBuildable 返回true)挠羔,則對 f1 調(diào)用 build 方法井仰。

  3. build($concrete)
    構(gòu)建 $concrete, 由于 $concrete 是閉包函數(shù) f1破加,則執(zhí)行這個閉包函數(shù)俱恶,針對 f1 里面的字符串 $concrete 調(diào)用 make 方法。

  4. make($concrete)
    構(gòu)建關(guān)于字符串 $concrete 的對象范舀, 由于字符串 $concrete 沒有在容器里面綁定過(getConcrete 方法里面 isset($this->bindings[$abstract]) 為 false)合是,getConcrete 函數(shù)返回字符串 $concrete 本身,且 $concrete 是可構(gòu)建的(isBuildable 返回 true)锭环,則對字符串 $concrete 調(diào)用 build 方法聪全。

  5. build($concrete)
    由于 $concrete 是字符串,則針對這個字符串構(gòu)造反射類辅辩,并分析反射類構(gòu)造對象难礼。

總結(jié)

至此娃圆,我們已經(jīng)分析了 Container 的主要功能和源代碼。Container 是 laravel 框架的核心蛾茉,理解這塊的代碼將對你正確靈活使用 laravel 框架具有很大的幫助讼呢。
laravel 其他功能的源碼分析將陸續(xù)奉上,敬請大家期待谦炬。寫文不易悦屏,如果你喜歡我的文章,還請贊賞支持一下键思。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末础爬,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子吼鳞,更是在濱河造成了極大的恐慌看蚜,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赖条,死亡現(xiàn)場離奇詭異失乾,居然都是意外死亡,警方通過查閱死者的電腦和手機纬乍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來裸卫,“玉大人仿贬,你說我怎么就攤上這事∧够撸” “怎么了茧泪?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長聋袋。 經(jīng)常有香客問我队伟,道長,這世上最難降的妖魔是什么幽勒? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任嗜侮,我火速辦了婚禮,結(jié)果婚禮上啥容,老公的妹妹穿的比我還像新娘锈颗。我一直安慰自己,他們只是感情好咪惠,可當(dāng)我...
    茶點故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布击吱。 她就那樣靜靜地躺著,像睡著了一般遥昧。 火紅的嫁衣襯著肌膚如雪覆醇。 梳的紋絲不亂的頭發(fā)上朵纷,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天,我揣著相機與錄音永脓,去河邊找鬼袍辞。 笑死,一個胖子當(dāng)著我的面吹牛憨奸,可吹牛的內(nèi)容都是我干的革屠。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼排宰,長吁一口氣:“原來是場噩夢啊……” “哼似芝!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起板甘,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤党瓮,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后盐类,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體寞奸,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年在跳,在試婚紗的時候發(fā)現(xiàn)自己被綠了枪萄。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡猫妙,死狀恐怖瓷翻,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情割坠,我是刑警寧澤齐帚,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站彼哼,受9級特大地震影響对妄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜敢朱,卻給世界環(huán)境...
    茶點故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一剪菱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蔫饰,春花似錦琅豆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至杖剪,卻和暖如春冻押,著一層夾襖步出監(jiān)牢的瞬間驰贷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工洛巢, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留括袒,地道東北人。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓稿茉,卻偏偏與公主長得像锹锰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子漓库,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,514評論 2 348

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理恃慧,服務(wù)發(fā)現(xiàn),斷路器渺蒿,智...
    卡卡羅2017閱讀 134,633評論 18 139
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法痢士,類相關(guān)的語法,內(nèi)部類的語法茂装,繼承相關(guān)的語法怠蹂,異常的語法,線程的語...
    子非魚_t_閱讀 31,598評論 18 399
  • JAVA面試題 1少态、作用域public,private,protected,以及不寫時的區(qū)別答:區(qū)別如下:作用域 ...
    JA尐白閱讀 1,146評論 1 0
  • 文章作者:Tyan博客:noahsnail.com 3.4 Dependencies A typical ente...
    SnailTyan閱讀 4,133評論 2 7
  • 觀棋一語閱讀 127評論 0 0