0Pricing
PHP Academy · レッスン

ポートとアダプターの概要

ポートと交換可能なアダプターでコアを分離します。

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

ヘキサゴンの考え方

Alistair Cockburnが提唱したPorts & Adapters、つまりヘキサゴナルアーキテクチャでは、アプリケーションを六角形として描きます。内部には純粋なビジネスロジックがあります。外部とのあらゆるやり取り(HTTP、DB、キュー、時計、メール)はポートを通過し、各ポートは1つ以上のアダプターによって満たされます。この形には上や下といった特別な位置はありません。UIとデータベースは対称であり、どちらも単なるアダプターです。

ポートはインターフェース

ポートはアプリケーションコアが所有するインターフェースで、ドメインの用語で必要性や能力を表現します。インフラストラクチャの用語を漏らしてはいけません。PDOStatementもGuzzleResponseもEloquentも使いません。

<?php
// Driven (outbound) port: the core needs to persist users
interface UserRepository
{
    public function byId(UserId $id): ?User;
    public function save(User $user): void;
}

駆動ポートと被駆動ポート

ポートには2種類あります。

  • 駆動(プライマリ、インバウンド)ポート — 外部がアプリケーションを駆動するために呼び出すAPIです。通常はユースケースのインターフェースです。
  • 被駆動(セカンダリ、アウトバウンド)ポート — アプリケーションが外部世界に到達するために呼び出すインターフェースです。リポジトリ、メーラー、時計などが該当します。

駆動アダプターはコアを呼び出し、コアは被駆動アダプターを通じて外部を呼び出します。

<?php
// Driving (inbound) port — the public capability of the core
interface RegisterUser
{
    public function handle(string $email, string $plainPassword): UserId;
}

コアが駆動ポートを実装する

ユースケースは駆動ポートを実装し、被駆動ポートに依存します。ここで注目すべき点は、注入されるポートとしてPasswordHasherとClockを受け取ることです。時間やハッシュ化さえも抽象化することで、コアを決定的かつテスト可能に保ちます。

<?php
final class RegisterUserService implements RegisterUser
{
    public function __construct(
        private UserRepository $users,
        private PasswordHasher $hasher,
        private Clock $clock,
    ) {}

    public function handle(string $email, string $plain): UserId {
        if ($this->users->byEmail($email)) {
            throw new EmailAlreadyTaken($email);
        }
        $user = User::register(
            $email,
            $this->hasher->hash($plain),
            $this->clock->now()
        );
        $this->users->save($user);
        return $user->id();
    }
}

被駆動アダプター

被駆動アダプターは、具体的な技術を使って被駆動ポートを実装します。ここではPDOアダプターがUserRepositoryを満たしています。コアに触れることなく、Doctrine、Redis、HTTP APIクライアントに置き換えられます。

<?php
final class PdoUserRepository implements UserRepository
{
    public function __construct(private PDO $pdo) {}

    public function byId(UserId $id): ?User {
        $stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = ?');
        $stmt->execute([(string) $id]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        return $row ? User::fromRow($row) : null;
    }
    public function save(User $user): void {
        // INSERT ... ON CONFLICT UPDATE
    }
}

駆動アダプター

駆動アダプターは、外部からのトリガーを駆動ポートへの呼び出しに変換します。HTTPコントローラー、CLIコマンド、メッセージコンシューマーはすべて、同じユースケースに対して交換可能な駆動アダプターです。

<?php
// CLI driving adapter
final class RegisterUserCommand
{
    public function __construct(private RegisterUser $register) {}

    public function run(array $argv): int {
        [$email, $password] = array_slice($argv, 1);
        $id = $this->register->handle($email, $password);
        fwrite(STDOUT, "Created user $id\n");
        return 0;
    }
}

テスト用のインメモリアダプター

最大の利点は、すべての被駆動ポートに高速なフェイクを用意できることです。テストでは、インメモリアダプター、決定的な時計、何もしないハッシャーを使って、本物のユースケースを実行します。

<?php
final class FixedClock implements Clock {
    public function __construct(private DateTimeImmutable $t) {}
    public function now(): DateTimeImmutable { return $this->t; }
}
final class PlainHasher implements PasswordHasher {
    public function hash(string $p): string { return 'h:' . $p; }
}

$service = new RegisterUserService(
    new InMemoryUsers(),
    new PlainHasher(),
    new FixedClock(new DateTimeImmutable('2026-01-01'))
);
echo 'wired OK', PHP_EOL;

アダプターは翻訳するが、判断しない

よくある間違いは、ビジネスルールをアダプターに漏れ込ませることです。原則として、アダプターが行うのはデータ形式とプロトコルの変換だけです。コントローラーやリポジトリ内に、価格、利用資格、ステータスについてのifがあるなら、それはコアに置くべきです。

