laravel 源码分析

Laravel 源码分析---Container

2018-05-07  本文已影响0人  上善若水_f6a4

Laravel 源码分析---Container

标签: laravel 源码分析


Container 简介

Container 是 laravel 框架的核心之一,laravel 框架中类的实例化、存储和管理都是由 Container 来负责的。laravel 里面的 Container 本质上是一个 IOC (Inversion of Control/控制反转) 容器,是用来实现依赖注入(DI/Dependency Injection)的。也有人把这种设计成为服务定位模式。简单的来说就是在 容器中绑定并保存各个类的抽象以及实例化的方法,在需要这个类的实例时,通过抽象访问类的实例化的方法,由容器自动实例化类,并返回。用到的技术主要是 PHP 中的反射类。

下面是两篇关于 IOC 容器和服务定位模式的文档,感兴趣可以参考一下:

  1. laravel 学习笔记 —— 神奇的服务容器
  2. PHP 设计模式系列 —— 服务定位器模式(Service Locator)

Container 核心变量与设计

在介绍分析 Container 的源码之前,我们先介绍一下 Container 类里面几个主要的变量和设计,理解这些,对于我们理解 Container 会有比较大的帮助。

  1. $abstract
    Container 容器的主要作用是自动化类的实例化,所以我们首先要给要实例化的类起一个名字。一般我们会用类的带有命名空间的类名作为要实例化的类的名字。但是 laravel 的 Container 提供了更加灵活的描述:$abstract 。$abstract 是要实例化类的抽象,他可以是类的全局名称,也可以是接口的全局名称,还可以是你给类起的一个名字,总之非常灵活。

  2. $concrete
    描述一个类如何实例化的信息,它可以是一个返回一个类实例的匿名函数,也可以是一个可实例化类的全局名称,Container 会利用反射类自动创建其构造函数所需参数,并实例化这个类。

  3. binding
    将一个 $abstract 和 $concrete 映射到一起就构成了一个 binding 。laravel 中 $abstract 到 $concrete 之间的绑定非常灵活,比如可以将一个接口绑定到一个实现了接口的类的实例,不过一般相互绑定的 $abstract 和 $concrete 也都是抽象和实例描述这样的关系。

  4. alias
    Container 允许用户为 $abstract 起多个别名,甚至允许 a 是 b 别名,b 是 c 的别名这样的操作。在 laravel 常见的别名有:对于某个拥有父类或者实现某个接口的类的 $abstract , 将其父类和实现的接口都起成其别名。

  5. extender
    Container 支持为一个 binding 添加 extender(扩展器/装饰器)。extender 本质上是一个闭包函数,其接收一个 $abstract 的实例作为参数,对此实例进行包装扩展后返回。 用户可以在 Container 上注册 $abstract 的 extender。这样在实例化的时候, Container 会使用这些 extender 对 $abstract 的实例进行装饰扩展。
    Container 通过 extender 的设计,可以实现相当丰富的功能,比如实现装饰器模式,对 $abstract 的实例添加装饰;实现代理模式,为 $abstract 的实例构造代理;实现适配器模式,基于 $abstract 的实例构造适配器等。

  6. contextual binding
    Container 支持创建基于上下文的 binding。先来解释一下什么叫做上下文,因为 Container 在构造 $abstract 的实例的时候,是利用 $abstract 对应的 $concrete 的反射类,通过解析反射类来构造 $concrete 的,如果 $concrete 的构造函数不存在参数,则可以直接构造出 $concrete; 如果 $concrete 的构造函数存在参数,且这些参数也为某些实例对象的时候,Container 会递归构造出这些实例参数,然后构造出 $concrete。在 Container 构造 $concrete 构造函数的参数对象的时候,就处于 $concrete 的上下文中。Container 通过使用 $buildStack 这个私有属性记录当前正在构造 $concrete 的堆栈来实现这个功能。
    下面我们在解释什么叫做基于下上文的 binding。我们先来看一下官方文档给出的说明:

