1. Home
  2. PHP
  3. Laravel
  4. Laravel のサービスコンテナで依存注入を行う

Laravel のサービスコンテナで依存注入を行う

  • 公開日
  • 更新日
  • カテゴリ:Laravel
  • タグ:PHP,Laravel,DependencyInjection,ServiceContainer
Laravel のサービスコンテナで依存注入を行う

PHP などでオブジェクト指向にてアプリケーション開発を行っているならば、出来るだけ結合度を低く保った状態でそれぞれの機能開発を進めていきたいところです。

Laravel では、DI をスムーズに実現できる仕組みが提供されています。今回は Laravel のサービスコンテナを用いた依存性注入について見ていきます。

Contents

  1. 開発環境
  2. 依存注入
  3. 入門編
    1. コンポーネント
    2. コンスタントインジェクション
  4. 実践編

開発環境

  • PHP 8.1
  • Laravel 9

依存注入

DI(Dependency Injection=依存性注入) とは、依存関係にあるオブジェクトを自身の中ではなく外から設定してあげる事で、よりスムーズなソース管理や処理の流れを実現するデザインパターンの事です。

Laravel では、これを簡単に導入できる仕組みが提供されています。

まずは何も考えずにクラス定義を行い、以下のようなコードがあったとします。

class Sample
{
    private Right $right;
    private Left  $left;

    public function __construct()
    {
        $this->right = new Right();
        $this->left = new Left();
    }

Sample クラスで Right クラスと Left クラスをインスタンス化していますが、この Sample クラスは、Right クラスと Left クラスがあってこそ全ての処理が実装できる事を意味しています。つまり

「Sample クラスは Right クラスと Left クラスに依存している」

という状態になります。

こういったメインのオブジェクトが依存するオブジェクトを自身の中で具象化するのではなく抽象化を行い、それらを外から入れてあげる事で、オブジェクト同士がより疎の関係となり、保守性の向上につながる。という事になります。

入門編

まずは抽象化などを一旦無視して、基本的な DI 実装の流れだけを見ていきます。

今回は定番の計算機能を実装します。冒頭の例に例えれば、DI 前は以下のような関係性です。

<?php

class Sample
{
    public function __invoke()
    {
        $calculation = new Calculation();
    }

コンポーネント

まずは注入する計算用のコンポーネントを作成します。 laravel/app 配下に Components ディレクトリを作成し、その配下に Calculation クラスを作成します。

laravel
├─ app
│   ├─ Components
│   │   └─ Calculation.php
laravel/app/Component/Calculation.php
<?php
declare(strict_types=1);

namespace App\Components;

class Calculation
{
    // 加算
    public function add(int $arg1, int $arg2): int
    {
        return $arg1 + $arg2;
    }

    // 減算
    public function sub(int $arg1, int $arg2): int
    {
        return $arg1 - $arg2;
    }

    // 乗算
    public function mul(int $arg1, int $arg2): int
    {
        return $arg1 * $arg2;
    }

    // 除算
    public function div(int $arg1, int $arg2): float
    {
        return $arg1 / $arg2;
    }
}

シンプルな四則演算のメソッドを実装しました。

コンスタントインジェクション

依存の注入を行ってみます。

<?php

use App\Components\Calculation;

class Sample
{
    /**
     * @param Calculation $calculation
     */
    public function __construct(private Calculation $calculation)
    {
    }

    public function index()
    {
        $result = [
            'add' =>  $this->calculation->add(1, 1),
            'sub' =>  $this->calculation->sub(2, 1),
            'mul' =>  $this->calculation->mul(3, 2),
            'div' =>  $this->calculation->div(4, 2),
        ];

        print_r($result);
        // => Array
        //  (
        //    [add] => 2
        //    [sub] => 1
        //    [mul] => 6
        //    [div] => 2
        //  )
    }
}

コンストラクタにてタイプヒンティング付きで渡す事で自動で依存解決される(コンストラクタインジェクションといいます)ので、この中でインスタンス化しなくても済んでいる事がわかります。

実践編

ここまでで簡単な DI の流れを見てきました。今度は抽象化も行って、もう少し実践的に使ってみます。

まずは DI を行う為のサンプルで適当なコンポーネントクラスを2つ作成します。

インターフェースを定義し、それをコンポーネントクラスで実装します。

SampleComponentInterface.php
<?php

interface SampleComponentInterface
{
    public function execute(): string;
}
SampleComponentAAA.php
<?php

class SampleComponentAAA implements SampleInterface
{
    public function execute(): string
    {
        return 'AAA';
    }
}
SampleComponentBBB.php
<?php

class SampleComponentBBB implements SampleInterface
{
    public function execute(): string
    {
        return 'BBB';
    }
}

作成したこれらの Sample クラスから使うとします。

Sample.php
<?php

class SampleService
{
    public function __construct(private SampleInterface $SampleComponent)
    {
    }

    public function index()
    {
        return  $this->SampleComponent->execute();
    }
}

そしてこの場合に、サービスプロバイダの記述は以下になります。(AppServiceProvider に記述しますが専用に作成しても OK)

app/Providers/AppServiceProvider.php
<?php

use App\Sample\SampleInterface;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind(SampleInterface::class, function($app) {
            return new \App\Components\SampleComponentAAA();
            // OR
            //return new \App\Components\SampleComponentBBB();
        });
    }
}

抽象化を行い依存をサービスコンテナ側で解決しているので、Sample クラスではどの具象クラス(AAA or BBB)を使用するかについて言及しなくても良くなっています。また、利用するコンポーネントを変更する際にも Sample クラスには変更が不要になりました。

オブジェクト同士の関係をシンプルに保つ事で、拡張性や保守性も向上するので是非試してみてください。

Author

rito

  • Backend Engineer
  • Tokyo, Japan
  • PHP 5 技術者認定上級試験 認定者
  • 統計検定 3 級