Repository Pattern
Part of the Object Oriented Programming section of Coddy's PHP journey — lesson 81 of 91.
The Repository Pattern is a design pattern that mediates between your application's business logic and data storage. It provides a collection-like interface for accessing domain objects, hiding the details of how data is actually stored or retrieved.
Think of a repository as a specialized collection that knows how to persist and retrieve objects. Your application code asks the repository for a user by ID or saves a new product, without knowing whether the data lives in a database, file, or API. This separation makes your code more testable and 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
) {}
}
The repository implementation handles the actual storage mechanism:
<?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());
Output:
Alice
2The beauty of this pattern is that you can swap implementations without changing your business logic. Need to switch from in-memory storage to a database? Create a new repository class implementing the same interface. Your application code remains untouched because it depends on the interface, not the concrete implementation.
Challenge
EasyLet's build a product inventory system using the Repository Pattern. You'll create a clean separation between your domain objects and how they're stored, allowing your application code to work with products without knowing the storage details.
You'll organize your code across four files:
Product.php— Create aProductclass representing items in your inventory. Each product has anid(int),name(string), andprice(float). Use constructor promotion with public properties for easy access.ProductRepositoryInterface.php— Define aProductRepositoryInterfacethat establishes the contract for any product storage implementation. Your interface should declare these methods:find(int $id): ?Product— Retrieve a product by ID, or null if not foundfindAll(): array— Get all products as an arraysave(Product $product): void— Store or update a productdelete(int $id): void— Remove a product by ID
InMemoryProductRepository.php— Include both the Product class and the interface. Create anInMemoryProductRepositoryclass that implements the interface using a private array to store products. Products should be indexed by their ID for efficient lookup. ThefindAll()method should return just the product objects (usearray_values()to reset array keys).main.php— Include the repository file. You'll receive one input: a JSON string containing an array of products to add, followed by an ID to search for.The JSON format will be:
[{"id": 1, "name": "Laptop", "price": 999.99}, {"id": 2, "name": "Mouse", "price": 29.99}]Create an
InMemoryProductRepository, save all products from the JSON input, then use the second input (the ID) to find a specific product. Print the results in this format:Total products: [count] Found: [name] - $[price]Format the price to two decimal places. If the product isn't found, print
Found: Not foundinstead.
The Repository Pattern shines when you need to swap storage implementations. Your main code depends only on the interface — switching from in-memory storage to a database would mean creating a new repository class without changing any application logic.
Cheat sheet
The Repository Pattern mediates between business logic and data storage, providing a collection-like interface for accessing domain objects while hiding storage implementation details.
Define a repository interface that establishes the contract for data access:
<?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;
}Create a domain class using constructor promotion:
<?php
class User {
public function __construct(
public int $id,
public string $name,
public string $email
) {}
}Implement the repository interface with a concrete storage mechanism:
<?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]);
}
}Use the repository through its interface:
$repo = new InMemoryUserRepository();
$repo->save(new User(1, "Alice", "alice@example.com"));
$user = $repo->find(1);
$allUsers = $repo->findAll();The pattern allows swapping implementations without changing business logic — your code depends on the interface, not the concrete implementation.
Try it yourself
<?php
require_once 'InMemoryProductRepository.php';
// Read input
$jsonInput = trim(fgets(STDIN));
$searchId = intval(trim(fgets(STDIN)));
// Parse the JSON input into an array
$productsData = (array)json_decode($jsonInput, true);
// TODO: Create an InMemoryProductRepository instance
// TODO: Loop through $productsData and save each product
// Each item has 'id', 'name', and 'price' keys
// TODO: Get all products and print the total count
// Format: "Total products: [count]"
// TODO: Find the product with $searchId
// If found, print: "Found: [name] - $[price]" (price formatted to 2 decimal places)
// If not found, print: "Found: Not found"
?>This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesIntroduction to OOPClasses vs ObjectsThe $this KeywordMethodsPropertiesConstructor (__construct)Destructor (__destruct)Recap - Simple Calculator4Inheritance
Basic InheritanceThe parent:: KeywordMethod OverridingThe final KeywordAbstract ClassesRecap - Employee Hierarchy7Encapsulation
Public, Protected, PrivateAccess Modifiers in DepthGetters and SettersInformation HidingConstructor Promotion (8.0)Recap - Student Records System10Advanced OOP Concepts
Composition vs InheritanceDependency InjectionAnonymous ClassesEnums (PHP 8.1)Fibers (PHP 8.1)Object Cloning Deep DiveGenerators & Iterators13Design Patterns Part 2
Command PatternAdapter PatternDecorator PatternTemplate Method PatternState PatternComposite PatternRepository Pattern2Namespaces & Autoloading
Introduction to NamespacesThe use KeywordPSR-4 Autoloading StandardComposer AutoloaderRecap - Organized Project5Interfaces & Contracts
Introduction to InterfacesImplementing InterfacesMultiple Interface ImplementInterface vs Abstract ClassType Hinting with InterfacesRecap - Shape Calculator8Magic Methods
Magic Methods Introduction__toString & __debugInfo__get, __set, __isset, __unset__call & __callStatic__clone & Object Cloning__serialize & __unserializeRecap - Custom Collection11Type System & Error Handling
Type DeclarationsNullable TypesUnion & Intersection TypesException ClassesCustom Exception HierarchyTry, Catch, FinallyRecap - Form Validator14Project: Library Management
Project OverviewBook and User Classes3Class Properties
Instance vs Static PropertiesConstants in ClassesStatic Methods & PropertiesPrivate & Protected PropertiesReadonly Properties (PHP 8.1)Recap - Bank Account Manager6Polymorphism
Method Overriding RevisitedPolymorphism via InterfacesType Hinting & Union TypesLate Static BindingRecap - Payment Processor9Traits
Introduction to TraitsUsing Multiple TraitsTrait Conflict ResolutionAbstract Methods in TraitsTraits vs Inheritance12Design Patterns Part 1
Intro to Design PatternsSingleton PatternFactory PatternObserver PatternStrategy Pattern