fairycat

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 在解析一个服务的时候,会寻找相关该服务的闭包数组。例如afterResolvingCallbacksresolvingCallbacksextenders。这里我们需要的是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 会寻找别名还有绑定之类的,才确定需要实例化的对象。

延迟加载是服务提供者的registerboot两个方法都没有运行, application 只是记录了名字,还有对应的服务提供者,在deferredServices数组中。

评论

Name

Email

Website

Subject