依存性注入とサービスコンテナ
依存性注入(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 コンテナ」です。
仕組みを簡単に説明すると:
- Laravelはコントローラーを呼び出す前に、コンストラクターの型宣言を解析する
PostServiceが必要だとわかったら自動でインスタンスを生成する- もし
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/に切り出すとコントローラーがシンプルになる - インターフェースを使うと実装を差し替えやすくなりテストも書きやすい
AppServiceProviderのregister()でバインディングを登録する
次回はBladeの高度な機能(コンポーネント・スタック・カスタムディレクティブ)を学びます。