下面由Laravel教程栏目带大家推荐介绍关于Laravel存储库模式(Repository),希望对大家有所帮助!
- 1. Laravel 中的存储库模式(Repository)
- 2. 为什么要在 Laravel 中使用存储库模式(Repository)?
在大多数 web 应用程序中,访问数据库占了代码库的很大一部分。为了避免在我们应用程序逻辑上掺杂 SQL 查询,我们依赖抽象,它隐藏了 PHP 方法背后的数据访问机制。
有几种模式可以结构化数据访问,“Active Record” 和 “Repository” 是最著名的两种。在这篇博文中,我将在 Laravel 框架 的背景下具体解释它们。关于使用 Repository 模式的优点和缺点的讨论将在单独的博客文章中进行。
活动记录
默认情况下,Laravel 使用 Active Record 模式。每个 Laravel 程序员都直观地使用它,因为它是在抽象的 Model 基类中实现的,而模型通常从它继承而来。让我们来看一个例子:
use IlluminateDatabaseEloquentModel; /** * @property int $id * @property string $first_name * @property string $last_name */ class Person extends Model { } // --- 使用: $person = new Person(); $person->first_name = 'Jack'; $person->last_name = 'Smith'; $person->save();
当然,您可以读写您在 Person
上创建的属性。 但是要保存模型,您也可以 直接在模型上调用方法。 不需要另一个对象——模型已经提供了访问相应数据库表的所有方法。
这意味着,域模型将您的自定义属性和方法与同一类中的所有数据访问方法相结合。 第二部分是通过继承 Model
来实现的。
要点:
- Active Record 结合 域模型与数据访问功能。
- Laravel 使用 Active Record 模式并通过
Model
类实现它。
Repository
Repository 模式是 Active Record 模式的替代方案。它还提供了处理数据访问的抽象。但更广泛地说,它可以被视为域对象的概念性存储库或集合。
与活动记录模式相反,存储模式将数据库访问与域模型分离。它提供了一个高级接口,你可以在其中创建、读取、更新和删除域模型,而不必考虑实际的底层数据存储。
底层的存储库可以通过构建和执行 SQL 查询访问数据库,通过 REST API
访问远程系统,或者仅仅管理包含所有域模型的内存数据结构。这对测试很有用。存储库模式关键部分是它为其余代码提供的高级接口。
要点:
- 存储库表示域对象的概念集合。
- 它只负责用高级接口封装数据访问。
- Laravel 没有提供实现存储库模式的特定帮助程序
在 Laravel 中实现 Repository 模式时,我主要看到两种变体。
变体1:特定方法
在第一个变体中,存储库方法是重点和特定的。名称解释了调用者获得的内容,用于参数化底层查询的选项是有限的。
class InvoiceRepository { public function findAllOverdue(Carbon $since, int $limit = 10): Collection { return Invoice::where('overdue_since', '>=', $since) ->limit($limit) ->orderBy('overdue_since') ->get(); } public function findInvoicedToCompany(string $companyId): Collection { return Invoice::where('company_id', $companyId) ->orderByDesc('created_at') ->get(); } }
这种方法的优势在于方法的表现力。阅读代码时,很清楚从方法中期望什么以及如何调用它们。这会导致更少的错误。 Repository 方法很容易测试,因为参数有限。
这种方法的一个缺点是,最终可能会在存储库中使用大量的方法。由于方法无法轻松重用,因此必须为新用例添加其他方法。
要点:
- 存储模式可以通过提供特定方法的类来实现
- 每个方法包装一个查询,只公开必要的参数
- 优点: 可读性和可测试性
- 缺点: 缺乏灵活性和较低的可重用性
变式2: 一般方法
另一方面的方法是提供一般的方法。这导致了方法的减少。但是这些方法有一个很大的 API 曲面,因为每个方法都可以使用不同的参数组合来调用。
其中的关键问题是参数表示。这种表示应该引导调用方理解方法签名并避免无效的输入。为此,您可以引入一个特殊的类,例如使用 Query Object 模式。
但是我在实践中经常看到的是标量参数和 PHP 数组的混合。调用方可以传递完全无效的输入,仅类型数据并不能说明要传递什么。但是如果使用得当,这种轻量级的方法可以避免更繁琐的抽象。
class InvoiceRepository { public function find(array $conditions, string $sortBy = 'id', string $sortOrder = 'asc', int $limit = 10): Collection { return Invoice::where($conditions) ->orderBy($sortBy, $sortOrder) ->limit($limit) ->get(); } } // --- 使用: $repo = new InvoiceRepository(); $repo->find(['overdue_since', '>=', $since], 'overdue_since', 'asc'); $repo->find(['company_id', '=', $companyId], 'created_at', 'asc', 100);
这种方法减轻了第一种方法的问题:你可以得到更少的 Repository 方法,这些方法更灵活,并且可以更频繁地重用。
从消极的方面看,Repository 变得更加难以测试,因为有