#04 Laravel基礎

依存性注入とサービスコンテナ

依存性注入(DI)とは

依存性注入(DI)? クラスが必要とする別のクラスを外から渡す設計パターン。コンストラクターや型宣言で受け取ることでテストしやすくなる。 とは、クラスが必要とする別のクラス(依存)を外から渡す設計パターンです。

DIを使わない例

// 📁 app/Http/Controllers/PostController.php

class PostController extends Controller
{
    public function index()
    {
        $service = new PostService(); // ← コントローラー自身がインスタンスを生成
        $posts = $service->getLatest();
        return view('posts.index', compact('posts'));
    }
}

この書き方では PostService の中身が変わるたびにコントローラーも修正が必要になり、テストも難しくなります。

DIを使った例

// 📁 app/Http/Controllers/PostController.php

class PostController extends Controller
{
    // コンストラクターで受け取る(コンストラクターインジェクション)
    public function __construct(
        private PostService $postService
    ) {}

    public function index()
    {
        $posts = $this->postService->getLatest();
        return view('posts.index', compact('posts'));
    }
}

PostService $postService と型宣言するだけで、Laravelが自動でインスタンスを生成して渡してくれます。これを可能にするのが サービスコンテナ? Laravelがクラスの依存関係を自動で解決・注入する仕組み。「このクラスが必要なら自動で作って渡す」DIコンテナのこと。 です。


サービスコンテナとは

サービスコンテナ? Laravelがクラスの依存関係を自動で解決・注入する仕組み。「このクラスが必要なら自動で作って渡す」DIコンテナのこと。 はクラスの依存関係を自動で解決する「DI コンテナ」です。

仕組みを簡単に説明すると:

  1. Laravelはコントローラーを呼び出す前に、コンストラクターの型宣言を解析する
  2. PostService が必要だとわかったら自動でインスタンスを生成する
  3. もし PostService 自身も他のクラスに依存していれば、それも再帰的に解決する

サービスクラスを作る

コントローラーからビジネスロジックを切り出す「サービスクラス」はArtisanコマンドで生成する専用コマンドはないため、手動でファイルを作ります。

<?php
// 📁 app/Services/PostService.php(新規作成)

namespace App\Services;

use App\Models\Post;

class PostService
{
    public function getLatest(int $limit = 10)
    {
        return Post::latest()->take($limit)->get();
    }

    public function createPost(array $data): Post
    {
        return Post::create($data);
    }
}

app/Services/ フォルダは自分で作成します。Laravelの規約ではフォルダ名は自由ですが、慣習的に Services がよく使われます。


インターフェースとバインディング

さらに一歩進んで、インターフェースを使うとサービスの切り替えが容易になります。

<?php
// 📁 app/Contracts/PostRepositoryInterface.php(新規作成)

namespace App\Contracts;

interface PostRepositoryInterface
{
    public function getLatest(int $limit): \Illuminate\Database\Eloquent\Collection;
}
<?php
// 📁 app/Repositories/EloquentPostRepository.php(新規作成)

namespace App\Repositories;

use App\Contracts\PostRepositoryInterface;
use App\Models\Post;

class EloquentPostRepository implements PostRepositoryInterface
{
    public function getLatest(int $limit): \Illuminate\Database\Eloquent\Collection
    {
        return Post::latest()->take($limit)->get();
    }
}

このままではインターフェースを型宣言してもLaravelはどの実装クラスを使えばいいか分かりません。AppServiceProvider でバインディングを登録します。

<?php
// 📁 app/Providers/AppServiceProvider.php

namespace App\Providers;

use App\Contracts\PostRepositoryInterface;
use App\Repositories\EloquentPostRepository;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        // インターフェースと実装クラスを紐づける
        $this->app->bind(
            PostRepositoryInterface::class,
            EloquentPostRepository::class
        );
    }
}

これでコントローラーでインターフェースを型宣言できます。

// 📁 app/Http/Controllers/PostController.php

use App\Contracts\PostRepositoryInterface;

class PostController extends Controller
{
    public function __construct(
        private PostRepositoryInterface $posts // ← インターフェースで受け取る
    ) {}

    public function index()
    {
        $posts = $this->posts->getLatest(10);
        return view('posts.index', compact('posts'));
    }
}

テスト時には EloquentPostRepository の代わりにモックを渡すことができます。


シングルトンとしてバインドする

インスタンスを1つだけ生成して使いまわしたい場合は bind の代わりに singleton を使います。

// 📁 app/Providers/AppServiceProvider.php

$this->app->singleton(PaymentGateway::class, function () {
    return new StripeGateway(config('services.stripe.key'));
});

singleton で登録すると、アプリケーション全体で同じインスタンスが再利用されます。


まとめ

  • DIとは依存するクラスを外から渡す設計パターン
  • Laravelのサービスコンテナが型宣言を見て自動でインスタンスを注入する
  • ビジネスロジックは app/Services/ に切り出すとコントローラーがシンプルになる
  • インターフェースを使うと実装を差し替えやすくなりテストも書きやすい
  • AppServiceProviderregister() でバインディングを登録する

次回はBladeの高度な機能(コンポーネント・スタック・カスタムディレクティブ)を学びます。