Laravel でリポジトリ パターン (Repository) を使用する理由は何ですか?リポジトリモードを利用するメリットについては以下の記事で紹介していますので、ぜひ参考にしてください。
前回の記事では、リポジトリ パターンとは何か、Active Record パターンとの違い、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();
その唯一の目的は、ドメイン オブジェクト自体ではなく、ドメイン オブジェクトのコレクションを識別することです。
キーポイント:
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 インターフェイスを導入することで、依存関係の逆転を実現できます: <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:js;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(
&#39;invoices.index&#39;,
[&#39;invoices&#39; => $this->repo->findByCompanyId($companyId)]
);
}
}
class EloquentInvoiceRepository implements InvoiceRepository {
public function findByCompanyId($companyId): Collection {
// 使用 Eloquent 查询构造器实现该方法
}
}</pre><div class="contentsignin">ログイン後にコピー</div></div>
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 实现,这 ** 本身可能需要大量工作**。 在我看来,这在两种情况下是合理的:
您正在开发一个(与框架无关的)库,它本身不提供存储库实现。
测试用例复杂,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视频教程】
以上がLaravel のリポジトリ パターン (Repository) の利点の簡単な分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。