鍍金池/ 教程/ PHP/ 服務(wù)容器
門(mén)面
Laravel Homestead
安裝及配置
測(cè)試
HTTP 中間件
加密
升級(jí)指南
幫助函數(shù)
應(yīng)用目錄結(jié)構(gòu)
集合
新手入門(mén)指南-簡(jiǎn)單任務(wù)管理系統(tǒng)
任務(wù)調(diào)度
查詢構(gòu)建器
視圖
驗(yàn)證
Laravel Cashier(訂購(gòu)&支付&發(fā)票)
本地化
隊(duì)列
調(diào)整器
分頁(yè)
文件系統(tǒng)/云存儲(chǔ)
貢獻(xiàn)代碼
哈希
HTTP 控制器
緩存
遷移
HTTP 請(qǐng)求
Laravel Elixir
發(fā)行版本說(shuō)明
Envoy 任務(wù)運(yùn)行器(SSH任務(wù))
序列化
Session
起步
帶用戶功能的任務(wù)管理系統(tǒng)
起步
用戶授權(quán)
郵件
事件
填充數(shù)據(jù)
HTTP 路由
服務(wù)提供者
Blade 模板引擎
包開(kāi)發(fā)
用戶認(rèn)證
Artisan 控制臺(tái)
HTTP 響應(yīng)
集合
服務(wù)容器
關(guān)聯(lián)關(guān)系
一次請(qǐng)求的生命周期
契約
Redis
錯(cuò)誤&日志

服務(wù)容器

1、簡(jiǎn)介

Laravel 服務(wù)容器是一個(gè)用于管理類(lèi)依賴和執(zhí)行依賴注入的強(qiáng)大工具。依賴注入聽(tīng)上去很花哨,其實(shí)質(zhì)是通過(guò)構(gòu)造函數(shù)或者某些情況下通過(guò) set 方法將類(lèi)依賴注入到類(lèi)中。

讓我們看一個(gè)簡(jiǎn)單的例子:

<?php

namespace App\Jobs;

use App\User;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Contracts\Bus\SelfHandling;

class PurchasePodcast implements SelfHandling{
    /**
     * 郵件實(shí)現(xiàn)
     */
    protected $mailer;

    /**
     * 創(chuàng)建一個(gè)新的實(shí)例
     *
     * @param  Mailer  $mailer
     * @return void
     */
    public function __construct(Mailer $mailer)
    {
        $this->mailer = $mailer;
    }

    /**
     * 購(gòu)買(mǎi)一個(gè)播客
     *
     * @return void
     */
    public function handle()
    {
        //
    }
}

在本例中,當(dāng)播客被購(gòu)買(mǎi)后 PurchasePodcast 任務(wù)需要發(fā)送郵件,因此,你需要注入一個(gè)可以發(fā)送郵件的服務(wù)。由于該服務(wù)是被注入的,我們可以方便的使用其另一個(gè)實(shí)現(xiàn)來(lái)替換它,在測(cè)試的時(shí)候我們還可以”模擬“或創(chuàng)建一個(gè)假的郵件實(shí)現(xiàn)。

深入理解 Laravel 服務(wù)容器對(duì)于構(gòu)建功能強(qiáng)大的大型 Laravel 應(yīng)用而言至關(guān)重要,對(duì)于貢獻(xiàn)代碼到 Laravel 核心也很有幫助。

2、綁定

幾乎所有的服務(wù)容器綁定都是在服務(wù)提供者中完成。因此本章節(jié)的演示例子用到的容器都是在這種上下文環(huán)境中,如果一個(gè)類(lèi)沒(méi)有基于任何接口那么就沒(méi)有必要將其綁定到容器。容器并不需要被告知如何構(gòu)建對(duì)象,因?yàn)樗鼤?huì)使用 PHP 的反射服務(wù)自動(dòng)解析出具體的對(duì)象。

在一個(gè)服務(wù)提供者中,可以通過(guò)$this->app 變量訪問(wèn)容器,然后使用 bind 方法注冊(cè)一個(gè)綁定,該方法需要兩個(gè)參數(shù),第一個(gè)參數(shù)是我們想要注冊(cè)的類(lèi)名或接口名稱,第二個(gè)參數(shù)是返回類(lèi)的實(shí)例的閉包:

$this->app->bind('HelpSpot\API', function ($app) {
    return new HelpSpot\API($app['HttpClient']);
});

注意到我們接受容器本身作為解析器的一個(gè)參數(shù),然后我們可以使用該容器來(lái)解析我們正在構(gòu)建的對(duì)象的子依賴。

綁定一個(gè)單例 singleton 方法綁定一個(gè)只需要解析一次的類(lèi)或接口到容器,然后接下來(lái)對(duì)容器的調(diào)用將會(huì)返回同一個(gè)實(shí)例:

$this->app->singleton('FooBar', function ($app) {
    return new FooBar($app['SomethingElse']);
});

綁定實(shí)例 你還可以使用 instance 方法綁定一個(gè)已存在的對(duì)象實(shí)例到容器,隨后對(duì)容器的調(diào)用將總是返回給定的實(shí)例:

$fooBar = new FooBar(new SomethingElse);

$this->app->instance('FooBar', $fooBar);

2.1 綁定接口到實(shí)現(xiàn)

