下面由最近很火的Laravel存储库模式(Repository)教程栏目带大家推荐介绍关于最近很火的Laravel存储库模式(Repository)存储库模式(Repository),希望对大家有所帮助!
- 1. 最近很火的Laravel存储库模式(Repository) 中的存储库模式(Repository)
- 2. 为什么要在 最近很火的Laravel存储库模式(Repository) 中使用存储库模式(Repository)?
在大多数 web 应用程序中,访问数据库占了代码库的很大一部分。为了避免在我们应用程序逻辑上掺杂 SQL 查询,我们依赖抽象,它隐藏了 PHP 方法背后的数据访问机制。
有几种模式可以结构化数据访问,“Active Record” 和 “Repository” 是最著名的两种。在这篇博文中,我将在 最近很火的Laravel存储库模式(Repository) 框架 的背景下具体解释它们。关于使用 Repository 模式的优点和缺点的讨论将在单独的博客文章中进行。
默认情况下,最近很火的Laravel存储库模式(Repository) 使用 Active Record 模式。每个 最近很火的Laravel存储库模式(Repository) 程序员都直观地使用它,因为它是在抽象的 Model 基类中实现的,而模型通常从它继承而来。让我们来看一个例子:
use Illuminate\Database\Eloquent\Model; /** * @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
来实现的。
要点:
Model
类实现它。Repository 模式是 Active Record 模式的替代方案。它还提供了处理数据访问的抽象。但更广泛地说,它可以被视为域对象的概念性存储库或集合。
与活动记录模式相反,存储模式将数据库访问与域模型分离。它提供了一个高级接口,你可以在其中创建、读取、更新和删除域模型,而不必考虑实际的底层数据存储。
底层的存储库可以通过构建和执行 SQL 查询访问数据库,通过 REST API
访问远程系统,或者仅仅管理包含所有域模型的内存数据结构。这对测试很有用。存储库模式关键部分是它为其余代码提供的高级接口。
要点:
在 最近很火的Laravel存储库模式(Repository) 中实现 Repository 模式时,我主要看到两种变体。
在第一个变体中,存储库方法是重点和特定的。名称解释了调用者获得的内容,用于参数化底层查询的选项是有限的。
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 方法很容易测试,因为参数有限。
这种方法的一个缺点是,最终可能会在存储库中使用大量的方法。由于方法无法轻松重用,因此必须为新用例添加其他方法。
要点:
另一方面的方法是提供一般的方法。这导致了方法的减少。但是这些方法有一个很大的 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 变得更加难以测试,因为有更多的案例需要覆盖。方法签名更难理解,正因为如此,调用者可能会犯更多错误。此外,还将引入某种查询对象表示形式。无论它是显式的还是隐式的(比如数组),您的 Repository 实现及其调用者都将与它耦合。
要点:
当然,这两种方法可以结合起来使用。也许你想要一些特定的方法用于复杂的查询,而一些通用的方法用于简单的 where
查询。
现在,我们来谈谈如何实现方法体。
在上面的例子中,我使用了 Model
类的方法来获得对 Eloquent 查询构造器的访问。所以 Repository 的实现实际上使用了 Active Record 模式作为实现。
你不需要这样做。你可以使用 DB
facade 来获得一个查询构建器,同时避免使用 Model
类。或者你可以直接编写 SQL 查询:
class InvoiceRepository { public function findAllOverdue(Carbon $since, int $limit = 10): Collection { return DB::table('invoices') ->where('overdue_since', '>=', $since) ->limit($limit) ->orderBy('overdue_since') ->get(); } public function findInvoicedToCompany(string $companyId): Collection { return DB::select('SELECT * FROM invoices WHERE company_id = ? ORDER BY created_at LIMIT 100', [$companyId]); } }
存储模式的优点是,实现可以是任何东西,只要它满足接口。你还可以管理内存中的对象或者包(和缓存)一个 API。
但是最常见的是,底层数据存储是一个 SQL 数据库。要访问它,你可以根据每个方法选择最佳实现。对于性能关键的或者复杂的查询,你可能希望直接使用 SQL 语句。更简单的查询可以使用 Eloquent 查询生成器。
当你没有使用 模型
类来实现你的 Repository ,你可能会考虑在模型中不继承它。但是这个方法违反了很多内置的 最近很火的Laravel存储库模式(Repository) 魔术方法,在我看来并不是一个好的方法。
要点:
你的另一个选择是,是否要引入一个接口。上面的例子可以用一个接口和一个或多个实现来分隔:
// --- 接口: public interface InvoiceRepositoryInterface { public function findAllOverdue(Carbon $since, int $limit = 10): Collection; public function findInvoicedToCompany(string $companyId): Collection; } // --- 具体的类,实现了该接口 class InvoiceRepository implements InvoiceRepositoryInterface { public function findAllOverdue(Carbon $since, int $limit = 10): Collection { // 实现 } public function findInvoicedToCompany(string $companyId): Collection { // 实现 } }
添加接口是一种额外的间接方法,并不一定是好的。如果您的应用程序是 Repository 的唯一用户,并且您不希望它有多个实现,那么我不认为引入接口有什么意义。对于测试,Repository 可以用 PHPUnit 模拟,只要它不被标记为 final
。
如果你知道你将有多个实现,你应该使用一个接口。如果你正在编写一个将在多个项目中使用的 包。或者你想要测试一个特殊的 Repository 实现,那么可能会发生不同的实现。
为了从 最近很火的Laravel存储库模式(Repository) 的依赖注入中获益,你必须将具体的实现绑定到接口上。这必须在服务提供者的注册方法中完成。
use Illuminate\Support\ServiceProvider; class RepositoryServiceProvider extends ServiceProvider { public function register(): void { $this->app->bind(InvoiceRepositoryInterface::class, InvoiceRepository::class); } }
要点:
原文地址:https://dev.to/davidrjenni/repository-pattern-in-laravel-1pph
译文地址:https://learnku.com/laravel/t/62587
以上是最近很火的Laravel存储库模式(Repository)的详细内容。更多信息请关注PHP中文网其他相关文章!