章: 第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パターンを学びます。「オブジェクトの生成ロジックが複雑になってきた」ときに呼び出し元をシンプルに保つ方法です。