Patrón Repository
Parte de la sección Programación Orientada a Objetos del Journey de PHP de Coddy — lección 81 de 91.
El Patrón Repository es un patrón de diseño que media entre la lógica de negocio de su aplicación y el almacenamiento de datos. Proporciona una interfaz similar a una colección para acceder a los objetos de dominio, ocultando los detalles de cómo se almacenan o recuperan realmente los datos.
Piense en un repositorio como una colección especializada que sabe cómo persistir y recuperar objetos. El código de su aplicación le pide al repositorio un usuario por ID o guarda un nuevo producto, sin saber si los datos residen en una base de datos, un archivo o una API. Esta separación hace que su código sea más fácil de probar y más flexible.
<?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
) {}
}
La implementación del repositorio maneja el mecanismo de almacenamiento real:
<?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());
Salida:
Alice
2La belleza de este patrón es que puedes intercambiar implementaciones sin cambiar tu lógica de negocio. ¿Necesitas cambiar del almacenamiento en memoria a una base de datos? Crea una nueva clase de repositorio que implemente la misma interfaz. El código de tu aplicación permanece intacto porque depende de la interfaz, no de la implementación concreta.
Desafío
FácilVamos a construir un sistema de inventario de productos utilizando el Patrón Repository. Crearás una separación clara entre tus objetos de dominio y cómo se almacenan, permitiendo que el código de tu aplicación trabaje con productos sin conocer los detalles del almacenamiento.
Organizarás tu código en cuatro archivos:
Product.php— Crea una claseProductque represente los artículos en tu inventario. Cada producto tiene unid(int),name(string) yprice(float). Utiliza la promoción de constructor con propiedades públicas para un acceso fácil.ProductRepositoryInterface.php— Define unaProductRepositoryInterfaceque establezca el contrato para cualquier implementación de almacenamiento de productos. Tu interfaz debe declarar estos métodos:find(int $id): ?Product— Recuperar un producto por ID, o null si no se encuentrafindAll(): array— Obtener todos los productos como un arraysave(Product $product): void— Almacenar o actualizar un productodelete(int $id): void— Eliminar un producto por ID
InMemoryProductRepository.php— Incluye tanto la clase Product como la interfaz. Crea una claseInMemoryProductRepositoryque implemente la interfaz utilizando un array privado para almacenar los productos. Los productos deben estar indexados por su ID para una búsqueda eficiente. El métodofindAll()debe devolver solo los objetos de producto (usaarray_values()para restablecer las claves del array).main.php— Incluye el archivo del repositorio. Recibirás una entrada: una cadena JSON que contiene un array de productos para agregar, seguida de un ID para buscar.El formato JSON será:
[{"id": 1, "name": "Laptop", "price": 999.99}, {"id": 2, "name": "Mouse", "price": 29.99}]Crea un
InMemoryProductRepository, guarda todos los productos de la entrada JSON, luego usa la segunda entrada (el ID) para encontrar un producto específico. Imprime los resultados en este formato:Total products: [count] Found: [name] - $[price]Formatea el precio con dos decimales. Si el producto no se encuentra, imprime
Found: Not founden su lugar.
El Patrón Repository destaca cuando necesitas intercambiar implementaciones de almacenamiento. Tu código principal depende solo de la interfaz; cambiar del almacenamiento en memoria a una base de datos significaría crear una nueva clase de repositorio sin cambiar ninguna lógica de la aplicación.
Hoja de referencia
El Patrón de Repositorio actúa como mediador entre la lógica de negocio y el almacenamiento de datos, proporcionando una interfaz similar a una colección para acceder a objetos de dominio mientras oculta los detalles de implementación del almacenamiento.
Define una interfaz de repositorio que establezca el contrato para el acceso a datos:
<?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;
}Crea una clase de dominio utilizando la promoción de propiedades en el constructor:
<?php
class User {
public function __construct(
public int $id,
public string $name,
public string $email
) {}
}Implementa la interfaz del repositorio con un mecanismo de almacenamiento concreto:
<?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]);
}
}Utiliza el repositorio a través de su interfaz:
$repo = new InMemoryUserRepository();
$repo->save(new User(1, "Alice", "alice@example.com"));
$user = $repo->find(1);
$allUsers = $repo->findAll();El patrón permite intercambiar implementaciones sin cambiar la lógica de negocio; su código depende de la interfaz, no de la implementación concreta.
Pruébalo tú mismo
<?php
require_once 'InMemoryProductRepository.php';
// Leer la entrada
$jsonInput = trim(fgets(STDIN));
$searchId = intval(trim(fgets(STDIN)));
// Analizar la entrada JSON en un array
$productsData = (array)json_decode($jsonInput, true);
// TODO: Crear una instancia de InMemoryProductRepository
// TODO: Recorrer $productsData y guardar cada producto
// Cada elemento tiene las claves 'id', 'name' y 'price'
// TODO: Obtener todos los productos e imprimir el recuento total
// Formato: "Total products: [count]"
// TODO: Buscar el producto con $searchId
// Si se encuentra, imprimir: "Found: [name] - $[price]" (precio formateado a 2 decimales)
// Si no se encuentra, imprimir: "Found: Not found"
?>Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosIntroducción a OOPClases vs ObjetosLa palabra clave $thisMétodosPropiedadesConstructor (__construct)Destructor (__destruct)Resumen - Calculadora simple4Herencia
Herencia básicaLa palabra clave parent::Sobrescritura de métodosLa palabra clave finalClases abstractasResumen - Jerarquía de empleados7Encapsulamiento
Público, Protegido, PrivadoModificadores de acceso a fondoGetters y SettersOcultamiento de informaciónPromoción de constructor (8.0)Resumen - Sistema de registros de estudiantes10Conceptos avanzados de POO
Composición vs. HerenciaInyección de dependenciasClases anónimasEnums (PHP 8.1)Fibers (PHP 8.1)Clonación de objetos a fondoGeneradores e Iteradores13Patrones de diseño Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositePatrón Repository2Espacios de nombres y Autoloading
Introducción a los espacios de nombresLa palabra clave useEstándar de Autoloading PSR-4Autoloader de ComposerResumen - Proyecto organizado5Interfaces y Contratos
Introducción a las InterfacesImplementación de InterfacesImplementación de Múltiples InterfacesInterfaz vs Clase AbstractaType Hinting con InterfacesResumen - Calculadora de Formas8Métodos mágicos
Introducción a los métodos mágicos__toString y __debugInfo__get, __set, __isset, __unset__call y __callStatic__clone y clonación de objetos__serialize y __unserializeResumen - Colección personalizada3Propiedades de clase
Propiedades de instancia vs. estáticasConstantes en clasesMétodos y propiedades estáticosPropiedades privadas y protegidasPropiedades de solo lectura (PHP 8.1)Resumen - Gestor de cuenta bancaria6Polimorfismo
Sobrescritura de métodos revisadaPolimorfismo mediante interfacesType Hinting y tipos de uniónEnlace estático tardíoResumen - Procesador de pagos9Traits
Introducción a los TraitsUso de múltiples TraitsResolución de conflictos de TraitsMétodos abstractos en TraitsTraits frente a herencia12Patrones de diseño Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón ObserverPatrón Strategy