Laravel 服务提供者指南

这是一篇翻译文章,译文首发于 Laravel 服务提供者指南,转载请注明出处。

如果你使用过 Laravel 框架的话,那么,你不可能没听说过服务容器和服务提供者。事实上,它们是 Lavavel 框架核心,它们完成 Larvel 应用中服务启动的艰巨任务。

在这篇文章中,我们将简单介绍「服务容器」,同时还会深入讲解服务提供者。本教程还将演示如何在 Laravel 中创建一个自定义的服务提供者。另外,如果你需要在 Laravel 中成功使用服务容器,还需要注册它。那么,让我们开始吧。

实现一个自定义的服务提供者,需要实现两个非常重要的方法:bootregister 方法。关于这两个方法将在教程最后一个小节讨论。

在学习服务提供者之前,简单介绍一下服务容器,服务容器会在服务提供者中被经常使用。

理解服务容器和服务提供者

什么是服务容器

简而言之,Laravel 服务容器 是一个用于存储绑定组件的盒子,它还会为应用提供所需的服务。

Laravel 文档中描述如下:

Laravel 服务容器是用于管理类的依赖和执行依赖注入的工具 - Laravel 文档

这样,当我们需要注入一个内置的组件或服务时,可以在构造函数或方法中使用类型提示功能注入,然后在使用时从服务容器中自动解析出所需实例及其依赖!是不是很酷?这个功能可以让我们从手动管理组件中解脱出来,从而降低系统耦合度。

让我们看一个简单实例来加深理解。

<?php

Class SomeClass
{
    public function __construct(FooBar $foobarObject)
    {
        // use $foobarObject object
    }
}

如你所见,SomeClass 需要使用 FooBar 实例。换句话说它需要依赖其它组件。Laravel 实现自动注入需要从服务容器中查找并执行注入适当的依赖。

如果你希望了解 Laravel 是如何知道需要将哪个组件或服务绑定到服务容器中的,答案是通过服务提供者实现的。服务提供者完成将组件绑定到服务容器的工作。在服务提供者内部,这个工作被称之为服务容器绑定,绑定处理由服务提供者完成。

服务提供者实现了服务绑定,绑定处理则由 register 方法完成。

同时,这又会引入一个新的问题:Laravel 是如何知道有哪些服务提供者的呢?这个我们貌似还没有讨论到吧?我到时看到,之前有说 Laravel 会自动的去查找到服务!朋友,你的问题太多了:Laravel 只是一个框架,它不是一个超级英雄,不是么?我们当然需要去明确的告知 Laravel 框架我们有哪些服务提供者。

让我们来瞧瞧 config/app.php 配置文件。你会找到一个用于 Laravel 应用启动过程中被载入的服务提供者配置列表。

'providers' => [

        /*
         * Laravel Framework Service Providers...
         */
        Illuminate\Auth\AuthServiceProvider::class,
        Illuminate\Broadcasting\BroadcastServiceProvider::class,
        Illuminate\Bus\BusServiceProvider::class,
        Illuminate\Cache\CacheServiceProvider::class,
        Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
        Illuminate\Cookie\CookieServiceProvider::class,
        Illuminate\Database\DatabaseServiceProvider::class,
        Illuminate\Encryption\EncryptionServiceProvider::class,
        Illuminate\Filesystem\FilesystemServiceProvider::class,
        Illuminate\Foundation\Providers\FoundationServiceProvider::class,
        Illuminate\Hashing\HashServiceProvider::class,
        Illuminate\Mail\MailServiceProvider::class,
        Illuminate\Notifications\NotificationServiceProvider::class,
        Illuminate\Pagination\PaginationServiceProvider::class,
        Illuminate\Pipeline\PipelineServiceProvider::class,
        Illuminate\Queue\QueueServiceProvider::class,
        Illuminate\Redis\RedisServiceProvider::class,
        Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
        Illuminate\Session\SessionServiceProvider::class,
        Illuminate\Translation\TranslationServiceProvider::class,
        Illuminate\Validation\ValidationServiceProvider::class,
        Illuminate\View\ViewServiceProvider::class,

        /*
         * Package Service Providers...
         */
        Laravel\Tinker\TinkerServiceProvider::class,

        /*
         * Application Service Providers...
         */
        App\Providers\AppServiceProvider::class,
        App\Providers\AuthServiceProvider::class,
        // App\Providers\BroadcastServiceProvider::class,
        App\Providers\EventServiceProvider::class,
        App\Providers\RouteServiceProvider::class,
],

