Laravel之容器

1. 背景

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

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

那通過(guò)容器,解決了什么問(wèn)題呢?

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

2. DI

上面將了容器是用來(lái)解決依賴注入的隐解,那到底什么是依賴注入呢鞍帝?我們以下面的例子來(lái)說(shuō)明下:

我們假設(shè)有一個(gè)訂單,在構(gòu)造函數(shù)中我們新建了OrderRepository煞茫,通過(guò)倉(cāng)庫(kù)我們就可以對(duì)訂單進(jìn)行持久化了帕涌,但是突然有一天,我們想把訂單的存儲(chǔ)從數(shù)據(jù)庫(kù)換到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);

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

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

3. 依賴反轉(zhuǎn)

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

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核心就是通過(guò)事先將一些代碼片段注冊(cè)到容器中拌滋,當(dāng)我們需要實(shí)例化類的時(shí)候,通過(guò)容器绘盟,自動(dòng)的將對(duì)象需要的參數(shù)實(shí)例化出來(lái)鸠真,并注入進(jìn)去。

4. Laravel中的容器

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

Container

4.1 注冊(cè)

4.1.1 bind

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

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

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

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

bind在注冊(cè)上疾渴,像之前提到過(guò)的千贯,可以注冊(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è)試,通過(guò)dd可以打印出好container來(lái)店量,我們看到

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)過(guò)bind后,里面的bindings多了我們注冊(cè)過(guò)的name融师,下一步注冊(cè)過(guò)了右钾,就應(yīng)該要調(diào)用make實(shí)例化出來(lái),調(diào)用make后旱爆,container中resolved多個(gè)key

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

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

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

$reflector = new ReflectionClass($concrete);

4.1.2 instance

instance是將我們已經(jīng)實(shí)例化出來(lái)的對(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ù)是通過(guò)起別名的方式來(lái)讓容器make

4.1.5 share

share是通過(guò)閉包的形式浩淘,加上關(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)原來(lái)的容器實(shí)例化出來(lái)后,可以對(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)系作者
  • 序言:七十年代末洼怔,一起剝皮案震驚了整個(gè)濱河市署惯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌镣隶,老刑警劉巖极谊,帶你破解...
    沈念sama閱讀 212,080評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異安岂,居然都是意外死亡轻猖,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,422評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門域那,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)咙边,“玉大人,你說(shuō)我怎么就攤上這事“苄恚” “怎么了王带?”我有些...
    開封第一講書人閱讀 157,630評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)市殷。 經(jīng)常有香客問(wèn)我愕撰,道長(zhǎng),這世上最難降的妖魔是什么被丧? 我笑而不...
    開封第一講書人閱讀 56,554評(píng)論 1 284
  • 正文 為了忘掉前任盟戏,我火速辦了婚禮绪妹,結(jié)果婚禮上甥桂,老公的妹妹穿的比我還像新娘。我一直安慰自己邮旷,他們只是感情好黄选,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,662評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著婶肩,像睡著了一般办陷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上律歼,一...
    開封第一講書人閱讀 49,856評(píng)論 1 290
  • 那天民镜,我揣著相機(jī)與錄音,去河邊找鬼险毁。 笑死制圈,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的畔况。 我是一名探鬼主播鲸鹦,決...
    沈念sama閱讀 39,014評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼跷跪!你這毒婦竟也來(lái)了馋嗜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,752評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤吵瞻,失蹤者是張志新(化名)和其女友劉穎葛菇,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體橡羞,經(jīng)...
    沈念sama閱讀 44,212評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡眯停,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,541評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了尉姨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片庵朝。...
    茶點(diǎn)故事閱讀 38,687評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出九府,到底是詐尸還是另有隱情椎瘟,我是刑警寧澤,帶...
    沈念sama閱讀 34,347評(píng)論 4 331
  • 正文 年R本政府宣布侄旬,位于F島的核電站肺蔚,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏儡羔。R本人自食惡果不足惜宣羊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,973評(píng)論 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望汰蜘。 院中可真熱鬧仇冯,春花似錦、人聲如沸族操。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,777評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)色难。三九已至泼舱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間枷莉,已是汗流浹背娇昙。 一陣腳步聲響...
    開封第一講書人閱讀 32,006評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留笤妙,地道東北人冒掌。 一個(gè)月前我還...
    沈念sama閱讀 46,406評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像危喉,于是被迫代替她去往敵國(guó)和親宋渔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,576評(píng)論 2 349

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

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