Laravel 中的 Repository Pattern:清理你的混乱代码

发布: (2025年12月28日 GMT+8 19:55)
5 min read
原文: Dev.to

Source: Dev.to

Repository Pattern in Laravel: Clean Up Your Messy Code 的封面图片

Laravel Mastery

问题

见过这样的控制器吗?

public class OrderController extends Controller
{
    public function show($id)
    {
        $order = Order::with(['customer', 'items.product'])
            ->where('id', $id)
            ->first();

        return response()->json($order);
    }

    public function getUserOrders($userId)
    {
        // Same query duplicated! 😱
        $orders = Order::with(['customer', 'items.product'])
            ->where('customer_id', $userId)
            ->get();

        return response()->json($orders);
    }
}

问题

  • 🔴 到处都是重复的查询
  • 🔴 控制器与 Eloquent 紧密耦合
  • 🔴 没有数据库无法进行测试
  • 🔴 业务逻辑与数据访问混在一起

解决方案:仓储模式

步骤 1 – 创建接口

interface OrderRepositoryInterface
{
    public function find(int $id): ?Order;
    public function findWithRelations(int $id): ?Order;
    public function findByCustomer(int $customerId): Collection;
}

步骤 2 – 实现仓储

class OrderRepository implements OrderRepositoryInterface
{
    protected $model;

    public function __construct(Order $model)
    {
        $this->model = $model;
    }

    public function findWithRelations(int $id): ?Order
    {
        return $this->model
            ->with(['customer', 'items.product'])
            ->find($id);
    }

    public function findByCustomer(int $customerId): Collection
    {
        return $this->model
            ->with(['customer', 'items.product'])
            ->where('customer_id', $customerId)
            ->orderBy('created_at', 'desc')
            ->get();
    }
}

步骤 3 – 在服务提供者中注册

class RepositoryServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->bind(
            OrderRepositoryInterface::class,
            OrderRepository::class
        );
    }
}

步骤 4 – 精简控制器

class OrderController extends Controller
{
    protected $orderRepository;

    public function __construct(OrderRepositoryInterface $orderRepository)
    {
        $this->orderRepository = $orderRepository;
    }

    public function show(int $id)
    {
        $order = $this->orderRepository->findWithRelations($id);

        if (! $order) {
            return response()->json(['message' => 'Not found'], 404);
        }

        return response()->json($order);
    }

    public function getUserOrders(int $userId)
    {
        $orders = $this->orderRepository->findByCustomer($userId);
        return response()->json($orders);
    }
}

好处

  • 无重复 – 查询逻辑集中在一个地方
  • 易于测试 – 模拟仓库而不是数据库
  • 灵活性 – 在不触及业务逻辑的情况下切换数据源
  • 代码整洁 – 控制器仅关注 HTTP 相关事务
  • 可重用性 – 同一仓库可在控制器、任务、命令等中使用

高级:基础仓库

abstract class BaseRepository
{
    protected $model;

    public function all(): Collection
    {
        return $this->model->all();
    }

    public function find(int $id): ?Model
    {
        return $this->model->find($id);
    }

    public function create(array $data): Model
    {
        return $this->model->create($data);
    }

    public function update(int $id, array $data): bool
    {
        return $this->model->find($id)?->update($data) ?? false;
    }

    public function delete(int $id): bool
    {
        return $this->model->find($id)?->delete() ?? false;
    }
}

扩展基础仓库

class ProductRepository extends BaseRepository
{
    public function __construct(Product $model)
    {
        parent::__construct($model);
    }

    public function getFeatured(int $limit = 10): Collection
    {
        return $this->model
            ->where('is_featured', true)
            ->where('stock', '>', 0)
            ->limit($limit)
            ->get();
    }

    public function searchAndFilter(array $filters)
    {
        $query = $this->model->query();

        if (!empty($filters['search'])) {
            $query->where('name', 'like', "%{$filters['search']}%");
        }

        if (!empty($filters['min_price'])) {
            $query->where('price', '>=', $filters['min_price']);
        }

        return $query->paginate(15);
    }
}

轻松进行测试

不使用仓库

// Must set up entire database
$order = Order::factory()->hasItems(3)->create();
$response = $this->getJson("/api/orders/{$order->id}");

使用仓库

// Just mock the repository!
$orderRepo = Mockery::mock(OrderRepositoryInterface::class);
$orderRepo->shouldReceive('find')
    ->with(1)
    ->andReturn($mockOrder);

$this->app->instance(OrderRepositoryInterface::class, $orderRepo);

常见陷阱

❌ 不要返回查询构建器

// 错误
public function getActive()
{
    return $this->model->where('active', true); // Query builder!
}

✅ 返回具体结果

// 正确
public function getActive(): Collection
{
    return $this->model->where('active', true)->get();
}

✅ 仓库仅用于数据访问

// 正确 – 仓库仅处理数据
public function create(array $data): Order
{
    return $this->model->create($data);
}

// 业务逻辑在 Service 中
class OrderService
{
    public function placeOrder(array $data): Order
    {
        $order = $this->orderRepository->create($data);
        Mail::to($order->customer)->send(new OrderCreated($order));
        return $order;
    }
}

快速检查清单

  • 我的控制器是否直接进行数据库查询?
  • 我是否在多个地方重复相同的查询?
  • 在没有数据库的情况下测试我的代码是否困难?
  • 我是否想轻松在 Eloquent / Query Builder / 原始 SQL 之间切换?
  • 我是否在构建一个不仅仅是简单 CRUD 的应用?

如果你对 2 条以上的问题回答了 yes,仓储模式将帮助你!

结论

Repository 模式并非对所有简单的 CRUD 应用都必需,但当你的应用规模扩大时,它就变得极其宝贵。它为你提供:

  • 干净、可测试的代码
  • 集中的数据访问逻辑
  • 更改数据源的灵活性
  • 更好的关注点分离

从小处开始——先在最复杂的模型上实现它,然后根据需要逐步扩展。

想要完整深度解析?

这是精简版!想阅读完整指南,包括:

  • ✨ 更多高级示例(缓存、服务层集成)
  • ✨ 实际博客系统实现
  • ✨ 完整的测试策略
  • ✨ 电商订单管理示例

阅读 Medium 上的完整文章:

👉 Repository Pattern in Laravel: From Problem to Solution

关注我获取更多 Laravel 小技巧:

👉 masteryoflaravel on Medium

你对 Repository Pattern 有什么经验?喜欢它吗?讨厌它吗?在评论区一起讨论吧! 💬

Back to Blog

相关文章

阅读更多 »