服務(wù)容器的一個(gè)非常強(qiáng)大的特性是其綁定接口到實(shí)現(xiàn)的能力。我們假設(shè)有一個(gè) EventPusher 接口及其RedisEventPusher實(shí)現(xiàn),編寫(xiě)完該接口的 RedisEventPusher實(shí)現(xiàn)后,就可以將其注冊(cè)到服務(wù)容器:

$this->app->bind('App\Contracts\EventPusher', 'App\Services\RedisEventPusher');

這段代碼告訴容器當(dāng)一個(gè)類(lèi)需要EventPusher的實(shí)現(xiàn)時(shí)將會(huì)注入 RedisEventPusher,現(xiàn)在我們可以在構(gòu)造器或者任何其它通過(guò)服務(wù)容器注入依賴的地方進(jìn)行 EventPusher 接口的類(lèi)型提示:

use App\Contracts\EventPusher;

/**
 * 創(chuàng)建一個(gè)新的類(lèi)實(shí)例
 *
 * @param  EventPusher  $pusher
 * @return void
 */
public function __construct(EventPusher $pusher){
    $this->pusher = $pusher;
}

2.2 上下文綁定

有時(shí)侯我們可能有兩個(gè)類(lèi)使用同一個(gè)接口,但我們希望在每個(gè)類(lèi)中注入不同實(shí)現(xiàn),例如,當(dāng)系統(tǒng)接到一個(gè)新的訂單的時(shí)候,我們想要通過(guò) PubNub 而不是 Pusher 發(fā)送一個(gè)事件。Laravel 定義了一個(gè)簡(jiǎn)單、平滑的方式來(lái)定義這種行為:

$this->app->when('App\Handlers\Commands\CreateOrderHandler')
          ->needs('App\Contracts\EventPusher')
          ->give('App\Services\PubNubEventPusher');
你甚至還可以傳遞一個(gè)閉包到 give 方法:
$this->app->when('App\Handlers\Commands\CreateOrderHandler')
          ->needs('App\Contracts\EventPusher')
          ->give(function () {
                  // Resolve dependency...
              });

2.3 標(biāo)簽

少數(shù)情況下我們需要解析特定分類(lèi)下的所有綁定,比如,也許你正在構(gòu)建一個(gè)接收多個(gè)不同 Report 接口實(shí)現(xiàn)的報(bào)告聚合器,在注冊(cè)完Report 實(shí)現(xiàn)之后,可以通過(guò) tag 方法給它們分配一個(gè)標(biāo)簽:

$this->app->bind('SpeedReport', function () {
    //
});

$this->app->bind('MemoryReport', function () {
    //
});

$this->app->tag(['SpeedReport', 'MemoryReport'], 'reports');

這些服務(wù)被打上標(biāo)簽后,可以通過(guò)tagged方法來(lái)輕松解析它們:

$this->app->bind('ReportAggregator', function ($app) {
    return new ReportAggregator($app->tagged('reports'));
});

3、解析

有很多方式可以從容器中解析對(duì)象,首先,你可以使用make方法,該方法接收你想要解析的類(lèi)名或接口名作為參數(shù):

$fooBar = $this->app->make('FooBar');

其次,你可以以數(shù)組方式訪問(wèn)容器,因?yàn)槠鋵?shí)現(xiàn)了 PHP 的 ArrayAccess 接口:

$fooBar = $this->app['FooBar'];

最后,也是最常用的,你可以簡(jiǎn)單的通過(guò)在類(lèi)的構(gòu)造函數(shù)中對(duì)依賴進(jìn)行類(lèi)型提示來(lái)從容器中解析對(duì)象,包括控制器、事件監(jiān)聽(tīng)器、隊(duì)列任務(wù)、中間件等都是通過(guò)這種方式。在實(shí)踐中,這是大多數(shù)對(duì)象從容器中解析的方式。 容器會(huì)自動(dòng)為其解析類(lèi)注入依賴,比如,你可以在控制器的構(gòu)造函數(shù)中為應(yīng)用定義的倉(cāng)庫(kù)進(jìn)行類(lèi)型提示,該倉(cāng)庫(kù)會(huì)自動(dòng)解析并注入該類(lèi):

<?php

namespace App\Http\Controllers;

use Illuminate\Routing\Controller;
use App\Users\Repository as UserRepository;

class UserController extends Controller{
    /**
     * 用戶倉(cāng)庫(kù)實(shí)例
     */
    protected $users;

    /**
     * 創(chuàng)建一個(gè)控制器實(shí)例
     *
     * @param  UserRepository  $users
     * @return void
     */
    public function __construct(UserRepository $users)
    {
        $this->users = $users;
    }

    /**
     * 通過(guò)指定 ID 顯示用戶
     *
     * @param  int  $id
     * @return Response
     */
    public function show($id)
    {
        //
    }
}

4、容器事件

服務(wù)容器在每一次解析對(duì)象時(shí)都會(huì)觸發(fā)一個(gè)事件,可以使用 resolving方法監(jiān)聽(tīng)該事件:

$this->app->resolving(function ($object, $app) {
    // 容器解析所有類(lèi)型對(duì)象時(shí)調(diào)用
});

$this->app->resolving(function (FooBar $fooBar, $app) {
    // 容器解析“FooBar”對(duì)象時(shí)調(diào)用
});

正如你所看到的,被解析的對(duì)象將會(huì)傳遞給回調(diào),從而允許你在對(duì)象被傳遞給消費(fèi)者之前為其設(shè)置額外屬性。

上一篇:視圖下一篇:緩存