Menu
Coddy logo textTech

리포지터리 패턴

Coddy PHP 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 91개 중 81번째.

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

이 패턴의 장점은 비즈니스 로직을 변경하지 않고도 구현을 교체할 수 있다는 것입니다. 인메모리 저장소에서 데이터베이스로 전환해야 하나요? 동일한 interface를 구현하는 새로운 저장소 class를 Create하세요. 애플리케이션 코드는 구체적인 구현이 아니라 interface에 의존하므로 그대로 유지됩니다.

challenge icon

챌린지

쉬움

Repository Pattern을 사용하여 product inventory system을 구축해 봅시다. domain objects와 저장 방식 사이를 깔끔하게 분리하여, 애플리케이션 코드가 저장소 세부 사항을 알지 않고도 products를 다룰 수 있도록 합니다.

코드를 네 개의 파일로 구성합니다:

  • Product.php: inventory의 항목을 나타내는 Product class를 Create합니다. 각 product에는 id (int), name (string), price (float)가 있습니다. 쉽게 액세스할 수 있도록 public properties를 사용하는 constructor promotion을 사용합니다.
  • ProductRepositoryInterface.php: 모든 product 저장 구현이 따라야 하는 contract를 정의하는 ProductRepositoryInterface를 선언합니다. interface에는 다음 method들을 Declare해야 합니다:
    • find(int $id): ?Product: ID로 product를 가져오거나, 찾지 못한 경우 null을 반환합니다
    • findAll(): array: 모든 products를 array로 가져옵니다
    • save(Product $product): void: product를 저장하거나 업데이트합니다
    • delete(int $id): void: ID로 product를 제거합니다
  • InMemoryProductRepository.php: Product class와 interface를 모두 포함합니다. private array를 사용하여 products를 저장하고 interface를 implements하는 InMemoryProductRepository class를 Create합니다. 효율적인 조회를 위해 products는 ID로 indexed되어야 합니다. findAll() method는 product objects만 반환해야 합니다(array keys를 재설정하려면 array_values()를 사용).
  • main.php: repository file을 포함합니다. 두 개의 input을 받습니다. 먼저 추가할 products의 JSON string을 받고, 다음 줄에서 검색할 integer ID를 받습니다.

    JSON 형식은 다음과 같습니다:

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

    InMemoryProductRepository를 Create하고, JSON input의 모든 products를 save한 다음, 두 번째 input(ID)을 사용하여 특정 product를 find합니다. 결과를 다음 형식으로 print합니다:

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

    price를 소수점 이하 두 자리로 Format합니다. product를 찾지 못한 경우에는 대신 Found: Not found를 print합니다.

Repository Pattern은 저장 구현을 교체해야 할 때 특히 유용합니다. main code는 interface에만 의존하므로, in-memory storage에서 database로 전환하려면 애플리케이션 로직을 변경하지 않고 새로운 repository class를 Create하면 됩니다.

직접 해보기

<?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 컴파일러