iT邦幫忙

2021 iThome 鐵人賽

DAY 21
0

Service Container 是 Laravel 框架中相當重點的一個功能,主要是用來節省撰寫程式碼時手動建立類別實例的步驟。

說明 Service Container 的功能前先來看一個例子。

/app/Http/Controllers/TodoController.php

public function store(Request $request)
{
    $data = $request->all(); 

    $user = Auth::user();

    $user->todos()->create([
        'name' => $data['name']
    ]);
}

這是我們在 Controller 中建立的 store 方法,負責收到請求時建立資料,他有一個參數 $request ,從用法上來看 $request 明顯是某個 class 的實例,問題是這個實例是什麼時候建立的?

Zero Configuration Resolution

在 Laravel 的 Service Container 中,或者說 Laravel App 當中,當我們將函式的參數型別定義為某個 class 或是 interface 時,Laravel App 會自動解析該類別的建立方法( __construct ) 並嘗試建立實例傳入函式當中,讓我們可以省略掉宣告類別實例的步驟。

舉例來說如果沒有 Service Container 的話上面的程式碼就變成

public function store()
{
    $request = new Request(...requirements); 
    $data = $request->all(); 

    //...
}

我們必須先建好創建 Request 實例所需的參數,才能建成 Request 實例,如果這接參數又有各自依賴的參數,就必須一一建立後才能開始使用 Request 的功能。

如果做一次還好,但每個 Controller 底下的方法都要建一次 Request 就吃不消了。

而 Service Container 的好處就在於當我們宣告要使用這個類別時,會解析類別後嘗試自動去建立該類別的實例,如果類別不須需要參數就直接建立。如果需要參數,就繼續嘗試建立參數的實例,一層層自動建立直到最後。而最後的結果就是已經完全準備好供我們直接使用的實例。

當然偶而還是有些類別無法簡單的建立實例,需要特別宣告參數,這時候就得在 Service Container 中特別定義該類別的建立方法。

Service Providers

在 Service Container 中當遇到無法簡單建立實例的類別時,就需要額外定義 Service Provider 指導該如何建立該類別的實例。

Service Provider可以藉由指令建立,預設放在 app/Providers 目錄下。

php artisan make:provider RiakServiceProvider

借用官方文件的範例說明。

<?php

namespace App\Providers;

use App\Services\Riak\Connection;
use Illuminate\Support\ServiceProvider;

class RiakServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton(Connection::class, function ($app) {
            return new Connection(config('riak'));
        });
    }
}

ServiceProvider 會有一個 register 函式,也就是在 Service Container 中註冊該類別的實例建立方式的方法。

register 中呼叫的 $this->app 指的就是 Laravel App 本身的實例,並且用 singleton 宣告註冊的方式。

像這邊就是註冊 Connection::class 的建立方法,當有參數宣告為 Connection 類別時,就提供 new Connection(config('riak')) 所產生的實例作為該參數的值。

註冊的方式還不只一種,底下分別介紹。

bind

bind 是基礎的註冊方法,這種方式每次建立的實例都是全新的,彼此不同。

use App\Services\Transistor;
use App\Services\PodcastParser;

$this->app->bind(Transistor::class, function ($app) {
    return new Transistor($app->make(PodcastParser::class));
});

singleton

singleton 相對於 bind ,當 singleton 的實例第一次被建立後,之後就不會再重新建立,而是重複提供第一次建立的實例,所以如果實例被編輯過的話下次呼叫時會保留編輯的內容。

use App\Services\Transistor;
use App\Services\PodcastParser;

$this->app->singleton(Transistor::class, function ($app) {
    return new Transistor($app->make(PodcastParser::class));
});

scoped

scoped 相似於 singleton ,建立之後就保持相同的實例,不過 scoped 保持的範圍只有單次的 request 或是 job 的處理週期中,當開始下一次的 request 處理時就重新建立實例。

instance

instance 也很像 singleton ,只差別在 singleton 會等到第一次被呼叫後才固定實例, instance 則是直接指派一個已經建好的實例。

use App\Services\Transistor;
use App\Services\PodcastParser;

$service = new Transistor(new PodcastParser);

$this->app->instance(Transistor::class, $service);

綁定實作類別

不只能在類別被呼叫的時候指派實例,也能在介面被呼叫時指派特定的實作來產生實例

EventPusher 是介面, RedisEventPusher 則是 EventPusher 的實作類別。

use App\Contracts\EventPusher;
use App\Services\RedisEventPusher;

$this->app->bind(EventPusher::class, RedisEventPusher::class);

這樣當呼叫 EventPusher 作為參數時,正常來說他是介面是不能產生實例的,但因為我們在 Service Container 中指派了對應的實作類別 RedisEventPusher, Laravel 就能直接用 RedisEventPusher 來產生實例。

use App\Contracts\EventPusher;

public function __construct(EventPusher $pusher)
{
    $this->pusher = $pusher;
}

config/app.php

Service Provider 建立好之後記得要加入 config/app.php 中的 'providers' 陣列底下,這樣 App 被啟動時 Service 才會被註冊。

bindings / singletons

除了一手一手建立 Service Provider 外,如果要綁定的類別不是需要參數才能建立的類型,也可以用 $bindings 或 $singletons 批量註冊。

<?php

namespace App\Providers;

use App\Contracts\DowntimeNotifier;
use App\Contracts\ServerProvider;
use App\Services\DigitalOceanServerProvider;
use App\Services\PingdomDowntimeNotifier;
use App\Services\ServerToolsProvider;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * All of the container bindings that should be registered.
     *
     * @var array
     */
    public $bindings = [
        ServerProvider::class => DigitalOceanServerProvider::class,
    ];

    /**
     * All of the container singletons that should be registered.
     *
     * @var array
     */
    public $singletons = [
        DowntimeNotifier::class => PingdomDowntimeNotifier::class,
        ServerProvider::class => ServerToolsProvider::class,
    ];
}

make

除了在函式的參數宣告時借用 Service Container 的力量建立實例,也可以直接用 App 的 make() 方法建立。

public function store()
{
    $request = $this->app->make(Request::class); 
    $data = $request->all(); 

    //...
}

makeWith

如果建立類別的部分參數無法簡單的被 Service Container 解析,也可用 makeWith 手動傳入

use App\Services\Transistor;

$transistor = $this->app->makeWith(Transistor::class, ['id' => 1]);

References

Laravel - Service Container

Laravel - Service Providers


上一篇
Eloquent ORM - Model 資料轉換
下一篇
依賴注入
系列文
Laravel 實務筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言