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 的实例,问题是这个实例是什麽时候建立的?
在 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 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 是基础的注册方法,这种方式每次建立的实例都是全新的,彼此不同。
use App\Services\Transistor;
use App\Services\PodcastParser;
$this->app->bind(Transistor::class, function ($app) {
return new Transistor($app->make(PodcastParser::class));
});
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 相似於 singleton ,建立之後就保持相同的实例,不过 scoped 保持的范围只有单次的 request 或是 job 的处理周期中,当开始下一次的 request 处理时就重新建立实例。
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;
}
Service Provider 建立好之後记得要加入 config/app.php 中的 'providers' 阵列底下,这样 App 被启动时 Service 才会被注册。
除了一手一手建立 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,
];
}
除了在函式的参数宣告时借用 Service Container 的力量建立实例,也可以直接用 App 的 make() 方法建立。
public function store()
{
$request = $this->app->make(Request::class);
$data = $request->all();
//...
}
如果建立类别的部分参数无法简单的被 Service Container 解析,也可用 makeWith 手动传入
use App\Services\Transistor;
$transistor = $this->app->makeWith(Transistor::class, ['id' => 1]);
<<: Day21 - 在 XState, 状态机器里无穷尽的状态、 资料:Extended State and context - 1
>>: Day 22:1863. Sum of All Subset XOR Totals
这礼拜还是再追第九周的进度,因为额外研究了其他东西的关系。 apache alias js 剪贴簿 ...
前言 刚好读到layout的部分,来做之前都没试过的APP色系转换。 正文 这次利用spinner来...
大家好,本系列文章探讨经典 Design Patterns 在现代语言 Golang 的演变。虽然...
在UI出图之前,我们先确认好目前的 wireframe 是完整的。接下来只要依照 wireframe...
上次看的电脑萤幕程序 rom/programs/monitor.lua 还有一段特别的写法,是关於 ...