以上就是有关服务容器的基本概念。下一节,我们将焦点聚集到服务提供者这个核心主题上!

什么是服务提供者

如果说服务容器是提供绑定和依赖注入的的工具,那么 服务提供者 则是实现绑定的工具。

让我们先来看一个内容提供的服务提供者服务来理解它的运行原理。打开 vender/laravel/framework/src/Illuminate/Cache/CacheServiceProvider.php 文件。

public function register()
{
    $this->app->singleton('cache', function ($app) {
        return new CacheManager($app);
    });

    $this->app->singleton('cache.store', function ($app) {
        return $app['cache']->driver();
    });

    $this->app->singleton('memcached.connector', function () {
        return new MemcachedConnector;
    });
}

这里我们需要将重点集中在 register 方法中,这个方法用于绑定服务到服务容器。如你所见,这里一共执行了三个服务的绑定处理:cachecache.storememcached.connector

然后,当我们需要在 Laravel 中使用 cache 服务时,服务容器会解析出 CacheManager 实例并返回。也就是说我们仅仅是提供了一个可以从 $this->app 访问的对应关系表。

通过服务提供者绑定服务是 Laravel 服务容器绑定服务的正确打开方式。同时通过服务提供者的 register 方法,还有利于理解 Laravel 服务容器是如何管理所有的服务的。我们之前提到过,通过从 config/app.php 配置文件中读取服务提供者配置列表,从将所有服务注册服务容器中。

以上,就是服务提供者和它的故事。下一节,我们会学习如何创建一个服务提供者来实现将自己的服务注册到 Laravel 服务容器。

自定义服务提供者

Laravel 已经内置了一个用于创建服务提供者的 artisan 命令来简化创建流程。进入命令行模式后执行下面命令来创建服务提供者。

php artisan make:provider EnvatoCustomServiceProvider

运行后会在 app/Providers 目录下创建 EnvatoCustomServiceProvider.php 文件。打开该文件看下它的源码。

<?php
namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class EnvatoCustomServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }

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

之前我们有提到服务提供者有两个重要方法:bootregister 方法,在实现自定义服务提供者时大部分都是在处理这两个方法。

register 方法用于执行服务绑定处理。另外在 boot 方法中可以使用所有已绑定的服务。在这个教程的最后一节我们将学习更多有关这两个方法的细节,但在这里我们会先了解些这两个方法的使用示例加深理解。

注册自定义服务提供者

前面我们创建了一个自定义的服务提供者。接下来需要让 Laravel 知道如何让这个服务提供者同其它服务提供者一样在应用启动时被加载到 Laravel 中。

为了完成注册服务提供者的功能,仅需要将类名加入到 config/app.php 配置文件的 providers 节点。

'providers' => [

        /*
         * Laravel Framework Service Providers...
         */
        Illuminate\Auth\AuthServiceProvider::class,
        Illuminate\Broadcasting\BroadcastServiceProvider::class,
        Illuminate\Bus\BusServiceProvider::class,
        Illuminate\Cache\CacheServiceProvider::class,
        Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
        Illuminate\Cookie\CookieServiceProvider::class,
        Illuminate\Database\DatabaseServiceProvider::class,
        Illuminate\Encryption\EncryptionServiceProvider::class,
        Illuminate\Filesystem\FilesystemServiceProvider::class,
        Illuminate\Foundation\Providers\FoundationServiceProvider::class,
        Illuminate\Hashing\HashServiceProvider::class,
        Illuminate\Mail\MailServiceProvider::class,
        Illuminate\Notifications\NotificationServiceProvider::class,
        Illuminate\Pagination\PaginationServiceProvider::class,
        Illuminate\Pipeline\PipelineServiceProvider::class,
        Illuminate\Queue\QueueServiceProvider::class,
        Illuminate\Redis\RedisServiceProvider::class,
        Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
        Illuminate\Session\SessionServiceProvider::class,
        Illuminate\Translation\TranslationServiceProvider::class,
        Illuminate\Validation\ValidationServiceProvider::class,
        Illuminate\View\ViewServiceProvider::class,

        /*
         * Package Service Providers...
         */
        Laravel\Tinker\TinkerServiceProvider::class,

        /*
         * Application Service Providers...
         */
        App\Providers\AppServiceProvider::class,
        App\Providers\AuthServiceProvider::class,
        // App\Providers\BroadcastServiceProvider::class,
        App\Providers\EventServiceProvider::class,
        App\Providers\RouteServiceProvider::class,
        App\Providers\EnvatoCustomServiceProvider::class,
],

