Menu
Coddy logo textTech

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
2

The 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 icon

Challenge

Easy

Let'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 a Product class representing items in your inventory. Each product has an id (int), name (string), and price (float). Use constructor promotion with public properties for easy access.
  • ProductRepositoryInterface.php — Define a ProductRepositoryInterface that 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 found
    • findAll(): array — Get all products as an array
    • save(Product $product): void — Store or update a product
    • delete(int $id): void — Remove a product by ID
  • InMemoryProductRepository.php — Include both the Product class and the interface. Create an InMemoryProductRepository class that implements the interface using a private array to store products. Products should be indexed by their ID for efficient lookup. The findAll() method should return just the product objects (use array_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 found instead.

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"

?>
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming