服務(wù)容器

2018-12-17 10:46 更新

介紹

Laravel 服務(wù)容器是管理類依賴的強(qiáng)力工具。依賴注入是比較專業(yè)的說(shuō)法,真正意思是將類依賴透過(guò)構(gòu)造器或 「setter」 方法注入。

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

<?php namespace App\Handlers\Commands;use App\User;use App\Commands\PurchasePodcastCommand;use Illuminate\Contracts\Mail\Mailer;class PurchasePodcastHandler {

    /**
     * 一個(gè)發(fā)信功能的實(shí)現(xiàn)
     */
    protected $mailer;

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

    /**
     * 購(gòu)買一個(gè)播客節(jié)目
     *
     * @param  PurchasePodcastCommand  $command
     * @return void
     */
    public function handle(PurchasePodcastCommand $command)
    {
        //    }}

           

在這個(gè)例子中,當(dāng)播客被購(gòu)買時(shí), PurchasePodcast 命令處理器需要發(fā)送一封電子郵件。所以,我們將注入一個(gè)服務(wù)來(lái)提供這個(gè)能力。當(dāng)這個(gè)服務(wù)被注入以后,我們就可以輕易地切換到不同的實(shí)現(xiàn)。當(dāng)測(cè)試我們的應(yīng)用程序時(shí),我們同樣也可以輕易地「模擬」,或者創(chuàng)建一個(gè)虛擬的發(fā)信服務(wù)實(shí)現(xiàn),來(lái)幫助我們進(jìn)行測(cè)試。

如果要?jiǎng)?chuàng)建一個(gè)強(qiáng)大并且大型的應(yīng)用,或者對(duì) Laravel 的內(nèi)核做貢獻(xiàn),首先必須對(duì) Laravel 的服務(wù)容器進(jìn)行深入了解。

           

基本用法

綁定

幾乎你所有服務(wù)容器將與已注冊(cè)的服務(wù)提供者綁定,這些例子都在情境(context)使用容器做說(shuō)明,如果應(yīng)用程序其它地方需要容器實(shí)例,如工廠(factory),能以類型提示 Illuminate\Contracts\Container\Container 注入一個(gè)容器實(shí)例。另外,你可以使用 App facade 訪問(wèn)容器。

注冊(cè)基本解析器

在一個(gè)服務(wù)提供者內(nèi)部,你總是可以通過(guò) $this->app 實(shí)例變量來(lái)訪問(wèn)到容器。

在服務(wù)提供者里,總是通過(guò) $this->app 實(shí)例變量使用容器。

服務(wù)容器注冊(cè)依賴有幾種方式,包括閉包回調(diào)和綁定實(shí)例的接口。首先,我們來(lái)探討閉包回調(diào)的方式。被注冊(cè)至容器的閉包解析器包含一個(gè) key (通常用類名稱) 和一個(gè)有返回值的閉包:

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

           

注冊(cè)一個(gè)單例

有時(shí)候,你可能希望綁定到容器的對(duì)象只會(huì)被解析一次,之后的調(diào)用都返回相同的實(shí)例:

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

           

綁定一個(gè)已經(jīng)存在的實(shí)例

你也可以使用 instance 方法,綁定一個(gè)已經(jīng)存在的實(shí)例到容器,接下來(lái)將總是返回該實(shí)例:

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

           

解析

從容器解析出實(shí)例有幾種方式。                
一、可以使用 make 方法:

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

           

二、你可以像「訪問(wèn)數(shù)組」一樣對(duì)容器進(jìn)行訪問(wèn),因?yàn)樗鼘?shí)現(xiàn)了PHP的 ArrayAccess 接口:

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

           

最后,也是最重要的一點(diǎn),你可以在構(gòu)造函數(shù)中簡(jiǎn)單地「類型指定(type-hint)」你所需要的依賴,包括在控制器、事件監(jiān)聽器、隊(duì)列任務(wù),過(guò)濾器等等之中。容器將自動(dòng)注入你所需的所有依賴:

