Menu
Coddy logo textTech

Паттерн Репозиторий

Часть раздела Объектно-ориентированное программирование путешествия по PHP на Coddy — урок 81 из 91.

Паттерн Репозиторий — это паттерн проектирования, который выступает посредником между бизнес-логикой вашего приложения и хранилищем данных. Он предоставляет интерфейс, подобный коллекции, для доступа к доменным объектам, скрывая детали того, как данные на самом деле сохраняются или извлекаются.

Представьте репозиторий как специализированную коллекцию, которая умеет сохранять и извлекать объекты. Код вашего приложения запрашивает у репозитория пользователя по ID или сохраняет новый продукт, не зная, где именно находятся данные: в базе данных, файле или API. Такое разделение делает ваш код более тестируемым и гибким.

<?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). Вы создадите четкое разделение между вашими доменными объектами и тем, как они хранятся, что позволит коду вашего приложения работать с товарами, не зная деталей хранения.

Вы организуете свой код в четырех файлах:

  • Product.php — Создайте класс Product, представляющий товары в вашем инвентаре. Каждый товар имеет id (int), name (string) и price (float). Используйте продвижение свойств в конструкторе (constructor promotion) с публичными свойствами для легкого доступа.
  • ProductRepositoryInterface.php — Определите интерфейс ProductRepositoryInterface, который устанавливает контракт для любой реализации хранилища товаров. Ваш интерфейс должен объявлять следующие методы:
    • find(int $id): ?Product — Получить товар по ID или null, если он не найден
    • findAll(): array — Получить все товары в виде массива
    • save(Product $product): void — Сохранить или обновить товар
    • delete(int $id): void — Удалить товар по ID
  • InMemoryProductRepository.php — Подключите и класс Product, и интерфейс. Создайте класс InMemoryProductRepository, который реализует интерфейс, используя приватный массив для хранения товаров. Товары должны быть индексированы по их ID для эффективного поиска. Метод findAll() должен возвращать только объекты товаров (используйте array_values() для сброса ключей массива).
  • main.php — Подключите файл репозитория. Вы получите два входных значения: строку JSON, содержащую массив товаров для добавления, и ID для поиска.

    Формат JSON будет следующим:

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

    Создайте экземпляр InMemoryProductRepository, сохраните все товары из входного JSON, затем используйте второе входное значение (ID), чтобы найти конкретный товар. Выведите результаты в следующем формате:

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

    Отформатируйте цену до двух знаков после запятой. Если товар не найден, выведите вместо этого Found: Not found.

Паттерн Репозиторий особенно полезен, когда вам нужно заменить реализацию хранилища. Ваш основной код зависит только от интерфейса — переход от хранения в оперативной памяти к базе данных будет означать создание нового класса репозитория без изменения логики приложения.

Шпаргалка

Паттерн Репозиторий (Repository Pattern) выступает посредником между бизнес-логикой и хранилищем данных, предоставляя интерфейс, подобный коллекции, для доступа к доменным объектам и скрывая детали реализации хранилища.

Определите интерфейс репозитория, который устанавливает контракт для доступа к данным:

<?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;
}

Создайте доменный класс, используя продвижение свойств в конструкторе (constructor promotion):

<?php
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"));
$user = $repo->find(1);
$allUsers = $repo->findAll();

Этот паттерн позволяет менять реализации без изменения бизнес-логики — ваш код зависит от интерфейса, а не от конкретной реализации.

Попробуйте сами

<?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: Получить все продукты и вывести общее количество
// Формат: "Total products: [count]"

// TODO: Найти продукт с $searchId
// Если найден, вывести: "Found: [name] - $[price]" (цена отформатирована до 2 знаков после запятой)
// Если не найден, вывести: "Found: Not found"

?>
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование