Паттерн Репозиторий
Часть раздела Объектно-ориентированное программирование путешествия по 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Прелесть этого паттерна в том, что вы можете менять реализации, не затрагивая бизнес-логику. Нужно переключиться с хранения в памяти на базу данных? Создайте новый класс репозитория, реализующий тот же интерфейс. Код вашего приложения остается неизменным, поскольку он зависит от интерфейса, а не от конкретной реализации.
Задание
ЛегкоДавайте создадим систему инвентаризации товаров, используя паттерн Репозиторий (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"
?>В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыВведение в ООПКлассы и объектыКлючевое слово $thisМетодыСвойстваКонструктор (__construct)Деструктор (__destruct)Итоги — Простой калькулятор4Наследование
Основы наследованияКлючевое слово parent::Переопределение методовКлючевое слово finalАбстрактные классыИтоги — Иерархия сотрудников7Инкапсуляция
Public, Protected, PrivateПодробный разбор модификаторов доступаГеттеры и сеттерыСокрытие данныхConstructor Promotion (8.0)Итоги — Система учета студентов10Продвинутые концепции ООП
Композиция против наследованияВнедрение зависимостейАнонимные классыПеречисления (PHP 8.1)Файберы (PHP 8.1)Глубокое погружение в клонирование объектовГенераторы и итераторы13Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикПаттерн Репозиторий2Пространства имен и автозагрузка
Введение в пространства именКлючевое слово useСтандарт автозагрузки PSR-4Автозагрузчик ComposerИтоги: структурированный проект5Интерфейсы и контракты
Введение в интерфейсыРеализация интерфейсовРеализация нескольких интерфейсовИнтерфейс против абстрактного классаType Hinting с интерфейсамиИтоги — Калькулятор фигур8Магические методы
Введение в магические методы__toString и __debugInfo__get, __set, __isset, __unset__call и __callStatic__clone и клонирование объектов__serialize и __unserializeИтоги — Кастомная коллекция11Система типов и обработка ошибок
Объявление типовNullable-типыТипы объединения и пересеченияКлассы исключенийИерархия пользовательских исключенийTry, Catch, FinallyИтоги — Валидатор форм14Проект: Управление библиотекой
Обзор проектаКлассы Book и User3Свойства классов
Свойства экземпляра и статические свойстваКонстанты в классахСтатические методы и свойстваПриватные и защищенные свойстваСвойства Readonly (PHP 8.1)Итоги — Менеджер банковского счета6Полиморфизм
Снова о переопределении методовПолиморфизм через интерфейсыType Hinting и Union TypesПозднее статическое связываниеИтоги — Payment Processor9Трейты
Введение в трейтыИспользование нескольких трейтовРазрешение конфликтов трейтовАбстрактные методы в трейтахТрейты и наследование12Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн Strategy