ホームページ >PHPフレームワーク >Laravel >Laravel のリポジトリ パターン (Repository) の利点の簡単な分析

Laravel のリポジトリ パターン (Repository) の利点の簡単な分析

青灯夜游
青灯夜游転載
2022-12-05 19:20:231484ブラウズ

Laravel でリポジトリ パターン (Repository) を使用する理由は何ですか?リポジトリモードを利用するメリットについては以下の記事で紹介していますので、ぜひ参考にしてください。

Laravel のリポジトリ パターン (Repository) の利点の簡単な分析

  • 1. Laravel のリポジトリ パターン
  • 2. Laravel でリポジトリを使用する理由パターン(リポジトリ)?
前回の記事では、リポジトリ パターンとは何か、Active Record パターンとの違い、Laravel での実装方法について説明しました。ここで、なぜリポジトリ パターンを使用する必要があるのか​​を詳しく見ていきたいと思います。

前の記事のコメントで、

リポジトリ パターンが Laravel コミュニティで物議を醸すトピックであることに気付きました。これを使用する理由がないと考えて、組み込みのアクティブ レコード モードを使い続ける人もいます。他の方法を使用してデータ アクセスを論理ドメインから分離することを好む人もいます。私はこれらの意見を尊重し、今後のブログ投稿でこのトピックを取り上げることに注意してください。

この免責事項を踏まえて、リポジトリ パターンを使用する利点を理解しましょう。

単一責任の原則

単一責任の原則は、アクティブ レコード モードとリポジトリ モードを区別する主な識別子です。モデル クラスはすでにデータを保持し、ドメイン オブジェクトのメソッドを提供します。 Active Record パターンを使用する場合、データ アクセスが追加の責任として導入されます。これについては、次の例で説明したいと思います。

/**
 * @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 の従業員を呼び寄せることができたのでしょうか?

もちろん、この例は不自然ですが、アクティブ レコード パターンが意図的なドメイン モデルをどのように許可していないかを示しています。従業員と全従業員のリストとの境界線があいまいになります。従業員が実際の従業員として使用されているのか、他の従業員にアクセスするためのメカニズムとして使用されているのかを常に考慮する必要があります。

ウェアハウス モードは、この基本的なパーティショニングを強制することでこの問題を解決します。

その唯一の目的は、ドメイン オブジェクト自体ではなく、ドメイン オブジェクトのコレクションを識別することです。

キーポイント:

    #ウェアハウス パターンは、すべてのドメイン オブジェクトのコレクションを単一のドメイン オブジェクトから分離することにより、単一責任の原則を具体化します

Don't Reply Yourself (DRY)一部のプロジェクトでは、プロジェクト全体にデータベース クエリが散布されています。以下は、データベースからリストを取得し、ブレード ビューに表示する例です。

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: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( &amp;#39;invoices.index&amp;#39;, [&amp;#39;invoices&amp;#39; =&gt; $this-&gt;repo-&gt;findByCompanyId($companyId)] ); } } class EloquentInvoiceRepository implements InvoiceRepository { public function findByCompanyId($companyId): Collection { // 使用 Eloquent 查询构造器实现该方法 } }</pre>Controller は Repository 実装と同じように、Repository インターフェイスにのみ依存するようになりました。

これら 2 つのクラスは 1 つの抽象化にのみ依存するようになりました

次のセクションで説明するように、これによりさらなる利点がもたらされます。

  • ? 存储库模式作为一种抽象类,支持依赖反转.

抽象类

存储库 提高了可读性 因为复杂的操作被具有表达性名称的高级方法隐藏了.

访问存储库的代码与底层数据访问技术分离. 如有必要,您可以切换实现,甚至可以省略实现,仅提供 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(&#39;findInvoicedToCompany&#39;)
    ->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 サイトの他の関連記事を参照してください。

声明:
この記事はlearnku.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。