Service Container

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


<<:  Day21 - 在 XState, 状态机器里无穷尽的状态、 资料:Extended State and context - 1

>>:  Day 22:1863. Sum of All Subset XOR Totals

D21 第十一周 (回忆篇)

这礼拜还是再追第九周的进度,因为额外研究了其他东西的关系。 apache alias js 剪贴簿 ...

[Android Studio] -- Day 2 主题变换Theme01

前言 刚好读到layout的部分,来做之前都没试过的APP色系转换。 正文 这次利用spinner来...

DAY 1:Hey! Go Design Patterns

大家好,本系列文章探讨经典 Design Patterns 在现代语言 Golang 的演变。虽然...

[Day09 - UI/UX] UI 绘制

在UI出图之前,我们先确认好目前的 wireframe 是完整的。接下来只要依照 wireframe...

Day16 中断 Lua 的执行 - coroutine

上次看的电脑萤幕程序 rom/programs/monitor.lua 还有一段特别的写法,是关於 ...