Laravel学习笔记之Container源码解析

   2016-09-18 0
核心提示:说明:本文主要学习Laravel中Container的源码,主要学习Container的绑定和解析过程,和解析过程中的依赖解决。分享自己的研究心得,希望对别人有所帮助。实际上Container的绑定主要有三种方式:bind(),singleton(),instance(),且singleton()只是一种'shared'

说明:本文主要学习Laravel中Container的源码,主要学习Container的绑定和解析过程,和解析过程中的依赖解决。分享自己的研究心得,希望对别人有所帮助。实际上Container的绑定主要有三种方式:bind(),singleton(),instance(),且singleton()只是一种'shared' = true的bind(),这些已经在 Laravel学习笔记之IoC Container实例化源码解析 聊过,其实现方法并不复杂。当Service通过Service Provider绑定到Container中后,当需要该Service时,是需要Container帮助自动解析make()。OK,下面聊聊自动解析过程,研究下Container是如何在自动解析Service时解决该Service的依赖问题的。

开发环境: Laravel5.3 + PHP7 + OS X 10.11

PHPUnit测试下绑定

在聊解析过程前,先测试下\Illuminate\Container\Container中绑定的源码,这里测试下bind(),singleton(),instance()三个绑定方式:

<?php

namespace MyRightCapital\Container\Tests;

use MyRightCapital\Container\Container;

class ContainerBindTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @var Container $container
     */
    protected $container;

    public function setUp()
    {
        $this->container = new Container();
    }

    public function testBindClosure()
    {
        // Arrange
        $expected = 'Laravel is a PHP Framework.';
        $this->container->bind('PHP', function () use ($expected) {
            return $expected;
        });

        // Actual
        $actual = $this->container->make('PHP');

        // Assert
        $this->assertEquals($expected, $actual);
    }

    public function testBindInterfaceToImplement()
    {
        // Arrange
        $this->container->bind(IContainerStub::class, ContainerImplementationStub::class);

        // Actual
        $actual = $this->container->make(IContainerStub::class);

        // Assert
        $this->assertInstanceOf(IContainerStub::class, $actual);
    }

    public function testBindDependencyResolution()
    {
        // Arrange
        $this->container->bind(IContainerStub::class, ContainerImplementationStub::class);

        // Actual
        $actual = $this->container->make(ContainerNestedDependentStub::class);

        // Assert
        $this->assertInstanceOf(ContainerDependentStub::class, $actual->containerDependentStub);
        $this->assertInstanceOf(ContainerImplementationStub::class, $actual->containerDependentStub->containerStub);
    }

    public function testSingleton()
    {
        // Arrange
        $this->container->singleton(ContainerConcreteStub::class);
        $expected = $this->container->make(ContainerConcreteStub::class);

        // Actual
        $actual = $this->container->make(ContainerConcreteStub::class);

        // Assert
        $this->assertSame($expected, $actual);
    }

    public function testInstanceExistingObject()
    {
        // Arrange
        $expected = new ContainerImplementationStub();
        $this->container->instance(IContainerStub::class, $expected);

        // Actual
        $actual = $this->container->make(IContainerStub::class);

        // Assert
        $this->assertSame($expected, $actual);
    }
}

class ContainerConcreteStub
{

}

interface IContainerStub
{

}

class ContainerImplementationStub implements IContainerStub
{

}

class ContainerDependentStub
{
    /**
     * @var \MyRightCapital\Container\Tests\IContainerStub
     */
    public $containerStub;

    public function __construct(IContainerStub $containerStub)
    {
        $this->containerStub = $containerStub;
    }
}

class ContainerNestedDependentStub
{
    /**
     * @var \MyRightCapital\Container\Tests\ContainerDependentStub
     */
    public $containerDependentStub;

    public function __construct(ContainerDependentStub $containerDependentStub)
    {
        $this->containerDependentStub = $containerDependentStub;
    }
}

这里测试了bind()绑定闭包,绑定接口和对应实现,依赖解析这三个feature,singleton()测试了是否为单例绑定一个feature,instance()测试了已存在对象绑定这个feature,测试结果5个tests都通过:

Laravel学习笔记之Container源码解析

关于在PHPStorm中配置PHPUnit可参考这篇: Laravel学习笔记之基于PHPStorm编辑器的Laravel开发

make()源码解析

从以上testcase知道, make() 是负责从Container中解析出service的,而且在 testBindDependencyResolution() 这个test中,还能发现当 ContainerNestedDependentStub::class 有构造依赖时,Container也会自动去解析这个依赖并注入 ContainerNestedDependentStub::class 的构造函数中,这个依赖是 ContainerDependentStub::class ,而这个依赖又有自己的依赖 IContainerStub::class ,从断言语句 $this->assertInstanceOf(ContainerImplementationStub::class, $actual->containerDependentStub->containerStub); 知道,Container又自动解析了这个依赖,所有这一切都不需要我们手动去解析,全都是Container自动化解析的。

这一切Container是怎么做到的?

