ホームページ > 記事 > PHPフレームワーク > Laravelにおけるリポジトリパターン(Repository)の使い方を詳しく解説します。
- Laravel のリポジトリ パターン
- Laravel でリポジトリ パターンを使用する理由は何ですか?
私の 前の記事 では、リポジトリ パターンとは何か、アクティブ レコード パターンとの違い、およびそれを Laravel で実装する方法について説明しました。ここで、なぜリポジトリ パターンを使用する必要があるのかを詳しく見ていきたいと思います。
前の記事のコメントで、リポジトリ パターンが Laravel コミュニティで物議を醸しているトピックであることに気付きました。これを使用する理由がないと考えて、組み込みのアクティブ レコード モードを使い続ける人もいます。他の方法を使用してデータ アクセスを論理ドメインから分離することを好む人もいます。私はこれらの意見を尊重し、今後のブログ投稿でこのトピックを取り上げることに注意してください。
この免責事項を踏まえて、リポジトリ パターンを使用する利点を理解しましょう。 [関連チュートリアルの推奨事項:laravel]
/** * @property string $first_name * @property int $company_id */ class Employee extends Model {} $jack = new Employee(); $jack->first_name = 'Jack'; $jack->company_id = $twitterId; $jack->save();ドメイン モデルとデータ アクセス テクノロジにはさまざまな役割がありますが、直感的には理解できます。私たちのアプリケーションでは、従業員を何らかの方法でデータベースに保存する必要があるため、オブジェクトに対して
save() を呼び出してみませんか。単一のオブジェクトは単一行のデータに変換されて保存されます。
$jack->where('first_name', 'John')->firstOrFail()->delete(); $competition = $jack->where('company_id', $facebookId)->get();これでは、直感的ではなくなり、ドメイン モデルに違反することさえあります。なぜジャックは、別の会社で働いているかもしれない別の従業員を突然削除したのでしょうか?あるいは、なぜ彼は Facebook の従業員を呼び寄せることができたのでしょうか? もちろん、この例は不自然ですが、アクティブ レコード パターンが意図的なドメイン モデルをどのように許可していないかを示しています。従業員と全従業員のリストとの境界線があいまいになります。従業員が実際の従業員として使用されているのか、他の従業員にアクセスするためのメカニズムとして使用されているのかを常に考慮する必要があります。 ウェアハウス モードは、この基本的なパーティショニングを強制することでこの問題を解決します。
その唯一の目的は、ドメイン オブジェクト自体ではなく、ドメイン オブジェクトのコレクションを識別することです。
キーポイント:
class InvoiceController { public function index(): View { return view('invoices.index', [ 'invoices' => Invoice::where('overdue_since', '>=', Carbon::now()) ->orderBy('overdue_since') ->paginate() ]); }}
このようなクエリがより複雑になり、複数の場所で使用される場合は、それを Repository メソッドに抽出することを検討してください。
リポジトリ パターンは、重複クエリを式メソッドにパッケージ化することで重複クエリを減らすのに役立ちます。クエリを調整する必要がある場合、変更する必要があるのは 1 回だけです。
class InvoiceController { public __construct(private InvoiceRepository $repo) {} public function index(): View { return view('invoices.index', [ 'invoices' => $repo->paginateOverdueInvoices() ]); }}
これで、クエリは 1 回だけ実装され、単独でテストして他の場所で使用できるようになりました。さらに、コントローラーはデータの取得については責任を負わず、HTTP リクエストを処理して応答を返すことのみを担当するため、単一責任原則が再び適用されます。
要点:リポジトリ パターンは重複クエリの削減に役立ちます
コンポーネントを階層化する場合、通常、上位レベルのコンポーネントは下位レベルのコンポーネントに依存します。たとえば、コントローラーはモデル クラスに依存してデータベースからデータを取得します。
class InvoiceController { public function index(int $companyId): View { return view( 'invoices.index', ['invoices' => Invoice::where('company_id', $companyId)->get()] ); }}
依存関係はトップダウンで緊密に結合されています。
InvoiceController は、特定の Invoice
クラスに依存します。これら 2 つのクラスを個別にテストしたり、ストレージ メカニズムを置き換えたりするなど、これら 2 つのクラスを分離することは困難です。 Repository インターフェイスを導入することで、依存関係の逆転を実現できます: <pre class="brush:php;toolbar:false">interface InvoiceRepository {
public function findByCompanyId($companyId): Collection;}class InvoiceController {
public function __construct(private InvoiceRepository $repo) {}
public function index(int $companyId): View {
return view(
'invoices.index',
['invoices' => $this->repo->findByCompanyId($companyId)]
);
}}class EloquentInvoiceRepository implements InvoiceRepository {
public function findByCompanyId($companyId): Collection {
// 使用 Eloquent 查询构造器实现该方法
}}</pre>
Controller は Repository 実装と同じように、Repository インターフェイスにのみ依存するようになりました。
次のセクションで説明するように、これによりさらなる利点がもたらされます。
存储库 提高了可读性 因为复杂的操作被具有表达性名称的高级方法隐藏了.
访问存储库的代码与底层数据访问技术分离. 如有必要,您可以切换实现,甚至可以省略实现,仅提供 Repository 接口。 这对于旨在与框架无关的库来说非常方便。
OAuth2 服务包 —— league/oauth2-server
也用到这个抽象类机制。 Laravel Passport 也通过 实现这个库的接口 集成 league/oauth2-server 包。
正如 @bdelespierre 在 评论 里回应我之前的一篇博客文章时向我指出的那样,你不仅可以切换存储库实现,还可以将它们组合在一起。大致以他的示例为基础,您可以看到一个存储库如何包装另一个存储库以提供附加功能:
interface InvoiceRepository { public function findById(int $id): Invoice;}class InvoiceCacheRepository implements InvoiceRepository { public function __construct( private InvoiceRepository $repo, private int $ttlSeconds ) {} public function findById(int $id): Invoice { return Cache::remember( "invoice.$id", $this->ttlSeconds, fn(): Invoice => $this->repo->findById($id) ); }}class EloquentInvoiceRepository implements InvoiceRepository { public function findById(int $id): Invoice { /* 从数据库中取出 $id */ }}// --- 用法:$repo = new InvoiceCacheRepository( new EloquentInvoiceRepository(););
要点:
存储库模式提供的抽象也有助于测试。
如果你有一个 Repository 接口,你可以提供一个替代的测试实现。 您可以使用数组支持存储库,而不是访问数据库,将所有对象保存在数组中:
class InMemoryInvoiceRepository implements InvoiceRepositoryInterface { private array $invoices; // implement the methods by accessing $this->invoices... } // --- Test Case: $repo = new InMemoryInvoiceRepository(); $service = new InvoiceService($repo);
通过这种方法,您将获得一个现实的实现,它速度很快并且在内存中运行。 但是您必须为测试提供正确的 Repository 实现,这 ** 本身可能需要大量工作**。 在我看来,这在两种情况下是合理的:
1.您正在开发一个(与框架无关的)库,它本身不提供存储库实现。
2.测试用例复杂,Repository 的状态很重要。
另一种方法是“模仿”,要使用这种技术,你不需要适当的接口。你可以模仿任何 non-final 类。
使用 PHPUnit API ,您可以明确规定如何调用存储库以及应该返回什么。
$companyId = 42; /** @var InvoiceRepository&MockObject */ $repo = $this->createMock(InvoiceRepository::class); $repo->expects($this->once()) ->method('findInvoicedToCompany') ->with($companyId) ->willReturn(collect([ /* invoices to return in the test case */ ])); $service = new InvoiceService($repo); $result = $service->calculateAvgInvoiceAmount($companyId); $this->assertEquals(1337.42, $result);
有了 mock,测试用例就是一个适当的单元测试。上面示例中测试的唯一代码是服务。没有数据库访问,这使得测试用例的设置和运行非常快速。
另外:
原文地址:https://dev.to/davidrjenni/why-use-the-repository-pattern-in-laravel-2j1m
译文地址:https://learnku.com/laravel/t/62521
以上がLaravelにおけるリポジトリパターン(Repository)の使い方を詳しく解説します。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。