Dependency Injection(依存性注入)とは
アプリケーションを開発していると、「あるクラスが別のクラスを必要とする」場面が頻繁に起きます。たとえばコントローラがメール送信サービスを使うケースです。
DI(Dependency Injection)なしの書き方では、クラスの内部で依存オブジェクトを自分で生成します。
class OrderController
{
public function confirm(): void
{
$mailer = new MailerService(); // 内部でnewしている
$mailer->send('注文を受け付けました');
}
}
この書き方の問題点は、OrderController が MailerService に強く依存していることです。テストのときにモックに差し替えにくく、MailerService のコンストラクタ引数が変わるたびに呼び出し側も直す必要が出てきます。
DIありの書き方では、依存オブジェクトを外から「注入」します。
class OrderController
{
public function __construct(
private MailerService $mailer
) {}
public function confirm(): void
{
$this->mailer->send('注文を受け付けました');
}
}
こうすることで、OrderController は「MailerServiceのインターフェースに依存する」だけになり、テスト時に差し替えが簡単になります。
Symfonyのサービスコンテナとは
SymfonyではこのDIの仕組みを**サービスコンテナ(DIコンテナ)**が一元管理します。サービスコンテナは「どのクラスがどのクラスに依存しているか」を把握し、必要なタイミングで自動的にオブジェクトを生成・注入してくれます。
開発者は依存関係を「設定ファイル」か「Autowiring(自動配線)」で宣言するだけでよく、オブジェクトの生成順序を手動で管理する必要がありません。
プロジェクトのセットアップ
まずSymfonyプロジェクトを作成します。
composer create-project symfony/skeleton my-app
cd my-app
composer require symfony/framework-bundle
サービスの定義:config/services.yaml
Symfonyでは config/services.yaml にサービスの設定を書きます。デフォルトではほぼ何も書かずに済む「Autowiring」が有効になっています。
# config/services.yaml
services:
_defaults:
autowire: true # 型ヒントをもとに自動注入
autoconfigure: true # タグなどを自動設定
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
autowire: true を設定すると、コンストラクタの型ヒントを読み取り、対応するサービスを自動で注入してくれます。
実践:メール送信サービスを作る
サービスクラスの作成
<?php
// src/Service/MailerService.php
namespace App\Service;
class MailerService
{
public function __construct(
private string $fromAddress = 'noreply@example.com'
) {}
public function send(string $to, string $message): void
{
// 実際にはSwiftmailerやSymfony Mailerを使う
echo "送信先: {$to}, 内容: {$message}, 差出人: {$this->fromAddress}";
}
}
コントローラへの注入
<?php
// src/Controller/OrderController.php
namespace App\Controller;
use App\Service\MailerService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class OrderController extends AbstractController
{
public function __construct(
private MailerService $mailer
) {}
#[Route('/order/confirm', name: 'order_confirm')]
public function confirm(): Response
{
$this->mailer->send('user@example.com', '注文を受け付けました');
return new Response('注文確認メールを送信しました');
}
}
コントローラのコンストラクタに MailerService を型ヒントで宣言するだけで、Symfonyが自動的にインスタンスを生成して渡してくれます。new MailerService() と書く必要はありません。
スカラー値をサービスに注入する
クラスのオブジェクトではなく、文字列や数値などのスカラー値を注入したい場合は services.yaml で明示的に設定します。
# config/services.yaml
services:
App\Service\MailerService:
arguments:
$fromAddress: 'support@myapp.com'
こうすることで MailerService に support@myapp.com が自動的に渡されます。環境変数を使う場合は以下のように書きます。
App\Service\MailerService:
arguments:
$fromAddress: '%env(MAILER_FROM)%'
.env ファイルに MAILER_FROM=hello@example.com と書けば切り替えが簡単です。
インターフェースを使った差し替え
テストしやすい設計にするために、インターフェースを使ってサービスを抽象化するのがベストプラクティスです。
<?php
// src/Service/MailerInterface.php
namespace App\Service;
interface MailerInterface
{
public function send(string $to, string $message): void;
}
<?php
// src/Service/MailerService.php
namespace App\Service;
class MailerService implements MailerInterface
{
public function send(string $to, string $message): void
{
echo "メール送信: {$to}";
}
}
インターフェースとその実装クラスを紐づけるには services.yaml に以下を追加します。
services:
App\Service\MailerInterface:
alias: App\Service\MailerService
コントローラ側では MailerInterface を型ヒントにするだけで、実装クラスが注入されます。テスト時だけ別の実装を差し込む、という使い方が簡単になります。
まとめ
| 概念 | 説明 |
|---|---|
| DI(依存性注入) | 依存オブジェクトを外部から注入するパターン |
| サービスコンテナ | 依存関係を一元管理・自動解決する仕組み |
| Autowiring | 型ヒントをもとに自動でサービスを注入する機能 |
| services.yaml | サービスの設定・引数・エイリアスを定義するファイル |
Symfonyのサービスコンテナを活用することで、クラス同士の依存関係が整理され、テストしやすく保守性の高いコードが書けるようになります。最初は設定ファイルの書き方に戸惑うかもしれませんが、autowire: true を使えばほとんどの場面でほぼ設定不要で動くので、まずは型ヒントを使ったコンストラクタインジェクションから試してみてください。