实际上并不复杂,解决依赖只是用了PHP的Reflector反射机制来实现的。先看下make()源码:

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

        // 如果是instance()绑定的方式,就直接解析返回绑定的service
        if (isset($this->instances[$abstract])) {
            return $this->instances[$abstract];
        }

        // 获取$abstract对应绑定的$concrete
        $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;
    }
    
    protected function getConcrete($abstract)
    {
        if (! is_null($concrete = $this->getContextualConcrete($abstract))) {
            return $concrete;
        }

        // 如果是$this->container->singleton(ContainerConcreteStub::class);这种方式绑定,即$concrete = null
        // 则 $abstract = $concrete,可看以上PHPUnit的testSingleton()这个test
        // 这种方式称为'自动补全'绑定
        if (! isset($this->bindings[$abstract])) {
            return $abstract;
        }

        return $this->bindings[$abstract]['concrete'];
    }
    
    protected function isBuildable($concrete, $abstract)
    {
        return $concrete === $abstract || $concrete instanceof Closure;
    }

从以上源码可知道如果绑定的是闭包或者'自动补全'绑定($concrete = null),则需要build()这个闭包或类名,转换成对应的实例。如果是'接口实现'这种方式绑定,则需要再一次调用make()并经过getConcrete后$abstract = $concrete,然后符合isBuildable()的条件,进入build()函数内。所以以上的PHPUnit的测试用例中不管什么方式的绑定,都要进入build()函数内编译出相应对象实例。当编译出对象后,检查是否是共享的,以及是否要触发回调,以及标记该对象已经被解析。OK,看下build()的源码:

/**
     * Instantiate a concrete instance of the given type.
     *
     * @param  string  $concrete
     * @param  array   $parameters
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    public function build($concrete, array $parameters = [])
    {
        // 如果是闭包直接执行闭包并返回,e.g. PHPUnit的这个test:testBindClosure()
        if ($concrete instanceof Closure) {
            return $concrete($this, $parameters);
        }
        
        // 如这个test:testBindInterfaceToImplement(),这里的$concrete = ContainerImplementationStub::class类名称,
        // 则使用反射ReflectionClass来探测ContainerImplementationStub这个类的构造函数和构造函数的依赖
        $reflector = new ReflectionClass($concrete);

        // 如果ContainerImplementationStub不能实例化,这应该是接口或抽象类,再或者就是ContainerImplementationStub的构造函数是private的
        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();

        // 如果构造函数是空,说明没有任何依赖,直接new返回
        if (is_null($constructor)) {
            array_pop($this->buildStack);

            return new $concrete;
        }
        
        // 获取构造函数的依赖,返回ReflectionParameter[]
        $dependencies = $constructor->getParameters();

        $parameters = $this->keyParametersByArgument(
            $dependencies, $parameters
        );

        // 然后就是获取相关依赖,如testBindDependencyResolution()这个test中,
        // ContainerNestedDependentStub::class是依赖于ContainerDependentStub::class的
        $instances = $this->getDependencies(
            $dependencies, $parameters
        );

        array_pop($this->buildStack);

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

从源码可知道,比较麻烦的是当ContainerNestedDependentStub::class的构造函数有依赖ContainerDependentStub::class时,通过getDependencies()来解决的,看下getDependencies()的源码:

// 这里$parameters = ReflectionParameter[]
    protected 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];
            } 
            // 如果类名为null,说明是基本类型,如'int','string' and so on.
            elseif (is_null($dependency)) {
                $dependencies[] = $this->resolveNonClass($parameter);
            } 
            // 如果是类名,如ContainerDependentStub::class,则resolveClass去解析成对象
            else {
                $dependencies[] = $this->resolveClass($parameter);
            }
        }

        return $dependencies;
    }

通过上面注释,看下resolveClass()的源码:

protected function resolveClass(ReflectionParameter $parameter)
    {
        try {
            // $parameter->getClass()->name返回的是类名,如ContainerNestedDependentStub依赖于$containerDependentStub
            // $containerDependentStub的typehint是ContainerDependentStub,所以类名是'ContainerDependentStub'
            // 然后递归继续make(ContainerDependentStub::class)
            // 又和PHPUnit中这个测试$this->container->make(ContainerNestedDependentStub::class)相类似了
            // ContainerNestedDependentStub又依赖于IContainerStub::class,
            // IContainerStub::class是绑定于ContainerImplementationStub::class
            // 直到ContainerImplementationStub没有依赖或者是构造函数是基本属性,
            // 最后build()结束
            return $this->make($parameter->getClass()->name);
        } catch (BindingResolutionException $e) {
            if ($parameter->isOptional()) {
                return $parameter->getDefaultValue();
            }

            throw $e;
        }
    }

从以上代码注释直到build()是个递归过程,A类依赖于B类,B类依赖于C类和D类,那就从A类开始build,发现依赖于B类,再从Container中解析make()即再build()出B类,发现依赖于C类,再make() and build(),发现B类又同时依赖于D类,再make() and build(),以此类推直到没有依赖或依赖基本属性,解析结束。这样一步步解析完后,发现Container的解析make()并不是很神秘很复杂中的过程。

从以上源码发现PHP的反射Reflector是个很好用的技术,这里给出个test,看下Reflector能干些啥:

<?php

class ConstructorParameter
{

}

class ReflectorTest
{
    private $refletorProperty1;

    protected $refletorProperty2;

    public $refletorProperty3;

    /**
     * @var int
     */
    private $request;

    public function __construct(int $request = 10, string $response, ConstructorParameter $constructorParameter, Closure $closure)
    {

        $this->request = $request;
    }

    private function reflectorMethod1()
    {
    }

    protected function reflectorMethod2()
    {
    }

    public function reflectorMethod3()
    {
    }
}

