Laravel 服務容器實現原理

前言

經過實現laravel 框架功能,以便深刻理解laravel框架的先進思想。php

什麼是服務容器

服務容器是用來管理類依賴與運行依賴注入的工具。Laravel框架中就是使用服務容器來實現 控制反轉 依賴注入 laravel

什麼是控制反轉(IoC)和依賴注入(DI)

控制反轉(IoC) 就是說把建立對象的 控制權 進行轉移,之前建立對象的主動權和建立時機是由本身把控的,而如今這種權力轉移到第三方,也就是 Laravel 中的容器。閉包

依賴注入(DI)則是幫助容器實如今運行中動態的爲對象提供提依賴的資源。app

概念容易不太容易讓人理解,舉個栗子:框架

//咱們構建一我的的類和一個狗的類
 class People
{
    public $dog = null;

    public function __construct()
    {
        $this->dog = new Dog();
    }

    public function putDog(){
        return $this->dog->dogCall();
    }

}

class Dog{
    public function dogCall(){
        return '汪汪汪';
    }
}

這我的在遛狗,忽然遇到了死對頭,他因而放狗咬人函數

$people = new People();
$people->putDog();

在這個操做中,people類要執行 putDog() 這個方法,須要依賴Dog類,通常咱們像上面同樣,在people中利用構造函數來添加這個Dog依賴。若是使用控制反轉 依賴注入則是這個樣子工具

class People
{
    public $dog = null;

    public function __construct(Dog $Dog)
    {
        $this->dog = $dog;
    }

    public function putDog(){
        return $this->dog->dogCall();
    }

}

People類經過構造參數聲明本身須要的 依賴類,由容器自動注入。這樣就實現了程序的有效解耦,好處在這就很少說了。ui

Laravel容器依賴注入的實現

實現原理須要瞭解的知識點:

閉包(匿名函數):this

匿名函數(Anonymous functions),也叫閉包函數(closures),容許 臨時建立一個沒有指定名稱的函數debug

反射:PHP 5 以上版本具備完整的反射 API,添加了對類、接口、函數、方法和擴展進行反向工程的能力。 此外,反射 API 提供了方法來取出函數、類和方法中的文檔註釋

理解了閉包和反射的基本用法咱們來看Laravel中是怎麼實現容器的,下面代碼是我對laravel框架容器部分代碼的簡化核心版:
lass Container
{
    /**
     *  容器綁定,用來裝提供的實例或者 提供實例的回調函數
     * @var array
     */
    public $building = [];

    /**
     * 註冊一個綁定到容器
     */
    public function bind($abstract, $concrete = null, $shared = false)
    {
        if(is_null($concrete)){
            $concrete = $abstract;
        }

        if(!$concrete instanceOf Closure){
            $concrete = $this->getClosure($abstract, $concrete);
        }

        $this->building[$abstract] =  compact("concrete", "shared");
    }

    //註冊一個共享的綁定 單例
    public function singleton($abstract, $concrete, $shared = true){
        $this->bind($abstract, $concrete, $shared);
    }

    /**
     * 默認生成實例的回調閉包
     *
     * @param $abstract
     * @param $concrete
     * @return Closure
     */
    public function getClosure($abstract, $concrete)
    {
        return function($c) use($abstract, $concrete){
            $method = ($abstract == $concrete)? '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;
    }

    /**
     * 獲取綁定的回調函數
     */
    public function getConcrete($abstract)
    {
        if(! isset($this->building[$abstract])){
            return $abstract;
        }

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

    /**
     * 判斷 是否 能夠建立服務實體
     */
    public function isBuildable($concrete, $abstract)
    {
        return $concrete === $abstract || $concrete instanceof Closure;
    }

    /**
     * 根據實例具體名稱實例具體對象
     */
    public function build($concrete)
    {
        if($concrete instanceof Closure){
            return $concrete($this);
        }

        //建立反射對象
        $reflector = new ReflectionClass($concrete);

        if( ! $reflector->isInstantiable()){
            //拋出異常
            throw new \Exception('沒法實例化');
        }

        $constructor = $reflector->getConstructor();
        if(is_null($constructor)){
            return new $concrete;
        }

        $dependencies = $constructor->getParameters();
        $instance = $this->getDependencies($dependencies);

        return $reflector->newInstanceArgs($instance);

    }

    //經過反射解決參數依賴
    public function getDependencies(array $dependencies)
    {
        $results = [];
        foreach( $dependencies as $dependency ){
            $results[] = is_null($dependency->getClass())
                ?$this->resolvedNonClass($dependency)
                :$this->resolvedClass($dependency);
        }

        return $results;
    }

    //解決一個沒有類型提示依賴
    public function resolvedNonClass(ReflectionParameter $parameter)
    {
        if($parameter->isDefaultValueAvailable()){
            return $parameter->getDefaultValue();
        }
        throw new \Exception('出錯');

    }

    //經過容器解決依賴
    public function resolvedClass(ReflectionParameter $parameter)
    {
        return $this->make($parameter->getClass()->name);

    }

}

容器的工做流程

接着上面遛狗的例子:

//實例化容器類
$app =  new Container();
//向容器中填充Dog
$app->bind('Dog','App\Dog');
//填充People
$app->bind('People', 'App\People');
//經過容器實現依賴注入,完成類的實例化;
$people = $app->make('People');
//調用方法
echo $people->putDog();

上面示例中咱們先實例化容器類,而後使用 bind() 方法 綁定接口和 生成相應的實例的閉包函數。而後使用 make() 函數生成實例對象,在 make() 中會調用 isBuildable($concrete, $abstract) 來判斷 給定的服務實體( $concrete 參數)是否能夠建立,能夠建立 就會調用 build($concrete) 函數 , build($concrete) 函數會判斷傳的參數是 是 閉包 仍是 具體類名 ,若是是閉包則直接運行,若是是具體類名的話,則經過反射獲取該類的構造函數所需的依賴,完成實例化。

重點理解 下面這幾個函數中 反射的用法,應該就很好理解了

build($concrete)
getDependencies(array $dependencies)
resolvedNonClass(ReflectionParameter $parameter)
resolvedClass(ReflectionParameter $parameter)

最後

IoC 理解起來是有點難度,可能文中描述讓你感受不是很清楚,能夠將文中代碼 在php中用debug觀察 運行狀態。

理解了容器的具體實現原理,再去看Laravel中的相關實現,就會感受豁然開朗。

相關文章
相關標籤/搜索