See how Repository Pattern abstracts data sources and improves testability
The Repository Pattern creates an abstraction layer between your business logic (controllers, services) and your data access layer (Eloquent, API calls, cache).
Click each button to see how the Repository Pattern allows you to fetch from different sources without changing the controller code!
Fetching products...
| ID | Name | Price | Stock | Category | Source |
|---|---|---|---|---|---|
| Real-time |
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
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
// 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
);
}
}
Test Type: Feature Test
Setup: Requires database, migrations, seeders
Speed: ~500ms per test
Code: ~25 lines per test
Test Type: Unit Test
Setup: Mock the repository interface
Speed: ~5ms per test
Code: ~8 lines per test