有时侯我们可能有两个类使用同一个接口,但我们希望在每个类中注入不同实现,例如,两个控制器依赖 Illuminate\Contracts\Filesystem\Filesystem 契约的不同实现。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 来实例化的,这样在上面的例子中,在不同控制器的构造函数中声明同一接口的参数,会得到不同的实例对象。

Container 源码分析

下面我们开始分析 Container 的源码。Container 类位于 laravel 框架 Illuminate\Container 命名空间下。大家也可以自己打开源码跟着分析。

Container 类的接口

下面我们来开始开始看一下 laravel 是如何实现 Container 的。首先我们来看一下 Container 类的定义

class Container implements ArrayAccess, ContainerContract
{
}

我们看到 Container 主要实现了两个接口,一个是 ArrayAccess 接口,这是一个 PHP 的内置接口,这个接口的定义如下:

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

实现这个接口类的对象,我们就可以像访问数组一样访问对象了,这里我们不在详述。

Container 类实现的第二个接口是 ContainerContract ,这个接口主要定义了 Container 作为 IOC 所需要实现的方法,下面我们来看一下这个接口的定义。

interface Container
{
    /**
     * Determine if the given abstract type has been bound.
     * 判断给定 $abstract 是否已经被绑定 
     */
    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.
     * 创建给定 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.
     * 根据容器中的绑定,给出 $abstract 的实例。
     */
    public function make($abstract, array $parameters = []);

    /**
     * Call the given Closure / class@method and inject its dependencies.
     * 调用给定匿名函数或者 class@method 描述的类的方法,并且自动注入依赖参数
     */
    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 实例化的回调函数
     */
    public function resolving($abstract, Closure $callback = null);

    /**
     * Register a new after resolving callback.
     * 注册一个 $abstract 实例化之后的回调函数
     */
    public function afterResolving($abstract, Closure $callback = null);
}

以上是 Container 接口中声明的方法,而我们接下来 Container 源码的分析也主要针对 Container 接口中的方法。

Container 类的主要属性和方法

在了解了 Container 的几个主要变量和概念的设计后,我们来看一下 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 (绑定关系),key 为 $abstract ,value 为程序处理过的 $concrete,为一个关联数组,模型如下:
     * [
     *  'concrete' => Closure,
     *  'shared' => bool
     * ]
     */
    protected $bindings = [];

    /**
     * The container's shared instances.
     * 可共享的 $abstract 的实例, 键为可共享的 $abstract , 值为 $abstract 对应的实例
     */
    protected $instances = [];

    /**
     * The registered type aliases.
     * $abstract 的别名, key 是别名, value 是别名对应的$abstract
     * @var array
     */
    protected $aliases = [];

    /**
     * The extension closures for services.
     * 扩展的 $abstract ,为一个二维数组,key 为 $abstract, vaule 为 Closure 组成的数组 
     */
    protected $extenders = [];

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

   /**
     * The stack of concretions currently being built.
     * 当前正在创建的 concretions 的堆栈
     * @var array
     */
    protected $buildStack = [];
    
    /**
     * The contextual binding map.
     * 
     * @var array
     */
    public $contextual = [];

    /**
     * All of the resolving callbacks by class type.
     * resolving 回调函数
     */
    protected $resolvingCallbacks = [];

    /**
     * All of the after resolving callbacks by class type.
     * Resolving 之后的回调函数
     */
    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.
     * 调用给定匿名函数或者 class@method 描述的类的方法,并且自动注入依赖参数
     */
    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 作为别名对应的真正的 $abstract。
     */
    public function getAlias($abstract);
}

上面的这些方法是 Container 实现其功能的主要方法。下面我会按照一定的顺序分析这些方法源代码。

Container 方法之 alias

