Backend Development 9 min read

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.

php中文网 Courses
php中文网 Courses
php中文网 Courses
Implementing the Repository Pattern in Laravel: A Step‑by‑Step Guide

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.

design patternse-commerceBackend DevelopmentcachingPHPRepository PatternLaravel
php中文网 Courses
Written by

php中文网 Courses

php中文网's platform for the latest courses and technical articles, helping PHP learners advance quickly.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.