Menu
Coddy logo textTech

Repositoryパターン

CoddyのPHPジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 81/91。

Repository Patternは、アプリケーションのビジネスロジックとデータストレージの間を仲介するデザインパターンです。ドメインオブジェクトにアクセスするためのコレクションのようなインターフェースを提供し、データが実際にどのように保存または取得されるかという詳細を隠蔽します。

リポジトリを、オブジェクトを永続化して取得する方法を知っている専門的なコレクションだと考えてみてください。アプリケーションコードは、データがデータベース、ファイル、API のいずれに存在するかを知らなくても、ID でユーザーをリポジトリに問い合わせたり、新しい product を保存したりできます。この分離により、コードのテスト容易性と柔軟性が高まります。

<?php
interface UserRepositoryInterface {
    public function find(int $id): ?User;
    public function findAll(): array;
    public function save(User $user): void;
    public function delete(int $id): void;
}

class User {
    public function __construct(
        public int $id,
        public string $name,
        public string $email
    ) {}
}

リポジトリの実装は、実際のストレージ機構を処理します。

<?php
class InMemoryUserRepository implements UserRepositoryInterface {
    private array $users = [];
    
    public function find(int $id): ?User {
        return $this->users[$id] ?? null;
    }
    
    public function findAll(): array {
        return array_values($this->users);
    }
    
    public function save(User $user): void {
        $this->users[$user->id] = $user;
    }
    
    public function delete(int $id): void {
        unset($this->users[$id]);
    }
}

$repo = new InMemoryUserRepository();
$repo->save(new User(1, "Alice", "alice@example.com"));
$repo->save(new User(2, "Bob", "bob@example.com"));

echo $repo->find(1)->name . "\n";
echo count($repo->findAll());

出力:

Alice
2

このパターンの魅力は、ビジネスロジックを変更せずに実装を入れ替えられることです。インメモリストレージからデータベースに切り替える必要がありますか?同じインターフェースを実装する新しいリポジトリクラスを作成します。アプリケーションコードはインターフェースに依存しており、具体的な実装には依存していないため、そのまま変更する必要はありません。

challenge icon

チャレンジ

簡単

Repository Pattern を使用して商品在庫システムを構築しましょう。ドメインオブジェクトと、それらの保存方法を明確に分離することで、アプリケーションコードが保存の詳細を知らなくても商品を扱えるようにします。

コードを4つのファイルに整理します。

  • Product.php:在庫内の項目を表す Product class を作成します。各商品には id(int)、name(string)、price(float)があります。簡単にアクセスできるよう、public プロパティを使用したコンストラクタプロモーションを使います。
  • ProductRepositoryInterface.php:あらゆる商品保存実装の契約を定める ProductRepositoryInterface を定義します。interface には次の method を Declare します。
    • find(int $id): ?Product:ID で商品を取得します。見つからない場合は null を返します
    • findAll(): array:すべての商品を array として取得します
    • save(Product $product): void:商品を保存または更新します
    • delete(int $id): void:ID で商品を削除します
  • InMemoryProductRepository.php:Product class と interface の両方を含めます。商品を保存するための private array を使用して、interface を implements する InMemoryProductRepository class を作成します。効率的に検索できるよう、商品は ID をキーとして indexed にします。findAll() method は商品オブジェクトだけを返す必要があります(array_values() を使用して array のキーをリセットします)。
  • main.php:repository ファイルを含めます。2つの input を受け取ります。最初は追加する商品の JSON string、次の行には検索する integer ID です。

    JSON の形式は次のとおりです。

    [{"id": 1, "name": "Laptop", "price": 999.99}, {"id": 2, "name": "Mouse", "price": 29.99}]

    InMemoryProductRepository を作成し、JSON input のすべての商品を保存します。その後、2番目の input(ID)を使用して特定の商品を検索します。結果を次の形式で print します。

    Total products: [count]
    Found: [name] - $[price]

    price は小数点以下2桁に Format します。商品が見つからない場合は、代わりに Found: Not found を print します。

Repository Pattern は、保存実装を入れ替える必要がある場合に力を発揮します。main code は interface のみに依存するため、インメモリ保存から database への切り替えは、アプリケーションロジックを変更せずに新しい repository class を作成するだけで実現できます。

自分で試してみよう

<?php

require_once 'InMemoryProductRepository.php';

// 入力を読み取る
$jsonInput = trim(fgets(STDIN));
$searchId = intval(trim(fgets(STDIN)));

// JSON入力を配列にパースする
$productsData = (array)json_decode($jsonInput, true);

// TODO: InMemoryProductRepository のインスタンスを作成する

// TODO: $productsData をループして各商品を保存する
// 各アイテムには 'id'、'name'、'price' のキーがある

// TODO: すべての商品を取得して合計数を出力する
// Format: "Total products: [count]"

// TODO: $searchId の商品を見つける
// 見つかった場合、出力: "Found: [name] - $[price]" (価格は小数点以下2桁にフォーマット)
// If not found, print: "Found: Not found"

?>
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: PHPオンラインコンパイラ