我经常看到“如何在Eloquent中使用存储库模板”风格的文章(其中之一是最近的PHP摘要中的内容 )。 它们的常规内容:让我们创建一个接口PostRepositoryInterface和EloquentPostRepository类,将它们很好地绑定到依赖容器中,并使用save and find代替标准的Ekovent方法。
为什么需要这个模板,有时他们根本不写(“这是一个模板 !还不够吗?”),有时,他们写一些有关可能的数据库更改(在每个项目中都很常见)以及测试和Mocha-Stub的信息。 在此类文章中,将这样的模板引入常规Laravel项目中的好处很难抓住。
让我们尝试找出什么是什么? Repository模板使您可以从特定的存储系统(我们通常将其作为数据库)中进行抽象,从而提供实体集合的抽象概念。
口才存储库的示例分为两种类型:
- 双重口才阵列变异
- 纯口才存储库
双重口才阵列变异
第一个示例(摘自随机文章):
<?php interface FaqRepository { public function all($columns = array('*')); public function newInstance(array $attributes = array()); public function paginate($perPage = 15, $columns = array('*')); public function create(array $attributes); public function find($id, $columns = array('*')); public function updateWithIdAndInput($id, array $input); public function destroy($id); } class FaqRepositoryEloquent implements FaqRepository { protected $faqModel; public function __construct(Faq $faqModel) { $this->faqModel = $faqModel; } public function newInstance(array $attributes = array()) { if (!isset($attributes['rank'])) { $attributes['rank'] = 0; } return $this->faqModel->newInstance($attributes); } public function paginate($perPage = 0, $columns = array('*')) { $perPage = $perPage ?: Config::get('pagination.length'); return $this->faqModel ->rankedWhere('answered', 1) ->paginate($perPage, $columns); } public function all($columns = array('*')) { return $this->faqModel->rankedAll($columns); } public function create(array $attributes) { return $this->faqModel->create($attributes); } public function find($id, $columns = array('*')) { return $this->faqModel->findOrFail($id, $columns); } public function updateWithIdAndInput($id, array $input) { $faq = $this->faqModel->find($id); return $faq->update($input); } public function destroy($id) { return $this->faqModel->destroy($id); } }
all , find , paginate方法返回Eloquent对象,但是create , updateWithIdAndInput正在等待数组。
名称updateWithIdAndInput本身意味着此“存储库”将仅用于CRUD操作。
没有正常的业务逻辑,但是我们将尝试实现最简单的业务逻辑:
<?php class FaqController extends Controller { public function publish($id, FaqRepository $repository) { $faq = $repository->find($id);
如果没有存储库:
<?php class FaqController extends Controller { public function publish($id) { $faq = Faq::findOrFail($id);
轻松两倍。
为什么要在项目中引入只会使其复杂化的抽象呢?
- 单元测试?
每个人都知道,在Laravel上进行常规CRUD项目的单元测试所占比例略高于100%。
但是稍后我们将讨论单元测试。 - 为了能够更改数据库?
但是Eloquent已经提供了几个数据库选项。
对于仅包含CRUD逻辑的应用程序,如果将Eloquent实体用作不受支持的基础,将是一种痛苦,并且会浪费时间。
在这种情况下,返回一个纯PHP数组并且也仅接受数组的存储库看起来更加自然。
通过删除Eloquent,我们可以从数据仓库中获得真正的抽象。
纯口才存储库
仅适用于Eloquent的存储库示例(也在一篇文章中找到):
<?php interface PostRepositoryInterface { public function get($id); public function all(); public function delete($id); public function save(Post $post); } class PostRepository implements PostRepositoryInterface { public function get($id) { return Post::find($id); } public function all() { return Post::all(); } public function delete($id) { Post::destroy($id); } public function save(Post $post) { $post->save(); } }
在本文中,我不会责骂不必要的后缀接口 。
这个实现有点像模板描述中所说的。
最简单的逻辑的实现看起来更加自然:
<?php class FaqController extends Controller { public function publish($id, PostRepositoryInterface $repository) { $post = $repository->find($id);
但是,为最简单的博客文章实现存储库是孩子们沉迷的玩具。
让我们尝试一些更复杂的事情。
具有子实体的简单实体。 例如,一项具有可能答案的调查(在网站或聊天中进行常规投票)。
创建此类调查对象的案例。 两种选择:
- 创建PollRepository和PollOptionRepository并同时使用。
该选项的问题在于抽象无法解决。
具有可能答案的调查是一个实体,它在数据库中的存储应该已经由一个PollRepository类实现。
PollOptionRepository :: delete并非易事,因为他将需要一个Poll对象来了解是否可以删除此答案选项(因为如果民意调查只有一个选项,则不会是民意调查)。
存储库模板并不意味着在存储库内部实现业务逻辑。 - 在PollRepository内部,添加saveOption和deleteOption方法 。
问题几乎相同。 事实证明,从存储中提取数据是一种卑鄙的作法...答案选项需要单独使用。
但是,如果本质更加复杂,该怎么办? 还有许多其他实体?
出现了同样的问题:为什么都这样?
从存储系统获得比Eloquent更大的抽象-将无法正常工作。
单元测试?
这是我书中可能的单元测试的示例-https : //gist.github.com/adelf/a53ce49b22b32914879801113cf79043
对于最简单的操作进行如此庞大的单元测试还远远不够。
我几乎可以肯定,该项目中的此类测试将被放弃。
没有人愿意支持他们。 我知道,我当时正在进行这样的测试。
专注于功能测试要容易得多,也更正确。
特别是如果它是一个API项目。
如果业务逻辑是如此复杂,以至于您真的想用测试来覆盖它,那么最好采用像Doctrine这样的数据映射器库并将业务逻辑与应用程序的其余部分完全分开。 单元测试将轻松10倍。
如果您真的想在Eloquent项目中沉迷于设计模式,那么在下一篇文章中,我将展示如何部分应用Repository模板并从中受益。