Laravel-Provider

思考并回答以下问题:

简介

服务提供者是Laravel应用启动的中心,你自己的应用以及所有Laravel的核心服务都是通过服务提供者启动。

但是,我们所谓的「启动」指的是什么?通常,这意味着注册服务,包括注册服务容器绑定、事件监听器、中间件甚至路由。服务提供者是应用配置的中心。

如果你打开Laravel自带的config/app.php文件,将会看到一个providers数组,这里就是应用所要加载的所有服务提供者类,当然,其中很多是延迟加载的,也就是说不是每次请求都会被加载,只有真的用到它们的时候才会加载。

通过本文档,你将会学习如何编写自己的服务提供者并在Laravel应用中注册它们。

编写服务提供者

所有的服务提供者都继承自Illuminate\Support\ServiceProvider类。大部分服务提供者都包含两个方法:register和boot。在register方法中,你唯一要做的事情就是绑定服务到服务容器,不要尝试在该方法中注册事件监听器,路由或者任何其它功能。

通过Artisan命令make:provider即可生成一个新的提供者:

1
php artisan make:provider RiakServiceProvider

register方法

正如前面所提到的,在register方法中只绑定服务到服务容器,而不要做其他事情,否则,一不小心就可能用到一个尚未被加载的服务提供者提供的服务。

现在让我们来看看一个基本的服务提供者长什么样,在任何服务提供者方法中,都可以通过$app属性来访问服务容器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php

namespace App\Providers;

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

class RiakServiceProvider extends ServiceProvider{
/**
* 在容器中注册绑定.
*
* @return void
*/
public function register()
{
$this->app->singleton(Connection::class, function ($app) {
return new Connection(config('riak'));
});
}
}

该服务提供者只定义了一个register方法,并使用该方法在服务容器中定义了一个Riak\Connection的实现。如果你不知道服务容器是如何工作的,请参考其文档。

bindings和singletons属性

如果你的服务提供者注册了很多简单的绑定,你可能希望使用bindings和singletons属性来替代手动注册每个容器绑定以简化代码。当服务提供者被框架加载后,会自动检查这些属性并注册相应绑定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php

namespace App\Providers;

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

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,
ServerToolsProvider::class => ServerToolsProvider::class,
];
}

boot方法

如果我们想要在服务提供者中注册视图Composer该怎么做?这就要用到boot方法了。该方法在所有服务提供者被注册以后才会被调用,这就是说我们可以在其中访问框架已注册的所有其它服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class ComposerServiceProvider extends ServiceProvider{
/**
* Perform post-registration booting of services.
*
* @return void
*/
public function boot()
{
view()->composer('view', function () {
//
});
}
}

boot方法的依赖注入

我们可以在boot方法中对依赖进行类型提示,服务容器会自动注入你所需要的依赖:

1
2
3
4
5
6
7
use Illuminate\Contracts\Routing\ResponseFactory;

public function boot(ResponseFactory $response){
$response->macro('caps', function ($value) {
//
});
}

注册服务提供者

所有服务提供者都是通过配置文件config/app.php中进行注册,该文件包含了一个列出所有服务提供者名字的providers数组,默认情况下,其中列出了所有核心服务提供者,这些服务提供者启动 Laravel核心组件,比如邮件、队列、缓存等等。

要注册你自己的服务提供者,只需要将其追加到该数组中即可:

1
2
3
4
'providers' => [
// 其它服务提供者
App\Providers\ComposerServiceProvider::class,
],

延迟加载服务提供者

如果你的提供者仅仅只是在服务容器中注册绑定,你可以选择延迟加载该绑定直到注册绑定的服务真的需要时再加载,延迟加载这样的一个提供者将会提升应用的性能,因为它不会在每次请求时都从文件系统加载。

Laravel编译并保存所有延迟服务提供者提供的服务及服务提供者的类名。然后,只有当你尝试解析其中某个服务时Laravel才会加载其服务提供者。

想要延迟加载一个提供者,需要实现\Illuminate\Contracts\Support\DeferrableProvider接口并定义一个provides方法,provides方法会返回通过服务提供者注册的服务容器绑定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php

namespace App\Providers;

use Riak\Connection;
use Illuminate\Support\ServiceProvider;
use Illuminate\Contracts\Support\DeferrableProvider;

class RiakServiceProvider extends ServiceProvider implements DeferrableProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app->singleton(Connection::class, function ($app) {
return new Connection($app['config']['riak']);
});
}

/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return [Connection::class];
}
}

源码

Illuminate\Support\ServiceProvider.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
<?php

namespace Illuminate\Support;

use Illuminate\Console\Application as Artisan;
use Illuminate\Contracts\Support\DeferrableProvider;

