サービスコンテナ入門:Laravelでのコード整理と拡張性の鍵

サービスコンテナ入門:Laravelでのコード整理と拡張性の鍵 プログラミング

「サービスコンテナって何?」
「サービスプロバイダもよくわからない・・・」

このような疑問を持つ方にぴったりの内容が、この記事にはあります。
Laravelのサービスコンテナについて、初心者でも理解しやすいように丁寧に解説しています。

Laravelでの依存性の管理やアプリケーション構造の理解に役立つ知識が詰まっており、
サービスコンテナとサービスプロバイダの基本を身につけたい方に最適です。

本記事の内容

  • サービスコンテナの基本理解
  • サービスコンテナを用いない場合(コードで説明)
  • サービスコンテナを用いる場合(コードで説明)
  • サービスコンテナのメリット

それでは、上記に沿って解説していきます。

サービスコンテナの基本理解

Laravelのサービスコンテナは、アプリケーションの各部分がうまく連携するための仕組みです。
考え方としては、ある部分が他の部分と「あまり密接に結びつかないようにする」ことが重要です。
これを「疎結合」と言います。

例えば、あなたがレストランで料理を注文するときをイメージしてください。
厨房でどのようにして料理が作られているかを詳細には知らないですよね。
ただ、ウェイターに注文を告げるだけで済みます。

サービスコンテナも同じです。
例えば、メールを送信する機能がユーザー登録の処理についての影響を考える必要はありません。

メール送信機能は、メールを送ることに専念すればいいだけです。
それに対して、ユーザー登録機能はメール送信機能を必要なデータとともに呼び出すだけで済みます。

サービスコンテナの利点は、アプリケーションを「部品」のように分けて考えることができる点です。
各部品は独立していて、必要に応じて簡単に取り替えることができます。

これにより、アプリケーションの修正や拡張が容易になります。
新しい機能を追加したり、既存の機能を改善したりする両方のケースにおいてです。

つまり、全体を一から作り直す必要がなくなります。
結果的に、ソースをコピペで使いまわすということもなくなることでしょう。

例えば、メール送信の部分を別の方法に変更したい場合があるとします。
サービスコンテナのおかげで、その部分だけを変えれば良く、他の部分には影響がありません。
これにより、コードの管理が容易になり、メンテナンスや新しい機能の追加がスムーズに行えるようになります。

まとめると、サービスコンテナはアプリケーションを次の点で大きく向上させる重要な機能です。

  • 組み立てやすさ
  • 変更のしやすさ

Laravelを使う上で、このサービスコンテナを理解し活用することは、
より効率的で拡張性の高いアプリケーション開発につながります。

これ以降では、コードを用いてサービスコンテナについて説明します。
そのためには、現状(サービスコンテナを用いない)を確認しておく必要があります。

サービスコンテナを用いない場合(コードで説明)

まずは、仕様ですね。
単純にDBアクセスするという処理を行います。

ただし、開発初期の時点ではDBが用意されていない状況です。
そのため、モッククラスを呼び出すこととします。
モッククラスはインターフェースの実装としています。

【インターフェース】app/Contracts/DatabaseInterface.php

<?php

namespace App\Contracts;

interface DatabaseInterface {
    public function query($sql);
}

【モッククラス】app/Services/MockDatabase.php

<?php

namespace App\Services;

use App\Contracts\DatabaseInterface;

class MockDatabase implements DatabaseInterface {
    public function query($sql) {
        // モックの動作を定義
        return "モックデータベースで実行されたクエリ: " . $sql;
    }
}

モッククラスを利用するページでは、次のようなルートとコントローラーを設定しています。

【ルート】routes/web.php

use App\Http\Controllers\NoContainerController;

Route::get('/no_container', [NoContainerController::class, 'show']);

【コントローラー】app/Http/Controllers/NoContainerController.php

<?php

namespace App\Http\Controllers;

use App\NoContainerUserClass;

class NoContainerController extends Controller {
    public function show() {
        $userClass = new NoContainerUserClass();
        return $userClass->getUser(1);
    }
}

このコントローラーでは、次のビジネスロジックを実行することになります。

【ビジネスロジック】app/NoContainerUserClass.php

<?php

namespace App;

use App\Services\MockDatabase;

class NoContainerUserClass {
    protected $database;

    public function __construct() {
        $this->database = new MockDatabase();
    }

    public function getUser($id) {
        return $this->database->query("SELECT * FROM users WHERE id = $id");
    }
}

この状況で「/no_container」にアクセスすると、次のように表示されます。

モックデータベースで実行されたクエリ: SELECT * FROM users WHERE id = 1

このようにモッククラスで動かしている状況において、実際の実装ができるとしましょう。

【リアルクラス】app/Services/MySqlDatabase.php

<?php

namespace App\Services;

use App\Contracts\DatabaseInterface;

