[ Laravel 从学徒到工匠系列 ] 服务提供者篇

作为引导者

Laravel 服务提供者主要用来进行注册服务容器绑定(即注册接口及其实现类的绑定)。事实上,Laravel 有好几十个服务提供者,用于管理框架核心组件的容器绑定。几乎框架里每一个组件的容器绑定都是靠服务提供者来完成的。你可以在 config/app.php 这个配置文件里查看项目目前有哪些服务提供者(从 Laravel 5.5 开始,Laravel 提供了包自动发现功能,所以这里也不一定全了)。

一个服务提供者必须至少有一个 register 方法。你可以在这个方法里将类绑定到容器,就像我们前面做的那样。当一个请求进入应用,框架启动时,所有罗列在配置文件里的服务提供者的 register 方法就会被调用。这在应用请求生命周期很早的阶段就会发生,所以在我们编写业务逻辑代码时,所有的服务都已经准备好了。

register Vs. boot 方法:永远不要在 register 方法里面使用任何服务。该方法只是用来将对象绑定到服务容器的地方。所有关于绑定类的解析、交互都要在 boot 方法(服务提供者的另一个方法)里进行。

一些通过 Composer 安装的第三方扩展包也会有服务提供者。这些第三方扩展包的安装说明里一般都会告诉你要在配置文件 config/app.phpproviders 数组里注册其提供的服务提供者(如果支持包自动发现,则不必这么做)。只有注册了对应的服务提供者,才能使用扩展包提供的服务。

包提供者:不是所有的第三方扩展包都需要服务提供者。实际上,任何扩展包都不需要服务提供者,因为服务提供者只是启动服务组件并让它们可以立即使用,它们只是一个用来组织启动代码和容器绑定的地方。

延迟加载的服务提供者

不是每一个罗列在配置文件 config/app.phpproviders 数组里的服务提供者在每次请求时都需要被实例化。这会对性能有影响,尤其是服务提供者注册的服务在这个请求中根本用不到的情况下。例如,QueueServiceProvider 注册的服务就不是每次请求都用得到,只有在请求用到队列时才会用到。

在 Laravel 4 中,延迟服务提供者加载是通过存放在 app/storage/meta 目录下的「服务清单」来实现的,清单中罗列了应用的所有服务提供者及其注册到容器中的名称。当需要获取 queue 容器绑定时,就会实例化并运行 QueueServiceProvider

在 Laravel 5 中,我们通过一种新的方式来实现延迟加载服务提供者,在需要延迟加载的服务提供者中将属性 $defer 设置为 true,并重写 providers 方法即可,在这个方法中,我们会以数组方式返回该服务提供者注册的服务容器绑定:

<?php

namespace App\Providers;

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

class RiakServiceProvider extends ServiceProvider{
    /**
     * 服务提供者加是否延迟加载.
     *
     * @var bool
     */
    protected $defer = true;

    /**
     * 注册服务提供者
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton(Connection::class, function ($app) {
            return new Connection($app['config']['riak']);
        });
    }

    /**
     * 获取由提供者提供的服务.
     *
     * @return array
     */
    public function provides()
    {
        return [Connection::class];
    }

}

作为管理者

构建一个架构良好的 Laravel 应用的关键就是学习使用服务提供者作为管理工具。

我们先来看个例子吧。也许我们的应用正在使用 Pusher 通过 WebSocket 推送消息给客户端。为了将我们的应用和 Pusher 解耦,最好创建一个新的 EventPusherInterface 接口和对应的实现类 PusherEventPusher,这样随着需求变化或应用增长,我们就可以随时轻松切换 WebSocket 提供商:

interface EventPusherInterface
{
    public function push($message, array $data = array());
}

<?php
namespace App\Services;

use App\Contracts\EventPusherInterface;
use App\Contracts\PusherSdkInterface;

class PusherEventPusher implements EventPusherInterface
{
    public function __construct(PusherSdkInterface $pusher)
    {
        $this->pusher = $pusher;
    }

    public function push($message, array $data = array())
    {
        // 通过 Pusher SDK 推送消息
    }
}

接下来,我们创建一个服务提供者 EventPusherServiceProvider

<?php

namespace App\Providers;

use App\Contracts\EventPusherInterface;
use App\Contracts\PusherSdkInterface;
use App\Services\PusherEventPusher;
use Illuminate\Support\ServiceProvider;
use Pusher\Pusher;

class EventPusherServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->singleton(PusherSdkInterface::class, function () {
            return new Pusher('app-key', 'secret-key', 'app-id');
        });

        $this->app->singleton(EventPusherInterface::class, PusherEventPusher::class);
    }
}

