laravel源码探析(二):Container类

8 篇文章 1 订阅

laravel框架在入口index.php文件中通过require实例化Illuminate\Foundation\Application类,用作连接框架各种服务、组件的“胶水”和实现 IoC控制反转的容器。

<?php

/*
|--------------------------------------------------------------------------
| Create The Application
|--------------------------------------------------------------------------
|
| The first thing we will do is create a new Laravel application instance
| which serves as the "glue" for all the components of Laravel, and is
| the IoC container for the system binding all of the various parts.
|
*/

$app = new Illuminate\Foundation\Application(
    $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);

在vendor目录下找到laravel包,laravel核心代码也是一个composer包。找到Illuminate\Foundation\Application类的定义文件.

Application类继承了服务容器Container类,和接口ApplicationContract、HttpKernelInterface。现在假设有三个类,A,B,C。B的实现依赖于A,C的实现又依赖于B,用传统的设计模式,在每次实例化C的时候,都要先把A、B类的定义文件require进来,再实例化A和B,再把这两个对象注入到的C的实例中。这显然是极为繁琐的一件事,假如各个类之间的依赖关系再复杂点,那么就要耗费大量的精力去处理各种依赖。控制反转(Inversion of Control,英文缩写为IoC)是一种新的设计模式,实现IoC后,每次实例化类C的时候,自动注入所依赖的类A和类B。这里的Container类就是做的这样一件事情,从容器中获取C类的对象,容器依次实例化依赖A,B,并注入C中,最后把已经注入依赖实例化后的C类对象返回给我们。在目录\vendor\laravel\framework\src\Illuminate\Container下找到Container容器类的定义文件:

Container容器类,实现了两个接口,ArrayAccess接口和ContainerContract接口,ArrayAccess接口是php的预定义接口,实现这个接口可以像访问数组一样访问对象。实现这个接口需要实现四个方法:http://php.net/manual/zh/class.arrayaccess.php

 

接下来重点看下ContainerContract接口的实现。Container类定义了很多protected属性:

protected static $instance; 当前存在的容器实例。

protected $resolved = [];保存已经解析的类

protected $bindings = [];绑定到容器的闭包、对象

protected $methodBindings = [];绑定到容器的方法

protected $instances = [];容器中的共享实例

protected $aliases = [];保存类的别名

protected $abstractAliases = [];类的别名到类名的映射

protected $extenders = []; service服务类的扩展

protected $tags = [];标签

protected $buildStack = [];当前实例化过程中保存对象的栈

protected $with = [];实例化时用到的参数

public $contextual = [];上下文环境

 protected $reboundCallbacks = [];重新绑定时需要调用的回掉函数

protected $globalResolvingCallbacks = [];所有类解析时要调用的回掉函数

protected $globalAfterResolvingCallbacks = [];所有类解析完成后要调用的回掉函数

protected $resolvingCallbacks = [];某一个类解析时要调用的回掉函数

protected $afterResolvingCallbacks = [];某一个类解析完成后要调用的回掉函数

 

Container类中的方法:

public function when($concrete) 定义一个上下文绑定。

public function bound($abstract)判断当前类名或别名是否已经绑定

public function resolved($abstract)判断当前类名或别名是否已经解析

public function isShared($abstract)判断是否共享实例

public function isAlias($name)判断是否是别名

public function bind($abstract, $concrete = null, $shared = false)注册绑定

protected function getClosure($abstract, $concrete)获取一个闭包,在解析的时候调用

.......

从容器获取一个实例化一个类,只需要调用make方法:

/**
     * Resolve the given type from the container.
     *
     * @param  string  $abstract
     * @param  array  $parameters
     * @return mixed
     */
    public function make($abstract, array $parameters = [])
    {
        return $this->resolve($abstract, $parameters);
    }

make方法有两个参数,字符串参数$abstract和数组参数$parameters,第一个参数是类名或类的别名,第二个参数是实例化类时需要传入的参数。可以看到make方法实际上是对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 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];
        }

        $this->with[] = $parameters;

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

        // Before returning, we will also set the resolved flag to "true" and pop off
        // the parameter overrides for this build. After those two things are done
        // we will be ready to return back the fully constructed class instance.
        $this->resolved[$abstract] = true;

        array_pop($this->with);

        return $object;
    }

首先获取类别名,再判断是否需要根据上下文环境构建对象,如果是共享实例且不需要根据上下文环境创建,则直接从共享实例数组$this->instances返回对象实例。将$parameters保存到$this->with栈中,调用getConcrete方法获取类的实现$concrete,判断$concrete是否能构建对象。若能则调用build方法,在build的过程中会把类需要的依赖全部解析并注入到最终需要的实例中。

 

/**
     * Instantiate a concrete instance of the given type.
     *
     * @param  string  $concrete
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    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 or 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);
    }

若$concrete是一个闭包,则直接调用闭包,若不是闭包则需要借助php的反射类ReflectionClass来解析依赖,通过对__construct构造方法的参数解析,来注入对象需要的依赖。有关反射类的详细说明,见php官方文档:http://php.net/manual/zh/book.reflection.php。解析完依赖后,调用反射器的newInstanceArgs方法,并传入解析好的依赖,实例化需要的类,这样整个IoC就完成了。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值