0Pricing
PHP Academy · レッスン

依存性逆転の実践

依存性逆転でアダプターをコアに接続します。

「依存性逆転の実践」はCoddyKit上の無料PHP Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはPHP Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 PHP Academyコースには全4レッスンが含まれています。

依存性逆転と依存性注入

この2つの異なる考え方は、混同されがちです。依存性注入は技法です。依存するオブジェクトを自分で生成するのではなく、外部から渡します。依存性逆転(SOLIDのD)は原則です。高レベルの方針と低レベルの詳細がともに抽象に依存し、その抽象を高レベルのモジュールが所有します。このレッスンでは、コアがインターフェースを定義し、具体的なアダプターをそこに接続することで、後者を実現します。

原則を正確に理解する

DIPは次のように定めています。

  • 高レベルモジュールは低レベルモジュールに依存してはいけません。両者は抽象に依存します。
  • 抽象は詳細に依存してはいけません。詳細が抽象に依存します。

重要な点は、インターフェースを実装者ではなく利用者が所有することです。ドメインがPaymentGatewayを宣言し、Stripeアダプターがそれに準拠します。逆ではありません。

コアで抽象を定義する

インターフェースは、それを必要とするコードの近くに置き、ドメインの用語で表現します。Stripeの型を漏れ込ませてはいけません。

<?php
// src/Billing/Application/Port/PaymentGateway.php
interface PaymentGateway
{
    public function charge(Money $amount, CardToken $token): ChargeId;
}

final class Money {
    public function __construct(public readonly int $cents, public readonly string $currency) {}
}
final class CardToken { public function __construct(public readonly string $value) {} }
final class ChargeId { public function __construct(public readonly string $value) {} }

アダプターを抽象に準拠させる

インフラストラクチャのアダプターはコアのインターフェースを実装し、ベンダーSDKへの変換を行います。依存関係の矢印は、アダプター(詳細)から抽象(方針)に向かいます。これで逆転が実現します。

<?php
final class StripePaymentGateway implements PaymentGateway
{
    public function __construct(private \Stripe\StripeClient $stripe) {}

    public function charge(Money $amount, CardToken $token): ChargeId {
        $intent = $this->stripe->paymentIntents->create([
            'amount'   => $amount->cents,
            'currency' => strtolower($amount->currency),
            'payment_method' => $token->value,
            'confirm'  => true,
        ]);
        return new ChargeId($intent->id);
    }
}

コンストラクター注入をデフォルトにする

コンストラクター経由で注入し、抽象を型ヒントに指定してください。依存関係が明示的かつ不変になり、渡し忘れも起こりません。必須の依存オブジェクトには、セッター注入やプロパティ注入を避けてください。構築途中の不完全なオブジェクトを許してしまうためです。

<?php
final class CheckoutService
{
    public function __construct(
        private PaymentGateway $payments,   // abstraction, not StripeClient
        private OrderRepository $orders,
    ) {}

    public function pay(OrderId $id, CardToken $token): ChargeId {
        $order  = $this->orders->get($id);
        $charge = $this->payments->charge($order->total(), $token);
        $order->markPaid($charge);
        $this->orders->save($order);
        return $charge;
    }
}

コンポジションルート

具体的な接続はすべて、main()やエントリーポイントにできるだけ近いコンポジションルートという1か所で行います。それ以外の場所でインフラストラクチャをnewしてはいけません。Stripeの存在を知っているのはこの場所だけです。

<?php
// public/index.php — composition root
$stripe   = new \Stripe\StripeClient(getenv('STRIPE_SECRET'));
$gateway  = new StripePaymentGateway($stripe);
$orders   = new PdoOrderRepository(new PDO(getenv('DB_DSN')));
$checkout = new CheckoutService($gateway, $orders);

// Everything below depends only on abstractions
$controller = new CheckoutController($checkout);

DIコンテナで接続する

複雑なアプリケーションでは、コンテナ(PHP-DI、Symfony)を使って接続を自動化します。重要なのは、インターフェースを実装にバインドすることです。オートワイヤリングは型に基づいてコンストラクターを解決するため、インターフェースとクラスの対応表を宣言するだけで済みます。

<?php
use function DI\autowire;
use function DI\get;

