Top View


Author melank

Laravelのサービスプロバイダとコントラクト #Laravel輪読会 Vol.3

2021/09/09

チームLIGHTの紹介

チームLIGHTは現在エンジニア5名、デザイナー2名、インターン1名の合計8人のチームです。 チームLIGHTの雑記ブログもありますので、よかったらそちらもそうぞ! こちらは技術的な内容ではなく、メンバーの日常のことをつらつらと書いています。

また、不定期でLIGHT@NOONという配信イベントも開催しています。

Laravel輪読会の紹介

チームLIGHTでは、週に1回1時間、Laravel輪読会を実施しています。

輪読会で読む本はこちらの本です!

PHPフレームワークLaravel Webアプリケーション開発 バージョン8.x対応

2021/6/1に発売されたばかりの本で、Laravel8にも対応しています。

ボリュームもたっぷりな上に、Laravelの設計やテストコードに関する内容も充実しているので、オススメです!

本題

今回読んだページはP.83 ~ P.94 の「2-3 サービスプロバイダ」「2-04 コントラクト」についてです。

サービスプロバイダについて

サービスプロバイダの役割は以下の 3 つ。

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

2-3-1 サービスプロバイダの基本的な動作

Laravelでは、全体の起動処理として、まず各サービスプロバイダのregisterメソッドが実行されます。そして、任意に各サービスプロバイダに実装されたbootメソッドが続いて実行されます。

registerメソッドでは、サービスコンテナへのメソッドを行い、他のことは行わないようにする。これは、メソッドが実行されるタイミングでは、サービスコンテナから他の機能のインスタンスを取得できないためです。その様な処理を記述したい場合には、bootメソッドで行う様にします。(下記は、DatabaseServiceProviderからregisterbootを抜粋したもの)


class DatabaseServiceProvider extends ServiceProvider
{
  public function boot()
  {
      Model::setConnectionResolver($this->app['db']);
      Model::setEventDispatcher($this->app['event']);
  }

  public function register()
  {
    Model::clearBootedModels();
    // サービスコンテナへのバインド処理
    $this->registerConnectionServices();
    $this->registerEloquentFactory();
    $this->registerQueueableEntiryResolver();
    $this->registerDoctrineTypes();
  }

2-3-2 DeferrableProviderインターフェースの遅延実行

サービスプロバイダは、アプリケーションの起動時にregisterメソッドが実行される仕組みですが、DeferrableProviderインタフェースを実装することで、この実行を遅らせることができます。具体的には、実際にサービスコンテナに指定した文字列の解決を行った(サービスコンテナに依頼した)タイミングで、サービスプロバイダのregisterメソッドが呼ばれて解決が行われます。

チームで話したこと

  • app.phpに書いてあるServiceProviderと、App\Providers配下にあるServiceProviderの違いについて

    • App\Providers配下にないものに関しては、カスタマイズしなくてもある程度はいい感じに動くけど、カスタマイズしたい時はPublishしてやってください、という程度かな?という仮説。
  • デフォルトのサービスプロバイダってほとんどが遅延実行だったような

    • だったら新しく追加するときも遅延実行のほうが良いんだろうか
    • laravel.com のドキュメントにそっくり書いてた👇

If your provider is only registering bindings in the service container, you may choose to defer its registration until one of the registered bindings is actually needed. Deferring the loading of such a provider will improve the performance of your application, since it is not loaded from the filesystem on every request.

  • bootでなにかするやつは、非遅延
    • Middleware 登録してるやつもそうっぽい? SessionServiceProvider とか
    • 使用頻度が高いやつは遅延させないほうがオーバーヘッドがないとかありそう
    • 特定ルートでしか使ってないとかなら遅延実行させれば速くなりそう

コントラクトについて

コントラクトは、Laravelのコアとなるコンポーネントで利用されている関数群をインタフェースとして定義したもの。 クラス同士を疎結合にする。機能の差し替えが簡単に可能になる。

2-4-1 コントラクトの基本

コンポーネントの機能を利用するクラスが、コンポーネントの抽象クラスに依存するようにすることで、疎結合なコードを実現する事ができます。

下記は、Encrypterコントラクトのコードです。Encrypterは、encryptdecryptメソッドをもつインタフェースです。暗号化の機能を持たせたいコンポーネントには、このインタフェースのメソッドを実装することとなります。

<?php

namespace Illuminate\Contracts\Encryption;

interface Encrypter
{
  public function encrypt($value, $serialize = true);

  public function decrypt($payload, $unserialize = true);
}

そして、その実装した暗号化処理を利用したいクラスには、タイプヒンティングとして暗号化処理の具象クラスであるコンポーネントではなく、上記のインタフェースを指定します。下記は、暗号化処理を必要とするクラスである、EncryptedStore からの抜粋です。

<?php

namespace Illuminate\Session;

use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Contracts\Encryption\Encrypter as EncrypterContract;
use SessionHandlerInterface;

class EncrptedStore extends Store
{
    protected $encrypter;

    public function __construct($name, SessionHandlerInterface $handler, EncrypterContract $encrypter, $id = null)
    {
        $this->encrypter = $encrypter;

        parent::__construct($name, $handler, $id);
    }

}

注目すべきは、コンストラクタの引数のタイプヒンティングで、暗号化処理を実装している具象クラスではなく、インタフェースを指定していることです。インタフェースで指定されたメソッドを実装してさえいれば、自由にコンポーネントの差し替えが可能になります。クラス間の疎結合を保つことで、Laravelに用意されたデフォルトの処理から、独自に作成した処理への差し替えなども簡単になります。

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

本書には、ライブラリを導入、コントラクトを利用して暗号化の方式を変更する実例が取り上げられています。 非常にわかりやすい説明なので、ぜひ実際に本書を手に取りご覧になってください。

チームで話したこと

  • コントラクトで暗号処理を差し替えるの、説明すごいわかりやすかった。この参考書わかりやすいなぁって思いました
  • Contract を見る度に Interface クラスを格納するディレクトリは 「Contract にしとくべきか?」 って迷って、結局 Interfaces にしてしまう
    • Interfaces って名前にしとけば、独自でつくった感がでて分かりやすいかも
  • global helper といえど中身が交換可能なのは良いところ
melank

melank

Twitter X

Engineer ✨ PHP ( Laravel, CakePHP ) 🐘 AWS ( 🏆 DBS, SCS, SAA, DVA, SOA )