  • JSON ↔ DTOのマッピング:アダプター
  • SQL ↔ エンティティへのデータ注入:アダプター
  • 「VIPは10%割引」:コア

1つのポート、多数のアダプター

ポートを使うと、置き換えや並列アダプターの構成が可能になります。NotificationPortには、メール、SMS、Slack用のアダプターを組み合わせて持たせられます。コアは1つのメソッドを呼び出し、いくつのチャネルが応答するかは配線によって決まります。

<?php
interface Notifier { public function send(string $to, string $msg): void; }

final class CompositeNotifier implements Notifier {
    /** @param Notifier[] $channels */
    public function __construct(private array $channels) {}
    public function send(string $to, string $msg): void {
        foreach ($this->channels as $c) $c->send($to, $msg);
    }
}

$notifier = new CompositeNotifier([new EmailNotifier(), new SmsNotifier()]);
echo 'composed', PHP_EOL;

六角形アーキテクチャをフォルダーに対応付ける

境界づけられたコンテキストに適した、実用的なPHPの構成は次のとおりです。

  • Domain/ — エンティティ、値オブジェクト、ドメインサービス
  • Application/Port/In/ — 駆動ポートのインターフェース(ユースケース)
  • Application/Port/Out/ — 被駆動ポートのインターフェース(リポジトリ、クロック)
  • Application/ — ユースケースの実装
  • Infrastructure/Adapter/In/ — コントローラー、CLI、コンシューマー
  • Infrastructure/Adapter/Out/ — PDO/Doctrine/HTTPアダプター

コンポジションルート(DIコンテナの設定)がInとOutのアダプターをポートに接続します。

六角形アーキテクチャ全体をテストする

ユニットテストに加えて、ポートを使うと、アプリケーションを主ポート経由で動かし、インメモリの副次アダプターを通じて検証する高速な受け入れテストを実施できます。HTTPやデータベースを使わずに、ユースケース全体をカバーできます。同じテストスイートを後から実際のアダプターに対して統合テストとして実行できるため、書き直しなしで段階的なテスト戦略を構築できます。

<?php
// Acceptance test: real use case, fake driven adapters, no I/O
$users = new InMemoryUsers();
$service = new RegisterUserService($users, new PlainHasher(),
    new FixedClock(new DateTimeImmutable('2026-01-01')));

$id = $service->handle('dev@coddykit.com', 'pw');

assert($users->byId($id) !== null);
echo 'acceptance: user persisted via in-memory adapter', PHP_EOL;

簡単な確認

ポートとアダプターについて正しい説明はどれですか?

まとめ

Ports & Adaptersは、インターフェースを介してコアを隔離します。

  • ポートは、コアが所有するドメイン用語によるインターフェースです。
  • 駆動ポートは受信アダプターから呼び出され、被駆動ポートはコアから呼び出されて、送信アダプターによって満たされます。
  • アダプターはプロトコルと形式を変換するだけで、ビジネス上の判断は決して行いません。
  • 同じポートで多数のアダプターを利用できます(テスト用のフェイク、ファンアウト用の複合アダプターなど)。
  • コンポジションルートがすべてを接続し、六角形アーキテクチャはフレームワークから独立したままになります。

よくある質問

「ポートとアダプターの概要」レッスンは無料ですか?

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

「ポートとアダプターの概要」で何を学びますか?

ポートと交換可能なアダプターでコアを分離します。 ブラウザで直接実行するハンズオンコードでPHP Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

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

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

「ポートとアダプターの概要」レッスンにはどのくらい時間がかかりますか?

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

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

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

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

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