return [
    PaymentGateway::class => autowire(StripePaymentGateway::class),
    OrderRepository::class => autowire(PdoOrderRepository::class),
    \Stripe\StripeClient::class => fn() => new \Stripe\StripeClient(getenv('STRIPE_SECRET')),
    PDO::class => fn() => new PDO(getenv('DB_DSN')),
];

アダプターの交換で原則を確かめる

コアがPaymentGatewayだけに依存しているため、プロバイダーの切り替えやオフラインでのテストは、接続を1行変更するだけで行えます。これはユニットテストで使うフェイクの例です。CheckoutServiceは変更されず、Stripeをインポートすることもありません。

<?php
final class FakeGateway implements PaymentGateway {
    public array $charges = [];
    public function charge(Money $a, CardToken $t): ChargeId {
        $this->charges[] = $a;
        return new ChargeId('ch_test_' . count($this->charges));
    }
}

$fake = new FakeGateway();
$id = $fake->charge(new Money(2500, 'EUR'), new CardToken('tok_visa'));
echo $id->value, ' charges=', count($fake->charges), PHP_EOL; // ch_test_1 charges=1

サービスロケーターの罠を避ける

コンテナ自体を注入し、メソッドの内部で依存関係を取り出すのは、サービスロケーターアンチパターンです。依存関係を隠し、型チェックを無効にし、コードをコンテナに再び結合させてしまいます。必要なものを明示的に注入してください。

<?php
// ANTI-PATTERN: hidden dependencies, container leaks everywhere
final class BadCheckout {
    public function __construct(private ContainerInterface $c) {}
    public function pay($id, $token) {
        $gateway = $this->c->get(PaymentGateway::class); // hidden!
        // ...
    }
}

コンテナを置いてよい場所

コンテナを置いてよい層は1つだけです。それはコンポジションルートと、その周辺のフレームワーク連携部分(例:コントローラーファクトリー)です。ドメインとアプリケーションのクラスはコンテナから独立させ、コンストラクター経由で通常のオブジェクトを受け取り、手動でもインスタンス化できるようにしてください。よい確認方法は、コンテナなしで1つのPHPファイルにアプリケーション全体を接続できるかどうかです。できるなら、依存関係を正直に表現できています。

サービスロケーションを使わずに遅延接続する

依存関係の構築にコストがかかる場合や、条件によってのみ必要になる場合があります。コンテナを注入したくなっても、代わりにファクトリークロージャを注入してください。依存関係は明示的かつ型付きのまま、実際に使うまで構築を遅らせられます。

<?php
final class ReportService
{
    /** @param Closure():PaymentGateway $gatewayFactory */
    public function __construct(private Closure $gatewayFactory) {}

    public function refundIfNeeded(bool $needed): void {
        if (!$needed) return;
        $gateway = ($this->gatewayFactory)(); // built only when required
        // $gateway->charge(...) etc.
    }
}

// Composition root supplies the factory, not the container
$svc = new ReportService(fn() => new StripePaymentGateway($stripe ?? null));
echo 'lazy dependency wired', PHP_EOL;

簡単な確認

PaymentGatewayインターフェースは誰が所有すべきですか?

まとめ

アダプターを正しい方法でコアに接続できました。

  • 依存性逆転と依存性注入は異なります。注入は仕組みであり、逆転とは利用者が抽象を所有することです。
  • コアがインターフェースを定義し、インフラストラクチャのアダプターがそれに準拠します。
  • 抽象をコンストラクター注入し、具体的な接続は1つのコンポジションルート(またはインターフェースとクラスをバインドするコンテナ設定)でのみ行います。
  • アダプターの交換やテストでのフェイクへの切り替えを、1行の変更で行えるようになります。
  • サービスロケーターアンチパターンを避け、コンテナはエッジにだけ置いてください。

よくある質問

「依存性逆転の実践」レッスンは無料ですか?

はい。「依存性逆転の実践」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、PHP Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 PHP Academyコースには全4レッスンが含まれています。

「依存性逆転の実践」で何を学びますか?

依存性逆転でアダプターをコアに接続します。 ブラウザで直接実行するハンズオンコードでPHP Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

PHP Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのPHP Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。

「依存性逆転の実践」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このPHP Academyレッスンでコードを書いて実行できますか?

はい。すべてのPHP Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. レイヤードアーキテクチャからクリーンアーキテクチャへ
  2. ポートとアダプターの概要
  3. ユースケースとアプリケーションサービス
  4. 依存性逆転の実践
← PHP Academyに戻る