<?php namespace App\Http\Controllers;use Illuminate\Routing\Controller;use App\Users\Repository as UserRepository;class UserController extends Controller {

    /**
     * The user repository instance.
     */
    protected $users;

    /**
     * Create a new controller instance.
     *
     * @param  UserRepository  $users
     * @return void
     */
    public function __construct(UserRepository $users)
    {
        $this->users = $users;
    }

    /**
     * Show the user with the given ID.
     *
     * @param  int  $id
     * @return Response
     */
    public function show($id)
    {
        //    }}

           

           

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

注入具體依賴

服務(wù)容器有個(gè)非常強(qiáng)大特色,能夠綁定特定實(shí)例的接口。舉例,假設(shè)我們應(yīng)用程序要集成 Pusher 服務(wù)去收發(fā)即時(shí)事件,如果使用 Pusher 的 PHP SDK,可以在類注入一個(gè) Pusher 客戶端實(shí)例:

<?php namespace App\Handlers\Commands;use App\Commands\CreateOrder;use Pusher\Client as PusherClient;class CreateOrderHandler {

    /**
     * Pusher SDK 客戶端實(shí)例
     */
    protected $pusher;

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

    /**
     * 執(zhí)行命令
     *
     * @param  CreateOrder  $command
     * @return void
     */
    public function execute(CreateOrder $command)
    {
        //    }}

           

在上面這個(gè)例子中,注入類的依賴到類中已經(jīng)能夠滿足需求;但同時(shí),我們也緊密耦合于 Pusher 的 SDK 。如果 Pusher 的 SDK 方法發(fā)生改變,或者我們要切換到別的事件服務(wù),那我們也需要同時(shí)修改 CreateOrderHandler 的代碼。

為接口編程

為了將 CreateOrderHandler 和事件推送的修改「隔離」,我們可以定義一個(gè) EventPusher 接口和一個(gè) PusherEventPusher 實(shí)現(xiàn):

<?php namespace App\Contracts;interface EventPusher {

    /**
     * Push a new event to all clients.
     *
     * @param  string  $event
     * @param  array  $data
     * @return void
     */
    public function push($event, array $data);}

           

一旦 PusherEventPusher 實(shí)現(xiàn)這接口,就可以在服務(wù)容器像這樣注冊(cè)它:

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

           

當(dāng)有類需要 EventPusher 接口時(shí),會(huì)告訴容器應(yīng)該注入 PusherEventPusher,現(xiàn)在就可以在構(gòu)造器中「類型指定」一個(gè) EventPusher 接口:

    /**
     * Create a new order handler instance.
     *
     * @param  EventPusher  $pusher
     * @return void
     */
    public function __construct(EventPusher $pusher)
    {
        $this->pusher = $pusher;
    }

           

           

上下文綁定

有時(shí)候,你可能會(huì)有兩個(gè)類需要用到同一個(gè)接口,但是你希望為每個(gè)類注入不同的接口實(shí)現(xiàn)。例如當(dāng)我們的系統(tǒng)收到一個(gè)新的訂單時(shí),我們需要使用 PubNub 來(lái)代替 Pusher 發(fā)送消息。Laravel 提供了一個(gè)簡(jiǎn)單便利的接口來(lái)定義以上的行為:

$this->app->when('App\Handlers\Commands\CreateOrderHandler')
          ->needs('App\Contracts\EventPusher')
          ->give('App\Services\PubNubEventPusher');

           

           

標(biāo)簽

偶爾你可能需要解析綁定中的某個(gè)「類」。例如你正在建設(shè)一個(gè)匯總報(bào)表,它需要接收實(shí)現(xiàn)了 Report 接口的不同實(shí)現(xiàn)的數(shù)組。在注冊(cè)了 Report 的這些實(shí)現(xiàn)之后,你可以用 tag 方法來(lái)給他們賦予一個(gè)標(biāo)簽:

$this->app->bind('SpeedReport', function(){
    //});$this->app->bind('MemoryReport', function(){
    //});$this->app->tag(['SpeedReport', 'MemoryReport'], 'reports');

           

一旦服務(wù)打上標(biāo)簽,可以通過(guò) tagged 方法輕易地解析它們:

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

           

           

實(shí)際應(yīng)用

Laravel 提供了幾個(gè)機(jī)會(huì)來(lái)使用服務(wù)容器以提高應(yīng)用程序的靈活性和可測(cè)試性。解析控制器是一個(gè)最主要的案例。所有的控制器都通過(guò)服務(wù)容器來(lái)進(jìn)行解析,意味著你可以在控制器的構(gòu)造函數(shù)中「類型指定」所需依賴,而且它們將被自動(dòng)注入。

<?php namespace App\Http\Controllers;use Illuminate\Routing\Controller;use App\Repositories\OrderRepository;class OrdersController extends Controller {

    /**
     * The order repository instance.
     */
    protected $orders;

    /**
     * Create a controller instance.
     *
     * @param  OrderRepository  $orders
     * @return void
     */
    public function __construct(OrderRepository $orders)
    {
        $this->orders = $orders;
    }

    /**
     * Show all of the orders.
     *
     * @return Response
     */
    public function index()
    {
        $orders = $this->orders->all();

        return view('orders', ['orders' => $orders]);
    }}

           

在這個(gè)例子中,OrderRepository 類將被自動(dòng)注入到控制器中。這意味著在進(jìn)行 單元測(cè)試 時(shí),我們可以綁定一個(gè)假的 OrderRepository 到容器中來(lái)代替我們對(duì)數(shù)據(jù)庫(kù)的真實(shí)操作,避免對(duì)真實(shí)數(shù)據(jù)庫(kù)的影響。

使用容器的其他幾個(gè)例子

當(dāng)然,在上面提到過(guò)的,控制器并不是 Laravel 通過(guò)服務(wù)容器進(jìn)行解析的唯一類。你也可以在路由的閉包中、過(guò)濾器中、隊(duì)列任務(wù)中、事件監(jiān)聽器中來(lái)「類型指定」你所需要的依賴。對(duì)于在這些情境中如何使用服務(wù)容器,請(qǐng)參考相關(guān)文檔。

           

容器事件

注冊(cè)一個(gè)解析事件監(jiān)聽器

容器在解析每一個(gè)對(duì)象時(shí)就會(huì)觸發(fā)一個(gè)事件。你可以用 resolving 方法來(lái)監(jiān)聽此事件:

$this->app->resolving(function($object, $app){
    // 當(dāng)容器解析任意類型的依賴時(shí)被調(diào)用});$this->app->resolving(function(FooBar $fooBar, $app){
    // 當(dāng)容器解析 `FooBar` 類型的依賴時(shí)被調(diào)用});

           

被解析的對(duì)象將被傳入到閉包方法中。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)