Laravel之容器

1. 背景

慣例介紹下容器的背景,回答第一個(gè)問題:什么是容器?

顧名思義,容器即存放東西的地方,里面存放的可以是文本、數(shù)值,甚至是對(duì)象、接口、回調(diào)函數(shù)。

那通過容器,解決了什么問題呢?

通過容器最主要解決的就是“解耦” 、“依賴注入(DI)“,從而實(shí)現(xiàn)”控制反轉(zhuǎn)(IoC)“

2. DI

上面將了容器是用來解決依賴注入的,那到底什么是依賴注入呢?我們以下面的例子來說明下:

我們假設(shè)有一個(gè)訂單,在構(gòu)造函數(shù)中我們新建了OrderRepository,通過倉庫我們就可以對(duì)訂單進(jìn)行持久化了,但是突然有一天,我們想把訂單的存儲(chǔ)從數(shù)據(jù)庫換到redis,我們這時(shí)候就必須改訂單的構(gòu)造函數(shù),將OrderRepository換為OrderRedisRepository,而且可能兩者的接口還不一樣,改動(dòng)成本非常大。如果哪天我們又想將存儲(chǔ)換到mongodb,那我們又得改Order的構(gòu)造函數(shù),這個(gè)時(shí)候,我們可以定義一個(gè)接口Repository,而Order的構(gòu)造函數(shù)接受Repository作為參數(shù),

class Order {

    /**
     * @type Repository
     */
    private $repository;

    /**
     * Order constructor.
     *
     * @param Repository $repository
     */
    public function __construct(Repository $repository)
    {
//        $this->repository = new OrderMysqlRepository();
//        $this->repository = new OrderRedisRepository();

        $this->repository = $repository;
    }
}

這樣客戶端在使用上就變成

$repository = new OrderMysqlRepository();
$order = new Order($repository);

上面就是依賴注入了,我們通過構(gòu)造函數(shù)傳參的方式將$repository注入到了Order中。

了解了依賴注入,下面就到了我們今天的重點(diǎn)依賴反轉(zhuǎn)。

3. 依賴反轉(zhuǎn)

上面客戶端在使用的時(shí)候,還是需要手動(dòng)的創(chuàng)建OrderMysqlRepository,有沒有可能將這個(gè)創(chuàng)建的邏輯也從客戶端抽離出來呢?看下面的代碼

class Container
{
    protected $binds;

    protected $instances;

    public function bind($abstract, $concrete)
    {
        if ($concrete instanceof \Closure) {
            $this->binds[$abstract] = $concrete;
        } else {
            $this->instances[$abstract] = $concrete;
        }
    }

    public function make($abstract, $parameters = [])
    {
        if (isset($this->instances[$abstract])) {
            return $this->instances[$abstract];
        }

        array_unshift($parameters, $this);

        return call_user_func_array($this->binds[$abstract], $parameters);
    }
}

上面就是一個(gè)簡(jiǎn)單的容器,在使用上

public function testContainer()
    {
        $container = new Container();
        $container->bind('order',function(Container $c,$repository){
            return new Order($c->make($repository));
        });
        $container->bind('Repository',new OrderRedisRepository);
        $order = $container->make('order',['Repository']);
    }

以上就是一個(gè)基本簡(jiǎn)單可用的Ioc容器了。

我們可以看到IoC核心就是通過事先將一些代碼片段注冊(cè)到容器中,當(dāng)我們需要實(shí)例化類的時(shí)候,通過容器,自動(dòng)的將對(duì)象需要的參數(shù)實(shí)例化出來,并注入進(jìn)去。

4. Laravel中的容器

Laravel中容器共有15個(gè)方法,簡(jiǎn)單分類了下

Container

4.1 注冊(cè)

4.1.1 bind

先來看下注冊(cè),Laravel的容器支持好多種注冊(cè)方式,先看最常用的bind,其函數(shù)簽名是:

public function bind($abstract, $concrete = null, $shared = false);

看到簽名中有3個(gè)參數(shù),在函數(shù)內(nèi)部經(jīng)過各種操作后,最終落地到存儲(chǔ)上,形式是:

$bindings = [
  'abstract' => [
    'concrete' => $concrete,
    'shared' => $shared;
   ],   
];

bind在注冊(cè)上,像之前提到過的,可以注冊(cè)文本、數(shù)值,甚至是對(duì)象、接口、回調(diào)函數(shù),下面就每種形式給出測(cè)試,

先看閉包形式:

public function testClosureResolution()
    {
        $container = new Container;
        $container->bind('name', function () {
            return 'Taylor';
        });
//          dd($container);
        $this->assertEquals('Taylor', $container->make('name'));
    }

上面為了測(cè)試,通過dd可以打印出好container來,我們看到

Illuminate\Container\Container {#20
  #resolved: []
  #bindings: array:1 [
    "name" => array:2 [
      "concrete" => Closure {#21
        class: "LaravelContainerTest"
      }
      "shared" => false
    ]
  ]
  #instances: []
  #aliases: []
  #extenders: []
  #tags: []
  #buildStack: []
  +contextual: []
  #reboundCallbacks: []
  #globalResolvingCallbacks: []
  #globalAfterResolvingCallbacks: []
  #resolvingCallbacks: []
  #afterResolvingCallbacks: []
}

上面是container的內(nèi)部,經(jīng)過bind后,里面的bindings多了我們注冊(cè)過的name,下一步注冊(cè)過了,就應(yīng)該要調(diào)用make實(shí)例化出來,調(diào)用make后,container中resolved多個(gè)key

 #resolved: array:1 [
    "name" => true
  ]

