Laravel Service Provider 概念詳解

Ίκαρος發表於2017-09-22

我們知道, Container 有很多種 「繫結」 的姿勢,比如 bind() , extend() , singleton() , instance() 等等,那麼 Laravel 中怎樣「註冊」這些「繫結」呢?那就是 Service Provider

先看下 Laravel 文件中這句話:

Service providers are the central place of all Laravel application bootstrapping. Your own application, as well as all of Laravel's core services are bootstrapped via service providers.

Service Providers (服務提供者)Laravel 「引導」過程的核心。這個「引導」過程可以理解成「電腦從按下開機按鈕到完全進入桌面」這段時間系統乾的事。

概覽

Service Provider 有兩個重要的方法:

namespace App\Providers;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * 註冊服務.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * 引導服務。
     *
     * @return void
     */
    public function boot()
    {
        //
    }
}

Laravel 在「引導」過程中幹了兩件重要的事:

  1. 透過 Service Providerregister() 方法註冊「繫結」
  2. 所有 Servier Providerregister() 都執行完之後,再透過它們 boot() 方法,幹一些別的事。

過程分析

這個「先後順序」可以在 Laravel 的啟動過程中找到:

  1. 首先,生成核心 Container : $app (例項化過程中還註冊了一大堆基本的「繫結])。
    public/index.php
    /*
    |--------------------------------------------------------------------------
    | Turn On The Lights
    |--------------------------------------------------------------------------
    |
    */
    $app = require_once __DIR__.'/../bootstrap/app.php';
// bootstrap/app.php
$app = new Illuminate\Foundation\Application(
    realpath(__DIR__.'/../')
);
  1. 接下來註冊 Http\Kernel , Console\Kernel , Debug\ExecptionHandler 三個「單例」繫結。
    bootstrap/app.php

    $app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
    );
    $app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    App\Console\Kernel::class
    );
    $app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
    );
  2. 然後「啟動」應用。
    public/index.php

    /*
    |--------------------------------------------------------------------------
    | Run The Application
    |--------------------------------------------------------------------------
    |
    */
    $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
    $response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
    );
  3. 由於以前的「繫結」,$kernel 獲取的其實是 App\Http\Kernel 類的例項,App\Http\Kernel 類又繼承了 Illuminate\Foundation\Http\Kernel 類。
    handle() 方法執行了 sendRequestThroughRouter() 方法:

// Illuminate\Foundation\Http
class Kernel implements KernelContract
{
    public function handle($request)
        try {
            //...
            $response = $this->sendRequestThroughRouter($request);

        } catch (Exception $e) {
            //...
        }
    }
}
  1. 這個 sendRequestThroughRouter() 方法執行了一系列 bootstrappers (引導器)

    // Illuminate\Foundation\Http
    class Kernel implements KernelContract
    {
    protected function sendRequestThroughRouter($request)
    {
        //...
    
        // 按順序執行每個 bootstrapper
        $this->bootstrap();
    
        //...
    }
    }
  2. bootstrap() 方法中又呼叫了 Illuminate\Foundation\Application 類的 bootstrapWith() 方法:

// Illuminate\Foundation\Http
class Kernel implements KernelContract
{
    public function bootstrap()
    {
        if (! $this->app->hasBeenBootstrapped()) {
            //
            $this->app->bootstrapWith($this->bootstrappers());
        }
    }
}
// Illuminate\Foundation\Http\Kernel
class Kernel implements KernelContract
{
    protected $bootstrappers = [
        //...

        \Illuminate\Foundation\Bootstrap\RegisterProviders::class,  // 註冊 Providers
        \Illuminate\Foundation\Bootstrap\BootProviders::class,  // 引導 Providers
    ];

    protected function bootstrappers()
    {
        return $this->bootstrappers;
    }
}

這裡就能看出來 Service Providerregisterboot 的「先後順序了」。

後續的執行過程暫時先不介紹了。

這就意味著,在 Service Provider boot 之前,已經把註冊好了所有服務的「繫結」。因此, 在 boot() 方法中可以使用任何已註冊的服務。
例如:

{
namespace App\Providers;
use Illuminate\Support\ServiceProvider;

class ComposerServiceProvider extends ServiceProvider
{
    public function boot()
    {
        // 這裡使用 make() 方法,可以更直觀
        $this->app->make('view')->composer('view', function () {
            //
        });

        // 或者使用 view() 輔助函式
        view()->composer('view', function () {
            //
        });
    }
}

如果瞭解 Container 「繫結」 和 make 的概念,應該很容易理解上面的過程。

剩餘的無非就是:

建立 Service Provier

php artisan make:provider MyServiceProvider

註冊繫結

namespace App\Providers;
use Illuminate\Support\ServiceProvider;

class MyServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->bind(MyInterface::class, MyClass::class);
    }
}

引導方法

namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Contracts\Routing\ResponseFactory;

class MyServiceProvider extends ServiceProvider
{

    /**
    * boot() 方法中可以使用依賴注入。
    * 這是因為在 Illuminate\Foundation\Application 類中,
    * 透過 bootProvider() 方法中的 $this->call([$provider, 'boot']) 
    * 來執行 Service Provider 的 boot() 方法
    * Container 的 call() 方法作用,可以參考上一篇文章
    */
    public function boot(ResponseFactory $response)
    {
        $response->macro('caps', function ($value) {
            //
        });
    }
}

config/app.php 中註冊 Service Provider

'providers' => [

        /*
         * Laravel Framework Service Providers...
         */

        /*
         * Package Service Providers...
         */

        /*
         * Application Service Providers...
         */
        App\Providers\MyServiceProvider::class,
],

注意:Laravel 5.5 之後有 package discovery 功能的 package 不需要在 config/app.php 中註冊。

延時載入

按需載入,只有當 Container 「make」 ServiceProviderproviders()方法中返回的值時,才會載入此 ServiceProvider
例如:

namespace Illuminate\Hashing;

use Illuminate\Support\ServiceProvider;

class HashServiceProvider extends ServiceProvider
{
    /**
     * 如果延時載入,$defer 必須設定為 true 。
     *
     * @var bool
     */
    protected $defer = true;

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton('hash', function () {
            return new BcryptHasher;
        });
    }

    /**
     * Get the services provided by the provider.
     *
     * @return array
     */
    public function provides()
    {
        return ['hash'];
    }
}

當我們「make」時,才會註冊 HashServiceProvider,即執行它的 register() 方法,進行 hash 的繫結:

class MyController extends Controller
{
    public function test()
    {
        // 此時才會註冊 `HashServiceProvider`
        $hash = $this->app->make('hash');

        $hash->make('teststring');

        // 或
        \Hash::make('teststring');
    }
}

以上就是 Laravel Service Provider 概念的簡單介紹,更深入的瞭解可以看看「點燈坊」的這篇文章:
http://oomusou.io/laravel/laravel-service-...

本作品採用《CC 協議》,轉載必須註明作者和本文連結
原創。 所有 Laravel 文章均已收錄至 Github laravel-tips 專案。

相關文章