Laravel 2.3 Service Provider

myjob發表於2019-05-31

我們知道, 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 Provider 的 register() 方法註冊「繫結」
  2. 所有 Servier Provider 的 register() 都執行完之後,再通過它們 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 Provider 中 register 和 boot 的「先後順序了」。

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

這就意味著,在 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」 ServiceProvider 類 providers() 方法中返回的值時,才會載入此 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');
    }
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章