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.

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

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

<?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Проверьте себя

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

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