「PHPフレームワーク Laravel Webアプリケーション開発」の感想・備忘録3

スポンサーリンク
「PHPフレームワーク Laravel Webアプリケーション開発」の感想・備忘録2の続き

サービスプロバイダ

サービスプロバイダとは

Laravelのライフサイクルで、ビジネスロジックが実行される前に実行したい処理を定義するためのもの。
主に以下の3つの役割があるが、一般的にはサービスコンテナへバインドを行うために使われる。

  1. サービスコンテナへのバインド
  2. イベントリスナー、ミドルウェア、ルーティングの登録
  3. 外部コンポーネントの組み込み

ミドルウェアなど、開発者が処理を差し込めるタイミングがいくつかあるが、サービスプロバイダは初期処理に処理を差し込むために用意されている。

サービスプロバイダの動作

読み込むサービスプロバイダはconfig/app.phpのproviders配列に定義する。

'providers' => [
    Illuminate\Auth\AuthServiceProvider::class,
    Illuminate\Broadcasting\BroadcastServiceProvider::class,
    Illuminate\Bus\BusServiceProvider::class,
    // 以下省略 
  1. Laravelの初期処理で、各サービスプロバイダのregisterメソッドが実行される。
  2. 全てのregisterメソッドの処理が終わると、各サービスプロバイダのbootメソッドが実行される。

サービスプロバイダは/Illuminate/Support/ServiceProviderを継承して実装する。
registerメソッドは必ず実装する必要があり、サービスコンテナへのバインド以外の処理を実装してはいけない。
(registerメソッドが実行されるタイミングでは、サービスコンテナから他のインスタンスを取得することができないため)
bootメソッドの実装は任意であり、バインド以外の処理はbootメソッドに実装する。

  • 親クラスServiceProviderはappプロパティにサービスコンテナインスタンスを保持しているため、$this->appで利用できる。
  • deferプロパティをtrueにするとregisterメソッドの実行を遅らせることができる。providesメソッドまたはwhenメソッドを定義する必要がある。使うことはあまりないと思う。

コントラクト

コントラクトとは

Laravelのコアコンポーネント(Illuminate\Encryption\Encrypterなど)で利用されている関数をインターフェースとして定義したもの。
例えばIlluminate\Encryption\Encrypterは以下のようになっている。

namespace Illuminate\Encryption;

use RuntimeException;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Contracts\Encryption\EncryptException;
use Illuminate\Contracts\Encryption\Encrypter as EncrypterContract;

class Encrypter implements EncrypterContract
{

フレームワーク内でこの暗号化機能を利用している以下のクラスは、コンストラクタのタイプヒンティングで具象クラス(Illuminate\Encryption\Encrypter)ではなくインターフェース(Illuminate\Contracts\Encryption\Encrypter)を指定している。

  • Illuminate/Foundation/Http/Middleware/VerifyCsrfToken
  • Illuminate/Cookie/Middleware/EncryptCookies
  • Illuminate/Session/EncryptedStore.php

コントラクト(Illuminate\Contracts\Encryption\Encrypter)の定義は以下のようになっている。

namespace Illuminate\Contracts\Encryption;

interface Encrypter
{
    public function encrypt($value, $serialize = true);
    public function decrypt($payload, $unserialize = true);
}

暗号化の機能を利用するクラスがインターフェース(コントラクト)に依存することで、暗号化処理はencryptメソッドとdecryptメソッドを実装してさえいればIlluminate\Encryption\Encrypterではなく独自の暗号化処理に差し替えることが可能になっている。

コントラクトを利用した機能の差し替え

暗号化処理を独自の処理に差し替える場合

1. App/HogeEncrypterを生成
namespace App;
use Illuminate\Contracts\Encryption\Encrypter as EncrypterContract;

class HogeEncrypter implements EncrypterContract
{
    protected $key;
    
    public function __construct($key)
    {
        $this->key = $key;
    }
    
    public function encrypt($value, $serialize = true)
    {
        return $value . 'hoge';
    }
    
    public function decrypt($payload, $unserialize = true)
    {
        return substr($payload, 0, strlen($payload) -4);
    }

    public function getKey()
    {
        return $this->key;
    }
    // getKeyメソッドはEncrypterインターフェースには定義がないが、実装しないとエラーになってしまうため実装する
}
2. バインドを追加

暗号化処理をサービスコンテナにバインドしているのはIlluminate\Encryption\EncryptionServiceProviderであり、registerメソッドでencrypterという文字列でバインドしている。
これをApp\Providers\AppServiceProviderで再バインドする。

public function register()
{
    app()->singleton('encrypter', function (\Illuminate\Foundation\Application $app) {
        $config = $app->make('config')->get('app');
        return new HogeEncrypter($this->key($config));
    });
}

protected function key(array $config)
{
    return tap($config['key'], function ($key) {
        if (empty($key)) {
            throw new RuntimeException(
                'No application encryption key has been specified.'
            );
        }
    });
}
// keyメソッドはEncryptionServiceProviderと全く同じソース

以上の変更を行った後にクッキーの値を見ると、暗号化された文字列の末尾がhogeになっている。

コメント