Implementing the Repository Pattern in Laravel: A Step‑by‑Step Guide
This guide explains the repository pattern for Laravel, detailing its benefits, step‑by‑step implementation for an e‑commerce product management module—including model, interface, repository, controller code—and offers best practices, caching tips, and common pitfalls to improve code structure, maintainability, and testability.
Laravel applications can become difficult to maintain as they grow. The repository pattern introduces an abstraction layer between business logic and data access, allowing all database interactions to be handled by dedicated repository classes instead of being scattered across controllers.
What is the Repository Pattern?
It is a design pattern that acts as a bridge between the application and the database, centralizing data operations and improving code organization.
Why Use It?
Benefits include clearer code structure, higher maintainability, better code reuse, easier testing, and greater flexibility to switch data sources without changing business logic.
Practical Example: E‑commerce Product Management
The following steps demonstrate a complete implementation.
Step 1: Create the Product Model
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Product extends Model
{
use HasFactory;
protected $fillable = [
'name',
'description',
'price',
'sku',
'stock_quantity',
'status'
];
public function category()
{
return $this->belongsTo(Category::class);
}
public function inventory()
{
return $this->hasOne(Inventory::class);
}
}Step 2: Define the Repository Interface
<?php
namespace App\Repositories\Interfaces;
interface ProductRepositoryInterface
{
public function getAllProducts();
public function getProductById($productId);
public function getActiveProducts();
public function createProduct(array $productData);
public function updateProduct($productId, array $productData);
public function deleteProduct($productId);
public function getProductsByCategory($categoryId);
public function updateStock($productId, $quantity);
}Step 3: Implement the Repository
<?php
namespace App\Repositories;
use App\Models\Product;
use App\Repositories\Interfaces\ProductRepositoryInterface;
use Illuminate\Support\Facades\Cache;
class ProductRepository implements ProductRepositoryInterface
{
protected $model;
protected $cacheTimeout = 3600; // 1 hour
public function __construct(Product $product)
{
$this->model = $product;
}
public function getAllProducts()
{
return Cache::remember('all_products', $this->cacheTimeout, function () {
return $this->model
->with(['category', 'inventory'])
->orderBy('created_at', 'desc')
->get();
});
}
public function getProductById($productId)
{
return Cache::remember("product_{$productId}", $this->cacheTimeout, function () use ($productId) {
return $this->model
->with(['category', 'inventory'])
->findOrFail($productId);
});
}
public function getActiveProducts()
{
return $this->model
->where('status', 'active')
->where('stock_quantity', '>', 0)
->with(['category', 'inventory'])
->orderBy('created_at', 'desc')
->get();
}
public function createProduct(array $productData)
{
$product = $this->model->create([
'name' => $productData['name'],
'description' => $productData['description'],
'price' => $productData['price'],
'sku' => $productData['sku'],
'stock_quantity' => $productData['stock_quantity'],
'status' => $productData['status'] ?? 'active'
]);
if (isset($productData['category_id'])) {
$product->category()->associate($productData['category_id']);
}
$this->clearProductCache();
return $product;
}
public function updateProduct($productId, array $productData)
{
$product = $this->getProductById($productId);
$product->update($productData);
if (isset($productData['category_id'])) {
$product->category()->associate($productData['category_id']);
$product->save();
}
$this->clearProductCache($productId);
return $product;
}
public function deleteProduct($productId)
{
$product = $this->getProductById($productId);
$this->clearProductCache($productId);
return $product->delete();
}
public function getProductsByCategory($categoryId)
{
return Cache::remember("category_products_{$categoryId}", $this->cacheTimeout, function () use ($categoryId) {
return $this->model
->where('category_id', $categoryId)
->with('inventory')
->get();
});
}
public function updateStock($productId, $quantity)
{
$product = $this->getProductById($productId);
$product->stock_quantity = $quantity;
$product->save();
$this->clearProductCache($productId);
return $product;
}
protected function clearProductCache($productId = null)
{
Cache::forget('all_products');
if ($productId) {
Cache::forget("product_{$productId}");
}
}
}Step 4: Use the Repository in a Controller
<?php
namespace App\Http\Controllers;
use App\Http\Requests\ProductRequest;
use App\Repositories\Interfaces\ProductRepositoryInterface;
class ProductController extends Controller
{
private $productRepository;
public function __construct(ProductRepositoryInterface $productRepository)
{
$this->productRepository = $productRepository;
}
public function index()
{
$products = $this->productRepository->getAllProducts();
return view('products.index', compact('products'));
}
public function store(ProductRequest $request)
{
$product = $this->productRepository->createProduct($request->validated());
return redirect()
->route('products.show', $product->id)
->with('success', '产品创建成功');
}
public function updateStock($id, Request $request)
{
$this->validate($request, [
'quantity' => 'required|integer|min:0'
]);
$product = $this->productRepository->updateStock($id, $request->quantity);
return response()->json([
'message' => '库存更新成功',
'new_quantity' => $product->stock_quantity
]);
}
}Best Practices & Tips
Use caching for frequently accessed data and clear it after updates.
Keep each repository focused on a single model and avoid mixing business logic.
Apply type‑hinting for repository interfaces and return types.
Handle errors with try‑catch blocks, return clear messages, and log important issues.
Common Pitfalls
Always define an interface for the repository to enable mocking and testing.
Avoid over‑design; keep repository methods concise and delegate complex logic to service layers.
Do not embed presentation logic in repositories.
Monitor query performance and use eager loading and caching for heavy queries.
Conclusion
The repository pattern is a powerful tool for organizing the data‑access layer of Laravel applications, enhancing maintainability, testability, and flexibility. Start with a simple implementation and gradually refactor existing code to adopt repositories, aiming for clearer and more maintainable code without unnecessary over‑engineering.
php中文网 Courses
php中文网's platform for the latest courses and technical articles, helping PHP learners advance quickly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.