因为 alias 是 Container 的一个基础功能,所以我们先来介绍一下 Container alias 的相关方法

    /**
     * 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 属于一个别名,则返回其此别名对应的值 
     * @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.");
        }

        // 地柜调用,返回别名 $abstract 最终对应的值
        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 是一个数组, 按照 key 为 $abstract, value 为 $alias 提取出来并设置到容器
            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 对应的 的实例,并且如果$abstract是一个别名的话,也删除这个别名的对应关系
        $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,函数 compact 以变量名作为数组的键, 值作为数组的值构造数组
        $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 是否是已经已经实例化, 如果是重新实例化, 并调用$abstract rebound 时的回调函数
        if ($this->resolved($abstract)) {
            $this->rebound($abstract);
        }
    }
    
    /**
     * Get the Closure to be used when building a type.
     * 创建关于 $abstract 和 $concrete 的闭包,此闭包函数封装了创建  $concrete 对应对象的方法
     * @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 : 这个函数调用的第一个参数是 $concrete ,而不是 $abstract,需要注意
            return $container->$method($concrete, $parameters);
        };
    }
    
   /**
     * Drop all of the stale instances and aliases.
     * 删除 $abstract 对应的 的实例,并且如果 $abstract 是一个别名的话,也删除这个别名的对应关系
     * @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, 并调用$abstract rebound 时的回调函数
     * @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,我们来看一下这个功能相关的源代码。

 /**
     * 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, 现在我们来介绍如何创建一个 $abstract 对应的实例。

    /**
     * Resolve the given type from the container.
     * 创建一个 $abstract 对应的实例并返回
     * @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 数组里面存储有 $abstract 里面对应的对象,则直接返回
        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 数组中
        if ($this->isShared($abstract)) {
            $this->instances[$abstract] = $object;
        }
        
        //触发 resolving 的回调方法
        $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.
     * 构建 $concrete 对应的对象
     * @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 是一个闭包,则直接调用这个闭包方法创建对象并返回
        if ($concrete instanceof Closure) {
            return $concrete($this, $parameters);
        }
        
        //创建 $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()) {
            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, 并根据 tag 进行实例化,下面我们来看一下部分代码的实现

 /**
     * 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;
    }

说明

现在我们看完了 Container 的主要源代码,现在我们主要来看一下 bind 和 make 这两个方法。

我们知道 bind 方法的 $concrete 参数可以为空,当 $concrete 为空时,框架会将 $concrete 设置成 $abstruct, 并根据 $abstruct 和 $concrete 的值构造闭包作为最终的 $concrete。

make 方法支持对没有绑定过的 $abstruct 进行构建,在 getConcrete 方法里面,如果 $abstruct 不存在绑定的 $concrete 的话,会直接返回 $abstruct。这样在 make 函数里面会调用 build 方法进行构建。

下面我们来分析一下对于绑定了字符串类型的 $concrete 且 $abstruct != $concrete 时, make 的执行流程:

  1. bind($abstruct, $concrete)
    添加 $abstruct 到 $concrete 的 binding。由于 $concrete 为字符串且 $abstruct != $concrete, 则 Container 构造关于 $abstruct 和 $concrete 的闭包,设为函数 f1,并且 $method 为 make,参数为字符串 $concrete(getClosure 方法的代码执行)。

  2. make($abstruct)
    创建 $abstruct 对应的对象,获取 $abstruct 对应的 $concrete 为闭包函数 f1,则 f1 是可构建的(isBuildable 返回true),则对 f1 调用 build 方法。

  3. build($concrete)
    构建 $concrete, 由于 $concrete 是闭包函数 f1,则执行这个闭包函数,针对 f1 里面的字符串 $concrete 调用 make 方法。

  4. make($concrete)
    构建关于字符串 $concrete 的对象, 由于字符串 $concrete 没有在容器里面绑定过(getConcrete 方法里面 isset($this->bindings[$abstract]) 为 false),getConcrete 函数返回字符串 $concrete 本身,且 $concrete 是可构建的(isBuildable 返回 true),则对字符串 $concrete 调用 build 方法。

  5. build($concrete)
    由于 $concrete 是字符串,则针对这个字符串构造反射类,并分析反射类构造对象。

总结

至此,我们已经分析了 Container 的主要功能和源代码。Container 是 laravel 框架的核心,理解这块的代码将对你正确灵活使用 laravel 框架具有很大的帮助。
laravel 其他功能的源码分析将陆续奉上,敬请大家期待。写文不易,如果你喜欢我的文章,还请赞赏支持一下。

上一篇下一篇

猜你喜欢

热点阅读