← Back to Module

Repository Pattern Explorer

See how Repository Pattern abstracts data sources and improves testability

What is the Repository Pattern?

The Repository Pattern creates an abstraction layer between your business logic (controllers, services) and your data access layer (Eloquent, API calls, cache).

❌ Without Repository

  • • Controller directly queries database
  • • Hard to test (needs database)
  • • Can't easily switch data sources
  • • Query logic scattered everywhere

✅ With Repository

  • • Controller talks to repository interface
  • • Easy to test (mock repository)
  • • Swap data sources transparently
  • • Centralized data access logic

Choose Data Source

Click each button to see how the Repository Pattern allows you to fetch from different sources without changing the controller code!

Fetching products...

Performance Metrics

Query Time
DB Queries
Controller Lines

Data Source

Products ()

ID Name Price Stock Category Source

Code Comparison

❌ Without Repository (25 lines)

class ProductController {
    public function index() {
        // Query logic in controller
        $products = Product::with('category')
            ->where('active', true)
            ->where('stock', '>', 0)
            ->orderBy('name')
            ->paginate(20);
        
        return view('products', [
            'products' => $products
        ]);
    }
}

// ❌ Problems:
// - Tightly coupled to Eloquent
// - Hard to test (needs database)
// - Can't switch to cache/API
// - Logic scattered across controllers

✅ With Repository (8 lines)

class ProductController {
    public function __construct(
        protected ProductRepositoryInterface $products
    ) {}
    
    public function index() {
        // Clean, simple, testable
        $products = $this->products->getAllActive();
        
        return view('products', [
            'products' => $products
        ]);
    }
}

// ✅ Benefits:
// - Loosely coupled to interface
// - Easy to test (mock repository)
// - Repository can use cache/API/DB
// - Logic centralized in one place

Repository Implementation Example

// 1. Define Interface
interface ProductRepositoryInterface {
    public function getAllActive(int $perPage = 20);
    public function findWithDetails(int $id);
    public function search(array $filters);
}

// 2. Implement Repository
class ProductRepository implements ProductRepositoryInterface {
    public function getAllActive(int $perPage = 20) {
        // Can add caching here transparently!
        return Cache::remember('products.active', 3600, function() {
            return Product::with('category')
                ->where('active', true)
                ->orderBy('name')
                ->paginate($perPage);
        });
    }
    
    public function findWithDetails(int $id) {
        return Product::with('reviews', 'images')->findOrFail($id);
    }
    
    public function search(array $filters) {
        $query = Product::query();
        
        if (!empty($filters['category'])) {
            $query->where('category_id', $filters['category']);
        }
        
        return $query->paginate(20);
    }
}

// 3. Bind in Service Provider
class AppServiceProvider extends ServiceProvider {
    public function register() {
        $this->app->bind(
            ProductRepositoryInterface::class,
            ProductRepository::class
        );
    }
}

Testing Comparison

Without Repository

Test Type: Feature Test

Setup: Requires database, migrations, seeders

Speed: ~500ms per test

Code: ~25 lines per test

  • ❌ Slow (database I/O)
  • ❌ Fragile (depends on DB state)
  • ❌ Hard to test edge cases

With Repository

Test Type: Unit Test

Setup: Mock the repository interface

Speed: ~5ms per test

Code: ~8 lines per test

  • ✅ Fast (no database)
  • ✅ Reliable (no dependencies)
  • ✅ Easy to test anything

When to Use Repository Pattern?

✅ Use When:

  • • Large, complex applications
  • • Multiple data sources (DB + API + Cache)
  • • Heavy business logic
  • • Need to swap implementations
  • • Strict testing requirements
  • • Long-term maintainability crucial

❌ Skip When:

  • • Simple CRUD applications
  • • Prototypes or MVPs
  • • Eloquent features are sufficient
  • • Small team, short timeline
  • • Straightforward data access
  • • Over-engineering risk