Laravel 服務提供者指南

這是一篇翻譯文章,譯文首發於 Laravel 服務提供者指南,轉載請註明出處。

若是你使用過 Laravel 框架的話,那麼,你不可能沒據說過服務容器和服務提供者。事實上,它們是 Lavavel 框架核心,它們完成 Larvel 應用中服務啓動的艱鉅任務。php

在這篇文章中,咱們將簡單介紹「服務容器」,同時還會深刻講解服務提供者。本教程還將演示如何在 Laravel 中建立一個自定義的服務提供者。另外,若是你須要在 Laravel 中成功使用服務容器,還須要註冊它。那麼,讓咱們開始吧。laravel

實現一個自定義的服務提供者,須要實現兩個很是重要的方法:bootregister 方法。關於這兩個方法將在教程最後一個小節討論。shell

在學習服務提供者以前,簡單介紹一下服務容器,服務容器會在服務提供者中被常用。閉包

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

什麼是服務容器

簡而言之,Laravel 服務容器 是一個用於存儲綁定組件的盒子,它還會爲應用提供所需的服務。app

Laravel 文檔中描述以下:composer

Laravel 服務容器是用於管理類的依賴和執行依賴注入的工具 - Laravel 文檔

這樣,當咱們須要注入一個內置的組件或服務時,能夠在構造函數或方法中使用類型提示功能注入,而後在使用時從服務容器中自動解析出所需實例及其依賴!是否是很酷?這個功能可讓咱們從手動管理組件中解脫出來,從而下降系統耦合度。框架

讓咱們看一個簡單實例來加深理解。ide

<?php

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

如你所見,SomeClass 須要使用 FooBar 實例。換句話說它須要依賴其它組件。Laravel 實現自動注入須要從服務容器中查找並執行注入適當的依賴。memcached

若是你但願瞭解 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();
        });
    }
}

這裏咱們作了兩個處理:

  • 引入須要使用的 AppLibraryServicesDemoOne 服務。DemoOne 類如今尚未建立,但以後會建立這個類。
  • register 方法中,咱們使用服務容器的 bind 方法將服務綁定到容器。這樣,當須要使用 AppLibraryServicesDemoOne 服務而被解析時,就回調用閉包方法,建立實例並返回 AppLibraryServicesDemoOne 對象。

如今建立 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 實現類綁定到 AppLibraryServicesContractsCustomServiceInterface 接口。後續,全部依賴 AppLibraryServicesContractsCustomServiceInterface 接口的功能都被解析成 AppLibraryServicesDemoOne 對象。 這個示例是否是更有實際意義呢?

固然,咱們還須要調整下控制器中的代碼。

<?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 對象是 AppLibraryServicesDemoOne 類的實例!這種方案的優點在於能夠很容易的替換掉 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'
    );
}

固然在這裏須要率先導入 IlluminateSupportFacadesView

有時,咱們還須要建立一些共享數據。

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

相關文章
相關標籤/搜索