在實(shí)現(xiàn)make的時(shí)候,通過判斷是否是閉包來判斷,如果是閉包,則直接調(diào)用,否則通過反射機(jī)制實(shí)例化出來

if ($concrete instanceof Closure) {
   return $concrete($this, $parameters);
}

$reflector = new ReflectionClass($concrete);

4.1.2 instance

instance是將我們已經(jīng)實(shí)例化出來的對(duì)象、文本等注冊(cè)進(jìn)入容器,使用方法如下

    public function testSimpleInstance()
    {
        $c = new Container();
        $name = 'zhuanxu';
        $c->instance('name',$name);
        $this->assertEquals($name,$c->make('name'));
    }

instance方法將其寫入到instances: []

4.1.3 singleton

$container = new Container;
$container->singleton('ContainerConcreteStub');

$var1 = $container->make('ContainerConcreteStub');
$var2 = $container->make('ContainerConcreteStub');
$this->assertSame($var1, $var2);

singleton是對(duì)bind的簡(jiǎn)單封裝

public function singleton($abstract, $concrete = null)
    {
        $this->bind($abstract, $concrete, true);
    }

4.1.4 alias

public function testAliases()
    {
        $container = new Container;
        $container['foo'] = 'bar';
        $container->alias('foo', 'baz');
        $container->alias('baz', 'bat');
        $this->assertEquals('bar', $container->make('foo'));
        $this->assertEquals('bar', $container->make('baz'));
        $this->assertEquals('bar', $container->make('bat'));
        $container->bind(['bam' => 'boom'], function () {
            return 'pow';
        });
        $this->assertEquals('pow', $container->make('bam'));
        $this->assertEquals('pow', $container->make('boom'));
        $container->instance(['zoom' => 'zing'], 'wow');
        $this->assertEquals('wow', $container->make('zoom'));
        $this->assertEquals('wow', $container->make('zing'));
    }

alias函數(shù)是通過起別名的方式來讓容器make

4.1.5 share

share是通過閉包的形式,加上關(guān)鍵字static實(shí)現(xiàn)的

public function share(Closure $closure)
    {
        return function ($container) use ($closure) {
            static $object;

            if (is_null($object)) {
                $object = $closure($container);
            }

            return $object;
        };
    }

4.1.6 extend

extend是在當(dāng)原來的容器實(shí)例化出來后,可以對(duì)其進(jìn)行擴(kuò)展

public function testExtendInstancesArePreserved()
    {
        $container = new Container;
        $container->bind('foo', function () {
            $obj = new StdClass;
            $obj->foo = 'bar';

            return $obj;
        });
        $obj = new StdClass;
        $obj->foo = 'foo';
        $container->instance('foo', $obj);
        $container->extend('foo', function ($obj, $container) {
            $obj->bar = 'baz';

            return $obj;
        });
        $container->extend('foo', function ($obj, $container) {
            $obj->baz = 'foo';

            return $obj;
        });
        
        $this->assertEquals('foo', $container->make('foo')->foo);
        $this->assertEquals('baz', $container->make('foo')->bar);
        $this->assertEquals('foo', $container->make('foo')->baz);
    }

4.2 實(shí)例化

4.2.1 call

call直接調(diào)用函數(shù),自動(dòng)注入依賴

public function testCallWithDependencies()
    {
        $container = new Container;
        $result = $container->call(function (StdClass $foo, $bar = []) {
            return func_get_args();
        });

        $this->assertInstanceOf('stdClass', $result[0]);
        $this->assertEquals([], $result[1]);

        $result = $container->call(function (StdClass $foo, $bar = []) {
            return func_get_args();
        }, ['bar' => 'taylor']);

        $this->assertInstanceOf('stdClass', $result[0]);
        $this->assertEquals('taylor', $result[1]);

        /*
         * Wrap a function...
         */
        $result = $container->wrap(function (StdClass $foo, $bar = []) {
            return func_get_args();
        }, ['bar' => 'taylor']);

        $this->assertInstanceOf('Closure', $result);
        $result = $result();

        $this->assertInstanceOf('stdClass', $result[0]);
        $this->assertEquals('taylor', $result[1]);
    }

今天就先講到這,有用的地方再去看的。

參考

laravel 學(xué)習(xí)筆記 —— 神奇的服務(wù)容器

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,923評(píng)論 18 139
  • 先說幾句廢話,調(diào)和氣氛。事情的起由來自客戶需求頻繁變更,偉大的師傅決定橫刀立馬的改革使用新的框架(created ...
    wsdadan閱讀 3,091評(píng)論 0 12
  • 背景 接到上面一個(gè)需求,要設(shè)計(jì)一個(gè)英雄打怪,怎么做呢? 設(shè)計(jì) 簡(jiǎn)要的設(shè)計(jì)圖 我們可以看到Role通過依賴注入武器,...
    小聰明李良才閱讀 516評(píng)論 0 3
  • 學(xué)習(xí)laravel快小一年了,到現(xiàn)在才去研究laravel 的核心 '容器 IOC' 這些概念. 寫項(xiàng)目的時(shí)候有大...
    哎喲我的巴扎黑閱讀 5,217評(píng)論 6 26
  • 夢(mèng)里看花追落水 身醒魂醉 茫茫朝朝歲歲 曾幾知味 窗外鶯歌紛飛 窗里垣壁頹頹 只嘆心累
    是臥貓先生閱讀 351評(píng)論 0 0