就是如此简单,现在你已经将自定义服务提供者注册到了 Laravel 中。只不过现在这个服务提供者还几乎什么都没有处理。下一节,我们将以实例演示如何使用 registerboot 方法。

深入讲解 register 和 boot 方法

起先,我们来深入研究 register 方法加深你对这个方法的理解。打开之前创建的 app/Providers/EnvatoCustomServiceProvider.php 文件,加入如下代码。

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Library\Services\DemoOne;

class EnvatoCustomServiceProvider extends ServiceProvider
{
    public function boot()
    {
    }

    public function register()
    {
        $this->app->bind('App\Library\Services\DemoOne', function ($app) {
            return new DemoOne();
        });
    }
}

这里我们做了两个处理:

  • 引入需要使用的 App\Library\Services\DemoOne 服务。DemoOne 类现在还没有创建,但之后会创建这个类。
  • register 方法中,我们使用服务容器的 bind 方法将服务绑定到容器。这样,当需要使用 App\Library\Services\DemoOne 服务而被解析时,就回调用闭包方法,创建实例并返回 App\Library\Services\DemoOne 对象。

现在创建 app/Library/Services/DemoOne.php 文件。

<?php
namespace App\Library\Services;

class DemoOne
{
    public function doSomethingUseful()
    {
      return 'Output from DemoOne';
    }
}

然后,在控制器的构造函数中注入依赖。

<?php
namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Library\Services\DemoOne;

class TestController extends Controller
{
    public function index(DemoOne $customServiceInstance)
    {
        echo $customServiceInstance->doSomethingUseful();
    }
}

以上便是一个使用绑定的简单方法。事实上,对于这个示例其实并不需要创建一个服务提供者,并实现 register 方法,因为 Laravel 还可以通过 PHP 的方式功能自动解析。

Laravel 文档中对此有一个说明:

如果我们的依赖无需任何接口,则无需将类绑定到容器。容器此时不需要了解创建对象的具体细节,而可以通过反射功能实现自动注入。

换句话说,如果我们需要绑定的服务依赖于其它接口,创建服务提供者则很有必要。接着来看一个实例以加深理解。

首先,创建一个简单的接口 app/Library/Services/Contracts/CustomServiceInterface.php

<?php
// app/Library/Services/Contracts/CustomServiceInterface.php
namespace App\Library\Services\Contracts;

Interface CustomServiceInterface
{
    public function doSomethingUseful();
}

然后,创建两个基于此接口的具体实现。或者说,创建两个继承此接口的实现类。

一个是定义在 app/Library/Services/DemoOne.php 文件中的 DemoOne 类。

<?php
// app/Library/Services/DemoOne.php
namespace App\Library\Services;

use App\Library\Services\Contracts\CustomServiceInterface;

class DemoOne implements CustomServiceInterface
{
    public function doSomethingUseful()
    {
      return 'Output from DemoOne';
    }
}

类似的,还有 app/Library/Services/DemoTwo.php

<?php
// app/Library/Services/DemoTwo.php
namespace App\Library\Services;

use App\Library\Services\Contracts\CustomServiceInterface;

class DemoTwo implements CustomServiceInterface
{
    public function doSomethingUseful()
    {
      return 'Output from DemoTwo';
    }
}

现在,将绑定具体类名修改为绑定接口。打开 EnvatoCustomServiceProvider.php 文件并改成如何代码。

<?php
namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Library\Services\DemoOne;

class EnvatoCustomServiceProvider extends ServiceProvider
{
    public function boot()
    {
    }

    public function register()
    {
        $this->app->bind('App\Library\Services\Contracts\CustomServiceInterface', function ($app) {
          return new DemoOne();
        });
    }
}