很好!现在我们对事件推送进行了清晰的抽象,同时也有了一个很方便的地方注册和绑定其他相关对象到容器里。最后,只需要将 EventPusherServiceProvider 注册到 config/app.php 配置文件的providers 数组就可以了。现在我们就可以将 EventPusherInterface 注入到应用代码里的任何控制器或类中。

在绑定接口实现类时使用 bind 还是 singleton 方法可以这样来考虑:如果在一次请求生命周期中该类只需要有一个实例,就使用 singleton;否则就使用 bind

我们在容器章节里面已经提到过,继承自 Illuminate\Support\ServiceProvider 的服务提供者都有一个 $app 属性,该属性是一个继承自 Container 类的完整 Illuminate\Foundation\Application 实例。因此,我们可以通过这个 $app 属性调用服务容器中的所有方法,如果你更喜欢用 App 门面,也可以这么做实现同样的功能:

App::singleton(EventPusherInterface::class, PusherEventPusher::class);

当然,服务提供者的功能不仅仅局限于注册特定类型的服务。我们还可以使用它们注册云存储服务、数据库访问服务、自定义的视图引擎如 Twig 等等。服务提供者只是应用程序的引导和管理工具,没什么其他的。

所以,尽情的去创建你自己的服务提供者吧。并不是非要等到发布个什么扩展包才需要服务提供者,对 Laravel 应用而言,它们只是非常好的代码管理工具而已。你要学会善用它们去引导和管理应用中的各个组件,以便组件之间可以相互组合,协同工作。

启动提供者

在所有服务提供者都注册以后(register 方法调用完),它们就进入了「启动」状态。这将会触发每个服务提供者执行各自的 boot 方法。在使用服务提供者时,一种常见的错误就是在register 方法里面调用其他提供者注册的服务。由于在某个服务提供者的 register 方法里,不能保证所有其他服务都已经被注册,在该方法里调用别的服务有可能会出现该服务不可用。因此,调用其它服务的代码应该被定义在服务提供者的 boot 方法中。register 方法只能用于注册服务到容器。

boot 方法中,你想做什么都可以:注册事件监听器、引入路由文件、注册过滤器、或者任何其他你能想到的事。再次强调,使用服务提供者作为管理工具的时候,如果你想将几个相关的事件监听器聚合到一起,就将它们放到该服务提供者的 boot 方法里。

现在,我们已经学习了依赖注入,以及如何通过服务提供者来组织管理我们的项目。此时此刻,我们已经为构建可维护、可测试、架构良好的 Laravel 应用打下了一个很好的基础。接下来,我们将探索 Laravel 框架本身是如何使用服务提供者的,并且深究其原理!

不要被条条框框束缚。记住,服务提供者并不只是扩展包才能使用。请尽情使用它来组织管理你的应用服务。

框架核心

至此,你可能已经注意到,在 config/app.php 配置文件里面已经有了很多服务提供者,其中每一个都负责引导框架核心的一部分服务。比如MigrationServiceProvider 负责引导用于运行数据库迁移的类,包括Artisan 迁移命令。EventServiceProvide 负责引导和注册事件调度类。尽管不同的服务提供者有着不同的复杂度,有些比较大,另一些相对较小,但它们都负责引导核心的一部分功能。

提升对 Laravel 核心代码理解的最好方法是去读核心服务提供者的源码。如果你对这些服务提供者的功能以及每个服务提供者注册了什么都很熟悉,那么你将会对Laravel 底层是如何工作的有更加深刻的理解。

大部分核心服务提供者是延迟加载的,这意味着不是每次请求都会加载它们;不过,一些用于引导框架基础服务的服务提供者是每一次请求都会被加载的,比如 FilesystemServiceProvideExceptionServiceProvider。核心服务提供者和应用容器将框架的不同部分联系起来,形成一个单一的、内聚的整体。这些核心服务提供者就是框架的构建块。

正如之前提到的那样,如果你想深入理解框架是如何运行的,请阅读 Laravel 框架的核心服务提供者的源码。通读之后,你将会对框架如何把各部分功能模块组合在一起,以及每一个服务提供者为应用提供了哪些功能有更加扎实的理解。此外,有了这些更深入的理解,你也可以为更好的 Laravel 生态系统添砖加瓦!

学院君 has written 1037 articles

Laravel学院院长,终身学习者

积分:124957 等级:P12 职业:手艺人 城市:杭州

1 条回复

登录后才能进行评论,立即登录?