$reflector_class        = new ReflectionClass(ReflectorTest::class);
$methods                = $reflector_class->getMethods();
$properties             = $reflector_class->getProperties();
$constructor            = $reflector_class->getConstructor();
$constructor_parameters = $constructor->getParameters();

foreach ($constructor_parameters as $constructor_parameter) {
    $dependency = $constructor_parameter->getClass();
    var_dump($dependency);

    if ($constructor_parameter->isDefaultValueAvailable()) {
        var_dump($constructor_parameter->getDefaultValue());
    }
}

var_dump($methods);
var_dump($properties);
var_dump($constructor);
var_dump($constructor_parameters);

打印结果太长了,就不粘贴了。可以看下PHP官方文档: Reflector

总结:本文学习了下Container的核心功能:service resolve的过程,并学习了service的依赖是如何被自动解析的。遇到好的心得再分享,到时见。

 
标签: Laravel
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • nginx 各类网站设置 (laravel , thinkphp , nod
    基础部分设置[root@centos ~]# vim /opt/nginx/conf/nginx.confuser www www;worker_processes auto;pid logs/nginx.pid;worker_rlimit_nofile 100000;events {use epoll;multi_accept on;worker_connections 65535 ;}http {include mime.types;default_type
    02-09
  • PHP trait 特性在 Laravel 中的使用个人心得
    trait 是在PHP5.4中为了方便代码复用的一种实现方式,但目前我在看的的PHP项目中较少看的有程序员去主动使用这个实现方式,在laravel中有很多 trait 的使用,关于trait 在 laravel 的使用请参看 Laravel 在哪些地方用了 trait?我曾在 Laravel 中大型项目面向
    02-09
  • 让我们用 laravel-mix 为 TypeScript 和 Sass
    介绍前端编译TypeScript、Sass、模板引擎等时经常用到Gulp和webpack。这是我个人的印象,但它们似乎都难以管理,因为它们的描述往往复杂而冗长。我不想积极进行,因为我要担心加载器的顺序并且有很多配置选项,我必须花时间去了解它们。我想推荐那里laravel
  • PHP Laravel软删除的实现方法介绍
    用Laravel 自带的 Eloquent ORM 来实现软删除。首先在数据迁移文件中添加删除时间字段./database/migrations/2014_10_12_000000_create_users_table.php?phpuse Illuminate\Database\Migrations\Migration;use Illuminate\Database\Schema\Blueprint;use Illu
  • Laravel中如何使用PHP的装饰器模式 php laravel
    本文小编为大家详细介绍“Laravel中如何使用PHP的装饰器模式”,内容详细,步骤清晰,细节处理妥当,希望这篇“Laravel中如何使用PHP的装饰器模式”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。装饰器模式定义:它可以帮助您在
    02-08 laravelphp
  • PHP laravel使用自定义邮件类实现发送邮件
    PHP laravel使用自定义邮件类实现发送邮件
    当登录邮箱为腾讯企业邮箱的时候。Phpmailer发送邮件就不好用了,具体哪里不好用,我没真没找到。但是,邮件得发啊,怎么办呢?我这里搞了一个自定义的发送邮件类,腾讯企业邮箱也可用。但是,邮件发送失败,不会返回报错信息,这个可能是有点坑。源码如下:?
  • 详解PHP laravel中的加密与解密函数
    目录一:简介二:配置三:使用加密/解密1:加密2:不使用序列化进行加密3:解密Laravel为我们提供了完整的加密方法及加密模式。我之前一般在加密的时候使用的是我自己写的加密函数,但是这个玩意,有的位置还是不太使用,当然,破解的话,基本上也是不可能的
  • PHP laravel缓存cache机制详解
    目录一、访问多个缓存存储二、从缓存中获取数据1.获取数据并设置默认值2.检查缓存项是否存在3.数值增加/减少4.获取存储5.获取删除三、缓存中存储数据1.获取存储数据2.缓存不存在时存储数据3.永久存储数据四、从缓存中移除数据Laravel中的cache为我们提供了三
  • PHP laravel实现导出PDF功能
    PHP laravel实现导出PDF功能
    目录一、laravel-tcpdf二、tcpdf三、TCPDF解决保存中文文件名的方法补充一、laravel-tcpdf导出PDF文件Laravel框架为我们集成了一个插件tcpdf。下载地址:https://github.com/elibyy/tcpdf-laravel然后使用composer进行安装就可以了。具体安装过程,请查看文末
  • PHP laravel缓存cache机制怎么实现
    今天小编给大家分享一下PHP laravel缓存cache机制怎么实现的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。Laravel中的cache为我们
点击排行