这里,我们将 DemoOne 实现类绑定到 App\Library\Services\Contracts\CustomServiceInterface 接口。后续,所有依赖 App\Library\Services\Contracts\CustomServiceInterface 接口的功能都被解析成 App\Library\Services\DemoOne 对象。 这个示例是不是更有实际意义呢?

当然,我们还需要调整下控制器中的代码。

<?php
namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Library\Services\Contracts\CustomServiceInterface;

class TestController extends Controller
{
    public function index(CustomServiceInterface $customServiceInstance)
    {
        echo $customServiceInstance->doSomethingUseful();
    }
}

或许你已经猜到 $customServiceInstance 对象是 App\Library\Services\DemoOne 类的实例!这种方案的优势在于可以很容易的替换掉 DemoOne 这个实现。

假如你想使用 DemoTwo 替换掉 DemoOne 服务。此时,仅需简单的调整下服务提供者中的代码 EnvatoCustomServiceProvider.php

将:

use App\Library\Services\DemoOne;

替换成:

use App\Library\Services\DemoTwo;

然后替换:

return new DemoOne();

到:

return new DemoTwo();

使用同样的手法甚至可以将自定义的实现替换掉任何核心服务中的依赖。不仅如此,除了 bind 方法;Laravel 服务容器还提供多种绑定方法。可以查看 Laravel 服务容器 文档了解更多。

下一个主题是可以扩展 Laravel 核心服务的 boot 方法。在这个方法中,你可以获取所有通过服务提供者注册到容器中的服务。通常,你会在这个方法中注册某些功能完成后需要触发其它操作的事件监听器。

依照惯例看几个示例先。

创建一个用于 Laravel 校验的自定义表单验证器。

public function boot()
{
    Validator::extend('my_custom_validator', function ($attribute, $value, $parameters, $validator) {
        // validation logic goes here...
    });
}

也许你想创建一个 view composer。在 boot 方法中创建是个不错的选择。

public function boot()
{
    View::composer(
        'demo', 'App\Http\ViewComposers\DemoComposer'
    );
}

当然在这里需要率先导入 Illuminate\Support\Facades\View

有时,我们还需要创建一些共享数据。

public function boot()
{
    View::share('key', 'value');
}

甚至可以显示的创建模型绑定。

public function boot()
{
    parent::boot();

    Route::model('user', App\User::class);
}

这些示例演示了 boot 方法的一些用法。只有更深入的理解,才能掌握它的使用方法!

与此同时,我们需要说再见了。我希望你喜欢本文所讨论的主题。

结论

本文讨论的是服务提供者,这是本文的中心思想,尽管我们是以服务容器作为开篇,因为它是理解服务提供者的重要组成部分。

随后,我们创建了一个自定义服务提供者,并且在本文的后半部分中,我们介绍了几个实际的示例。

原文: How to Register & Use Laravel Service Providers

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 5年前 自动加精
liuqing_hu
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 13

我开始在想,在controller中直接use一个Service类也可以实现依赖注入,还用ServiceProvider去绑定到容器是不是多余的,看了你举的DemoOne和DemoTwo的例子解答了我这个疑惑。

5年前 评论
Aaron

nice

5年前 评论
liuqing_hu

@江渚之上 感谢肯定

5年前 评论

我开始在想,在controller中直接use一个Service类也可以实现依赖注入,还用ServiceProvider去绑定到容器是不是多余的,看了你举的DemoOne和DemoTwo的例子解答了我这个疑惑。

5年前 评论

明白了,服务提供者的作用是将服务注册到服务容器,这样做有两个好处
1、如果某个服务依赖于另外的服务,我们可以在服务容器中注册依赖服务
2、可以绑定接口到实现,这样可以很容易的更换实现。

4年前 评论

file

这个地方是不是写错了呢?

4年前 评论

我终于明白ioc的作用了! 太感谢了!

4年前 评论
_null_ 4年前

怎么手动注册延迟服务提供者,下面的写法只能注册实时生效的服务提供者。

$this->app->register(EasySmsServiceProvider::class);
2年前 评论

那么我能不能把DemoOne和DemoTwo都绑定进去呢,如果都绑定进去应该如何使用呢?

2年前 评论
liuqing_hu (楼主) 1年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!