class MySqlDatabase implements DatabaseInterface {
    public function query($sql) {
        // 実際の動作を記述
        return "実際のDBで実行されたクエリ: " . $sql;
    }
}

この場合、ビジネスロジックでコードの修正が必要になります。
旧コードをコメントにしています。

<?php

namespace App;

use App\Services\MySqlDatabase;
// use App\Services\MockDatabase;

class NoContainerUserClass {
    protected $database;

    public function __construct() {
        $this->database = new MySqlDatabase();
        // $this->database = new MockDatabase();
    }

    public function getUser($id) {
        return $this->database->query("SELECT * FROM users WHERE id = $id");
    }
}

修正後に「/no_container」にアクセスした結果は、以下。

実際のDBで実行されたクエリ: SELECT * FROM users WHERE id = 1

以上が、サービスコンテナを用いない場合のコード管理です。

サービスコンテナを用いる場合(コードで説明)

今度は、サービスコンテナを利用する場合です。
それ用のページを設けます。

【ルート】routes/web.php

use App\Http\Controllers\YesContainerController;

Route::get('/yes_container', [YesContainerController::class, 'show']);

【コントローラー】app/Http/Controllers/YesContainerController.php

<?php

namespace App\Http\Controllers;

use App\YesContainerUserClass;

class YesContainerController extends Controller {
    protected $userClass;

    public function __construct(YesContainerUserClass $userClass) {
        $this->userClass = $userClass;
    }

    public function show() {
        return $this->userClass->getUser(1);
    }
}

【ビジネスロジック】app/YesContainerUserClass.php

<?php

namespace App;

use App\Contracts\DatabaseInterface;

class YesContainerUserClass {
    protected $database;

    public function __construct(DatabaseInterface $database) {
        $this->database = $database;
    }

    public function getUser($id) {
        return $this->database->query("SELECT * FROM users WHERE id = $id");
    }
}

これらのファイルに加えて、サービスプロバイダに追加が必要です。

【サービスプロバイダ】app/Providers/AppServiceProvider.php

<?php

namespace App\Providers;
use App\Contracts\DatabaseInterface;//追加
use App\Services\MockDatabase;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        $this->app->bind(DatabaseInterface::class, MockDatabase::class);//追加
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        //
    }
}

Laravelにおけるサービスコンテナの登録は、サービスプロバイダを通じて行われます。
サービスプロバイダは、アプリケーションのサービスやクラス、依存関係などをサービスコンテナに登録するための中心的な場所です。

この状況で「/yes_container」にアクセスすると、次の結果が表示されます。

モックデータベースで実行されたクエリ: SELECT * FROM users WHERE id = 1

そして、リアルクラスに置き換えましょう。
そのとき、修正するのはサービスプロバイダだけでOKです。

ファイル内の「MockDatabase」を「MySqlDatabase」に置き換えます。
念のため、全部を載せておきます。

<?php

namespace App\Providers;
use App\Contracts\DatabaseInterface;
use App\Services\MySqlDatabase;
//use App\Services\MockDatabase;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        $this->app->bind(DatabaseInterface::class, MySqlDatabase::class);
        //$this->app->bind(DatabaseInterface::class, MockDatabase::class);
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        //
    }
}

サービスコンテナを用いる場合は、この変更だけで済みます。
他のファイルを触る必要はありません。

変更後に「/yes_container」にアクセスした結果は、以下。

実際のDBで実行されたクエリ: SELECT * FROM users WHERE id = 1

以上が、サービスコンテナを用いる場合のコード管理です。

サービスコンテナのメリット

サービスコンテナを使うメリットがわかったでしょうか?
正直、これだけではわかりにくいかもしれません。

それなら、修正すべきビジネスロジックが複数ある場合を想定してみてください。
その場合、各ビジネスロジックを修正し回る可能性があります。

しかし、サービスコンテナを利用していれば、基本的にサービスプロバイダだけを触ればいいのです。
このことは、依存関係の一括管理と言えます。

依存関係の一括管理ができると、次のようなメリットがあります。

  1. 集中化
  2. 明確な構成
  3. 疎結合
  4. 拡張性
  5. メンテナンス性

それぞれを以下で説明します。

集中化

アプリケーション内のすべての依存関係がサービスプロバイダで定義されているため、依存性の管理が容易になります。

明確な構成

どのクラスがどの依存性に依存しているかが一目でわかり、アプリケーションの構成が明確になります。

疎結合

依存性の注入を通じて、クラスは具体的な実装ではなくインターフェースに依存します。
これにより、クラス間の結合度が低くなり、コードの再利用性とテスタビリティが向上します。

拡張性

新しいサービスやクラスをアプリケーションに追加する際、サービスプロバイダを通じて容易に統合できます。

メンテナンス性

依存関係の変更や更新が必要な場合、サービスプロバイダを通じて一箇所で行うことができます。

タイトルとURLをコピーしました