第07回: Repositoryパターン — DBの実装を「ビジネスロジックから隠す」

章: 第1章: オブジェクト指向の深化

コントローラにSQLを書いていませんか?

$stmt = $pdo->prepare('SELECT * FROM users WHERE ...') をコントローラやサービスクラスの中に直接書くと、DB実装とビジネスロジックが密結合します。

テストのたびにDBが必要になり、MySQLからPostgreSQLに移行する際はビジネスロジックまで書き換えが必要になります。

Repositoryパターンはデータアクセスの責務をビジネスロジックから切り離すことで、この問題を解決します。

Repositoryパターンの実装


<?php
// インターフェイス(データアクセスの契約)
interface UserRepository {
    public function findById(int $id): User;
    public function save(User $user): void;
}

// PDOによる具体実装
class PdoUserRepository implements UserRepository {
    public function __construct(private PDO $pdo) {}

    public function findById(int $id): User {
        $stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = ?');
        $stmt->execute([$id]);
        return new User($stmt->fetch());
    }

    public function save(User $user): void {
        $stmt = $this->pdo->prepare(
            'INSERT INTO users (name, email) VALUES (?, ?) ON DUPLICATE KEY UPDATE name=?, email=?'
        );
        $stmt->execute([$user->name, $user->email, $user->name, $user->email]);
    }
}

// テスト用のインメモリ実装
class InMemoryUserRepository implements UserRepository {
    private array $users = [];
    public function findById(int $id): User {
        return $this->users[$id] ?? throw new \RuntimeException('User not found');
    }
    public function save(User $user): void {
        $this->users[$user->id] = $user;
    }
}

直接PDO呼び出し vs Repositoryパターン

観点 直接PDO呼び出し Repositoryパターン
テスタビリティ DBが必須 InMemory実装に差し替え可能
DB移行 サービス全体を変更 Repository実装だけ変更
重複クエリ 各所に散らばる 一箇所に集約
ビジネスロジックの純粋さ DBの詳細が混入 DBを意識しなくてよい

チェックポイント: サービスクラスに $pdo->prepare() が出てきたら、Repositoryへの切り出しタイミングです。

サービスクラスとの組み合わせ


<?php
class UserService {
    public function __construct(private UserRepository $repo) {}

    public function getUser(int $id): User {
        return $this->repo->findById($id);
        // DBの詳細を一切知らなくていい
    }
}

// 本番環境
$service = new UserService(new PdoUserRepository($pdo));

// テスト環境
$service = new UserService(new InMemoryUserRepository());

チェックポイント: Repositoryのインターフェイスを定義してからサービスを書くと、テスト可能な設計が自然に生まれます。

まとめ & 次のステップ

  • Repositoryパターンはデータアクセス処理をビジネスロジックから分離する
  • インターフェイスを通じることで、本番とテストで実装を差し替えられる
  • サービスクラスはSQLやDB詳細を知らずに動作できる

次回はFactoryパターンを学びます。「オブジェクトの生成ロジックが複雑になってきた」ときに呼び出し元をシンプルに保つ方法です。

Related Articles