abstract class ServiceProvider
{
/**
* The application instance.
*
* @var \Illuminate\Contracts\Foundation\Application
*/
protected $app;

/**
* The paths that should be published.
*
* @var array
*/
public static $publishes = [];

/**
* The paths that should be published by group.
*
* @var array
*/
public static $publishGroups = [];

/**
* Create a new service provider instance.
*
* @param \Illuminate\Contracts\Foundation\Application $app
* @return void
*/
public function __construct($app)
{
$this->app = $app;
}

/**
* Register any application services.
*
* @return void
*/
public function register()
{
//
}

/**
* Merge the given configuration with the existing configuration.
*
* @param string $path
* @param string $key
* @return void
*/
protected function mergeConfigFrom($path, $key)
{
if (! $this->app->configurationIsCached()) {
$this->app['config']->set($key, array_merge(
require $path, $this->app['config']->get($key, [])
));
}
}

/**
* Load the given routes file if routes are not already cached.
*
* @param string $path
* @return void
*/
protected function loadRoutesFrom($path)
{
if (! $this->app->routesAreCached()) {
require $path;
}
}

/**
* Register a view file namespace.
*
* @param string|array $path
* @param string $namespace
* @return void
*/
protected function loadViewsFrom($path, $namespace)
{
if (is_array($this->app->config['view']['paths'])) {
foreach ($this->app->config['view']['paths'] as $viewPath) {
if (is_dir($appPath = $viewPath.'/vendor/'.$namespace)) {
$this->app['view']->addNamespace($namespace, $appPath);
}
}
}

$this->app['view']->addNamespace($namespace, $path);
}

/**
* Register a translation file namespace.
*
* @param string $path
* @param string $namespace
* @return void
*/
protected function loadTranslationsFrom($path, $namespace)
{
$this->app['translator']->addNamespace($namespace, $path);
}

/**
* Register a JSON translation file path.
*
* @param string $path
* @return void
*/
protected function loadJsonTranslationsFrom($path)
{
$this->app['translator']->addJsonPath($path);
}

/**
* Register a database migration path.
*
* @param array|string $paths
* @return void
*/
protected function loadMigrationsFrom($paths)
{
$this->app->afterResolving('migrator', function ($migrator) use ($paths) {
foreach ((array) $paths as $path) {
$migrator->path($path);
}
});
}

/**
* Register paths to be published by the publish command.
*
* @param array $paths
* @param mixed $groups
* @return void
*/
protected function publishes(array $paths, $groups = null)
{
$this->ensurePublishArrayInitialized($class = static::class);

static::$publishes[$class] = array_merge(static::$publishes[$class], $paths);

foreach ((array) $groups as $group) {
$this->addPublishGroup($group, $paths);
}
}

/**
* Ensure the publish array for the service provider is initialized.
*
* @param string $class
* @return void
*/
protected function ensurePublishArrayInitialized($class)
{
if (! array_key_exists($class, static::$publishes)) {
static::$publishes[$class] = [];
}
}

/**
* Add a publish group / tag to the service provider.
*
* @param string $group
* @param array $paths
* @return void
*/
protected function addPublishGroup($group, $paths)
{
if (! array_key_exists($group, static::$publishGroups)) {
static::$publishGroups[$group] = [];
}

static::$publishGroups[$group] = array_merge(
static::$publishGroups[$group], $paths
);
}

/**
* Get the paths to publish.
*
* @param string $provider
* @param string $group
* @return array
*/
public static function pathsToPublish($provider = null, $group = null)
{
if (! is_null($paths = static::pathsForProviderOrGroup($provider, $group))) {
return $paths;
}

return collect(static::$publishes)->reduce(function ($paths, $p) {
return array_merge($paths, $p);
}, []);
}

/**
* Get the paths for the provider or group (or both).
*
* @param string|null $provider
* @param string|null $group
* @return array
*/
protected static function pathsForProviderOrGroup($provider, $group)
{
if ($provider && $group) {
return static::pathsForProviderAndGroup($provider, $group);
} elseif ($group && array_key_exists($group, static::$publishGroups)) {
return static::$publishGroups[$group];
} elseif ($provider && array_key_exists($provider, static::$publishes)) {
return static::$publishes[$provider];
} elseif ($group || $provider) {
return [];
}
}

/**
* Get the paths for the provider and group.
*
* @param string $provider
* @param string $group
* @return array
*/
protected static function pathsForProviderAndGroup($provider, $group)
{
if (! empty(static::$publishes[$provider]) && ! empty(static::$publishGroups[$group])) {
return array_intersect_key(static::$publishes[$provider], static::$publishGroups[$group]);
}

return [];
}

/**
* Get the service providers available for publishing.
*
* @return array
*/
public static function publishableProviders()
{
return array_keys(static::$publishes);
}

/**
* Get the groups available for publishing.
*
* @return array
*/
public static function publishableGroups()
{
return array_keys(static::$publishGroups);
}

/**
* Register the package's custom Artisan commands.
*
* @param array|mixed $commands
* @return void
*/
public function commands($commands)
{
$commands = is_array($commands) ? $commands : func_get_args();

Artisan::starting(function ($artisan) use ($commands) {
$artisan->resolveCommands($commands);
});
}

/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return [];
}

/**
* Get the events that trigger this service provider to register.
*
* @return array
*/
public function when()
{
return [];
}

/**
* Determine if the provider is deferred.
*
* @return bool
*/
public function isDeferred()
{
return $this instanceof DeferrableProvider;
}
}
0%