- Created:
- Updated:
- Fairycat
Laravel服务拓展方法,无需提前解析服务
Laravel 中的各种可以拓展的服务、驱动,例如 Filesystem、 Notification 等。需要拓展的服务,在自定义的服务提供者中,使用 application 的extend
方法添加拓展就可以。
添加的拓展需要满足指定的要求,例如实现指定的接口。 Filesystem 的 disk 添加的驱动拓展是实现League\Flysystem\FilesystemInterface
接口。
拓展的开发这里不详细说明,各种服务的拓展都不一样。这里主要说明的是,拓展的添加方法。
这是官方文档的关于 filesystem 的添加拓展的方法:
public function boot() { Storage::extend('dropbox', function ($app, $config) { $client = new DropboxClient( $config['authorizationToken'] ); return new Filesystem(new DropboxAdapter($client)); }); }
有一段时间没看官方文档了,今天看到变得不一样了。题外话……
拓展方法基本都是extend
,拓展名加闭包的方式。
例子中,添加名为 dropbox 的拓展,闭包返回League\Flysystem\Filesystem
对象,对象添加DropboxAdapter
适配器。由于League\Flysystem\Filesystem
的适配器必须实现League\Flysystem\AdapterInterface
接口,所以DropboxAdapter
适配器实现的是该接口。这是关于League\Flysystem
包的内容了。有兴趣的话可以看看源码。
进入本文正题:对一个服务直接添加拓展,该服务还没使用到的时候就已经被解析了。要对一个延迟解析的服务添加拓展,这么做是不是有些欠妥。
延迟解析服务,是在用到的时候,在 application 中解析。如果一直用不到,它不会解析,只是个名称和类名称还有一些闭包而已。
如果要对延迟解析的服务添加拓展,使用上边的添加方法就尴尬了。原本要延迟解析的服务,为了添加拓展,把他解析了。它被解析了!!
Storage
相当于app('filesystem')
或者app->make('filesystem')
, application 在返回该服务之前,查看是否解析了,没有的话先解析再返回。
那么为了达到延迟解析的效果,就是把需要的拓展,传给 application,而不是直接给服务添加拓展。这样,在使用到该服务的时候, application 在解析该服务的时候,会把拓展添加上去的。
application 在解析一个服务的时候,会寻找相关该服务的闭包数组。例如afterResolvingCallbacks
、resolvingCallbacks
和extenders
。这里我们需要的是extenders
数组,可以通过 application 的extend
方法添加闭包。如果这个服务还没解析,则闭包不会运行,保存在数组中,等到这个服务被解析了,则运行该闭包。如果注册的时候,这个服务已经被解析了,则直接运行该闭包。
添加拓展方法:
$app->extend('abstract', function ($instances, $app) {
//$instances->extend('extend_name', function () {
// return new Obj();
//});
// Something else
return $instances;
});
闭包需要接受两个变量,$instance
为需要添加拓展或者需要进行别的处理的服务实例,$app
为 application 实例。
给服务实例$instance
添加拓展名为extend_name
,拓展对象Obj
。这一个步骤根据相应的服务而定。例如前边例子给Storage
也就是app('filesystem')
添加的拓展。
注意:闭包一定要有返回值,而且返回第一个参数接受的服务实例,例如上边代码中的$instances
。除非你打算用别的实例来代替这个实例,或者不返回服务实例将变成null
,而且,你要了解这么做的后果。
关于需要拓展的服务的名称,指定的是$instance
返回哪个实例。例如'filesystem'
,返回的是文件管理系统的 manager 实例,这个名称是服务进行注册的时候决定的(filesystem 之类的基础服务,名称已经写在 application 类中了)。当然也可以用类名,比如某些服务就是直接用类名当做名称的。 application 内部会自动处理该名称的。例如以下三个名称都一样。\Illuminate\Filesystem\FilesystemManager
实现\Illuminate\Contracts\Filesystem\Factory
接口,'filesystem'
是别名。
$app->extend('filesystem', function ($instances, $app) {
return $instances>extend('dropbox', function ($app, $config) {
$client = new DropboxClient(
$config['authorizationToken']
);
return new Filesystem(new DropboxAdapter($client));
});
});
//或者
$app->extend(\Illuminate\Filesystem\FilesystemManager::class, function ($instances, $app) {
return $instances->extend('dropbox', function ($app, $config) {
$client = new DropboxClient(
$config['authorizationToken']
);
return new Filesystem(new DropboxAdapter($client));
});
});
//或者
$app->extend(\Illuminate\Contracts\Filesystem\Factory::class, function ($instances, $app) {
return $instances->extend('dropbox', function ($app, $config) {
$client = new DropboxClient(
$config['authorizationToken']
);
return new Filesystem(new DropboxAdapter($client));
});
});
添加拓展应该在服务提供者的 如果是在boot
方法中添加。register
方法中添加,出现的后果,可能,这三个添加方式,只有第一个能正常添加,就是用'filesystem'
作为名称的可以添加,用类名称的就不行了。
如果服务的别名还没有定义,使用别名、类名或者接口名,都不会自动转化为统一的名称。别名定义之后,再进行添加拓展,例子中三个名称'filesystem'
、\Illuminate\Filesystem\FilesystemManager::class
和\Illuminate\Contracts\Filesystem\Factory::class
在 application 内部,会自动通过获取别名转换成'filesystem'
。
这个发生这个可能的条件,是服务的加载顺序。如果,添加拓展在别名定义之前,无法判别别名,会出现名称不对的情况,当解析该服务的时候,这个拓展是不会运行的。
考虑到如果在boot
方法中添加拓展,在别的服务提供者中的boot
方法中如果需要使用到这个拓展,因为服务提供者的顺序问题导致找不到这个拓展的情况,所以要在register
方法中添加拓展。可以理解为添加拓展也是注册的子集。为了避免出现别名出错而添加拓展的闭包没有运行,要注意使用最终别名来添加拓展,而不是使用类名或者接口。最终别名指的是进行bind
或者singleton
使用的名称。而作为框架的基本服务,例子中的 filesystem 的别名是写在 application 中的,别名早已定义完毕,所以使用哪个名称都可以。需要注意的是第三方的服务提供者。
如果添加拓展的时候,使用了错误的名称,导致的结果是这个拓展将不会运行,没有错误。到了真正使用到拓展的时候,才会出现找不到的错误。
当然最稳妥的办法,是在register
之后,在boot
之前能执行添加拓展,是最好的,因为这个时候,各个服务都已经注册完毕,相关的别名也都正常添加。那么可以在register
方法中使用 application 的booting
回调:
$app->booting(function ($app) {
$app->extend(\Illuminate\Filesystem\FilesystemManager::class, function ($instances, $app) {
return $instances->extend('dropbox', function ($app, $config) {
$client = new DropboxClient(
$config['authorizationToken']
);
return new Filesystem(new DropboxAdapter($client));
});
});
});
若估计不会在服务提供者的boot中运用到这个拓展,可以不考虑这么复杂,直接在boot中拓展即可。
延迟加载与延迟解析的区别
延迟解析是没有实例化这个服务对象。只有通过 application 调用时,才实例化,但它已经被注册了,以闭包的形式。当需要实例化, application 会寻找别名还有绑定之类的,才确定需要实例化的对象。
延迟加载是服务提供者的register
和boot
两个方法都没有运行, application 只是记录了名字,还有对应的服务提供者,在deferredServices
数组中。