Laravel源码学习之Container

Posted uneasy96

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Laravel源码学习之Container相关的知识,希望对你有一定的参考价值。

Ioc模式

Ioc(Inversion of Control)模式又称依赖注入(Dependency Injection)模式。控制反转就是将组件之间的依赖关系从程序的内部转移到程序外部,而依赖注入是指组件的依赖关系通过外部参数或其他形式注入,两种说法从本质上是一样的。
下面是一个简单的依赖注入实例:

<?php
interface Visit
{
    public function go();
}

class Leg implements Visit
{
    public function go()
    {
        echo "walk to Tibet";   
    }
}

class Traveller
{
    protected $trafficTool;
    
    public function __construct(Visit $trafficTool)
    {
        $this->trafficTool = $trafficTool;
    }
    
    public function visitTibet()
    {
        $this->trafficTool->go();
    }
}

//生成依赖的交通工具实例
$trafficTool = new Leg();
//依赖注入的方式解决依赖问题
$tra = new Traveller($trafficTool);
$tra->visitTibet();

上述实例就是一个简单的依赖注入过程,Traveller类的构造函数依赖一个外部的具有Visit接口的实例,而在实例化Traveller时,我们传递一个$trafficTool实例,即通过依赖注入的方式解决依赖问题。
这里需要注意的是:依赖注入需要通过接口来限制,而不能随意开放,这也提现了设计模式的另一原则————针对接口编程,而不是针对实现编程。
这里我们是通过手动的方式注入依赖,而通过Ioc容器可以实现自动依赖注入。下面对Laravel框架中的设计方法进行简化,实现Ioc容器完成依赖注入,代码如下:

<?php
class Container
{
    /*
     *用于存储提供实例的回调函数,真正的容器还会装实例等其他内容
     *从而实现单例等高级功能
     */
    protected $bindings = [];

    //绑定接口和生成相应实例的回调函数
    public function bind($abstract, $concrete = null, $shared = false)
    {
        if ( ! $concrete instanceof Closure){
            //如果提供的函数不是回调函数,则产生默认的回调函数
            $concrete = $this->getClosure($abstract, $concrete);
        }
        $this->bindings[$abstract] = compact('concrete', 'shared');
    }

    //默认生成实例的回调函数
    protected function getClosure($abstract, $concrete)
    {
        //生成实例的回调函数, $c一般为IOC容器对象,在调用回调生成实例时提供
        //即build函数中的$concrete($this)
        return function($c) use ($abstract, $cocrete)
        {
            $method = ( $abstract == $concrete ) ? 'build' : 'make';
            //调用的是容器的build或make方法生成实例
            return $c->$method($concrete);
        };
    }

    //生成实例对象,首先解决接口和要实例化类之间的依赖关系
    public function make($abstract)
    {
        $concrete = $this->getConcrete($abstract);
        if ($this->isBuildable($concrete, $abstract)){
            $object = $this->build($concrete);
        } else {
            $object = $this->make($concrete);
        }

        return $object;
    }

    protected function isBuildable($concrete, $abstract)
    {
        return $concrete === $abstract || $concrete instanceof Closure;
    }

    //获取绑定的回调函数
    protected function getConcrete($abstract)
    {
        if ( ! isset($this->bindings[$abstract])){
            return $abstract;
        }

        return $this->bindings[$abstract]['concrete'];
    }
    //实例化对象
    public function build($concrete)
    {
        if ($concrete instanceof Closure) {
            return $concrete($this);
        }
        $reflector = new ReflectionClass($concrete);
        if (! $reflector->isInstantiable()){
            echo $message = "Target [$concrete] is not instantiable";
        }
        $constructor = $reflector->getConstructor();
        if (is_null($constructor)){
            return new $concrete;
        }
        $dependencies = $constructor->getParameters();
        $instances = $this->getDependencies($dependencies);
        return $reflector->newInstanceArgs($instances);
    }
    //解决通过反射机制实例化对象时的依赖
    protected function getDependencies($parameters)
    {
        $dependencies = [];
        foreach ($parameters as $parameter) {
            $dependency = $parameter->getClass();
            if (is_null($denpendency)){
                $dependencies[] = NULL;
            } else {
                $dependencies[] = $this->resolveClass($parameter);
            }
        }
        return (array) $dependencies;
    }

    protected function resolveClass(ReflectionParameter $parameter)
    {
        return $this->make($parameter->getClass()->name);
    }
}

class Traveller
{
    protected $trafficTool;
    public function __construct(Visit $trafficTool)
    {
        $this->trafficTool = $trafficTool;
    }

    public function visitTibet()
    {
        $this->trafficTool->go();
    }
}
//实例化Ioc容器
$app = new Container();
//完成容器的添加
$app->bind("Visit", "Train");
$app->bind("traveller", "Traveller");
//通过容器实现依赖注入,完成类的实例化
$tra = $app->make("traveller");
$tra->visitTibet();

通过上述代码,就可以实现Ioc容器最核心的功能,解决依赖注入的根本问题。在实现过程中,没有用new关键字来实现实例化对象,不需要关注组件之间的依赖关系,只需要在容器中填充过程中理顺接口与实现类之间的关系及实现类与依赖接口之间的关系,就可以流水线式地完成实现类的实现过程。这里类的实例化是通过反射机制完成的。

以上是关于Laravel源码学习之Container的主要内容,如果未能解决你的问题,请参考以下文章

docker学习之初识container

爬虫学习之webmagic源码剖析

源码学习之设计模式(策略模式)

laravel学习之ArrayAccess

Qt源码学习之信号槽

iOS学习之代码块(Block)