使用Laravel实现存储库模式

时间:2014-11-04 20:19:23

标签: php design-patterns laravel repository-pattern

最近我开始研究Laravel 4及其功能。我想实现Repository模式以在那里移动模型逻辑。在这一点上,我面临着如何组织它的一些不便或误解。一般问题我有这样的话:是否可以在Laravel中实施和应用此模式而不会头痛,是否值得

这个问题将分为几个部分,这引起了我的困惑。

1)Laravel提供了将模型绑定为控制器参数的便捷方式,例如我是这样做的:

// routes.php
Route::bind('article', function($slug)
{
    return Article::where('slug', $slug)->first();
});

Route::get('articles/{article}', 'ArticlesController@getArticle');

// controllers/ArticlesController.php
class ArticlesController extends BaseController {

    public function getArticle(Article $article)
    {
        return View::make('article.show', compact('article'));
    }
}

如果我想使用Repository模式,那么我就不能使用这种方法,因为在这种情况下,控制器会清楚地意识到模型Article的存在? 以这种方式使用Repository模式重写此示例是否正确

// routes.php
Route::get('articles/{slug}', 'ArticlesController@getArticle');

// controllers/ArticlesController.php
class ArticlesController extends BaseController {

    private $article;

    public function __construct(ArticleRepository $article) {
        $this->article = $article;
    }

    public function getArticle($slug)
    {
        $article = $this->article->findBySlug($slug);

        return View::make('article.show', compact('article'));
    }
}

2)假设,上面使用Repository的代码是正确的。现在我想在每次显示时增加文章视图计数器,但是,我想在Event中进行此处理。也就是说,代码如下:

// routes.php
Route::get('articles/{slug}', 'ArticlesController@getArticle');

// controllers/ArticlesController.php
class ArticlesController extends BaseController {

    private $article;

    public function __construct(ArticleRepository $article) {
        $this->article = $article;
    }

    public function getArticle($slug)
    {
        $article = $this->article->findBySlug($slug);
        Events::fire('article.shown');

        return View::make('articles.single', compact('article'));
    }
}

// some event subscriber
class ArticleSubscriber {

    public function onShown()
    {
        // why implementation is missed described bellow
    }

    public function subscribe($events)
    {
        $events->listen('article.shown', 'ArticleSubscriber@onShown');
    }

}

此时我又对如何实现事件处理感到困惑。我无法将$article模型直接传递给事件,因为它再次违反了OOP的原则,我的订阅者将知道文章模型的存在。所以,我不能这样做:

// controllers/ArticlesController.php
...
\Events::fire('article.shown', $article);
...

// some event subscriber
...
public function onShown(Article $article)
{
    $article->increment('views');
}
...

另一方面,我没有看到引入subscriber存储库ArticleRepository的任何意义(或将其注入订阅者的构造函数中),因为我首先应该找到一篇文章,然后更新计数器,最后,我将得到额外的查询(因为以前在构造函数中我做同样的事情)到数据库:

// controllers/ArticlesController.php
...
Events::fire('article.shown', $slug);
...

// some event subscriber
...
private $article;

public function __construct(ArticleRepository $articleRepository)
{
    $this->article = $articleRepository;
}

public function onShown($slug)
{
    $article = $this->articleRepository->findBySlug($slug);
    $article->increment('views');
}
...

此外,在Event处理(即增加的视图计数)之后,控制器必须知道更新的模型,因为在视图中我想显示更新的视图计数器。事实证明,我仍然需要从Event返回一个新模型,但我不希望Event成为处理特定操作的常用方法(为此存在存储库)和返回一些价值。另外,您可能会注意到我的上一个onShow()方法再次违反Repository模式的规则,但我不了解如何将此逻辑放入存储库:

public function onShown($slug)
{
    $article = $this->articleRepository->findBySlug($slug);
    // INCORRECT! because the Event shouldn't know that the model is able to implement Eloquent
    // $article->increment('views');
}

我能否以某种方式将找到的模型传递回存储库并增加她的计数器(这与Repository模式的方法相矛盾吗?)?这样的事情:

public function onShown($slug)
{
    $article = $this->articleRepository->findBySlug($slug);
    $this->articleRepository->updateViews($article);
}

// ArticleRepository.php
...
public function updateViews(Article $article) {
    $article->increment('views');
}
...

因此,我将尝试制定更紧凑的内容:

  1. 如果我使用Repository模式,我将不得不拒绝将模型直接传递给DI提供的控制器和其他舒适设备吗?

  2. 是否可以使用存储库来保持模型的状态并在实体之间传递(例如,从过滤器到控制器,从控制器到Event并返回),避免重复调用到db并且这种方法是否正确(模型持久性)?

  3. 这些事情,这些是我的问题。我想听听答案,想法,评论。也许,我应用模式的方法不正确?现在它比解决数据映射问题引起更多麻烦。

    我还阅读了一些关于Repository实现的文章:

    1. http://heera.it/laravel-repository-pattern#.VFaKu8lIRLe
    2. http://vegibit.com/laravel-repository-pattern
    3. 但它并没有解决我的误解

3 个答案:

答案 0 :(得分:0)

存储库模式有其优点和缺点。

从我最近采用的模式开始,它可以提供更简单的测试体验 - 特别是在利用继承和多态时。

以下是我使用的近乎全面的存储库合约的摘录。

interface EntityRepository
{
    /**
     * @param $id
     * @return array
     */
    public function getById($id);

    /**
     * @return array
     */
    public function getAll();

    /**
     * @param array $attr
     * @return array
     */
    public function save(array $attr);

    /**
     * @param $id
     */
    public function delete($id);

    /**
     * Checks if a record with the given values exists
     * @param array $attr
     * @return bool
     */
    public function exists(array $attr);

    /**
     * Checks if any records with any of these values exists and returns true or false
     * @param array $attr
     * @return bool
     */
    public function unique(array $attr);
}

合同相对自我解释,save()管理插入和更新实体(模型)。

从这里开始,我将创建一个抽象类,实现我想要使用的供应商的所有功能 - 例如Eloquent或Doctrine。

值得注意的是,这份合同无法捕捉到所有内容,而我目前正在创建一个单独的实施方案来处理多对多的关系,但那是另一个故事。

要创建我的个人存储库类,为此,我为扩展EntityRepositoryContract的每个存储库创建另一个合同,并说明它们所独有的功能。如果是用户 - registerUser(...)disableUser(...)等等

最后的类将扩展EloquentEntityRepository并实现存储库的相关合同。 EloquentUserRepository的类签名可能是这样的:

class EloquentUserRepository extends EloquentEntityRepository implements UserRepositoryContract
{
...
}

在我自己的实现中,为了使类名更简洁,我利用命名空间来为每个实现添加别名:

use Repo\Eloquent\UserRepo; //For the Eloquent implementation
use Repo\Doctrine\UserRepo; //For the Doctrine implementation

我尽量不将所有存储库聚集在一起,而是按功能对应用程序进行分组,以保持我的目录结构不那么混乱。

我已经跳过了很多细节,但我不想过多地投入使用继承和多态来尝试使用存储库来实现更好的工作流程。

使用我当前的工作流程,我的测试有自己的抽象类专门用于基础存储库合同,所有实体存储库实现在前几个障碍之后轻松进行测试。

祝你好运!

答案 1 :(得分:0)

一切正常!

<强> api.php

Route::get('/articles/{article}', 'ArticleController@getArticle')->middleware('can:get,article');

<强> ArticleController.php

class ArticleController extends BaseController {

    protected $repo;

    public function __construct(ArticleRepository $repository) {

        $this->repo = $repository;
    }

    public function getArticle(int $id)
    {
        $articleRepo = $this->repo->find($id);
        return View::make('article.show', compact('articleRepo'));
    }
}

答案 2 :(得分:0)

您问的

@likerRr:

以这种方式使用存储库模式重写此示例是否正确:

首先,您应该考虑为什么我们要使用Desing Patterns,特别是Repository Pattern?我们使用存储库模式来实现SOLID原理(全部或很少)。 第一件事是不应该访问控制器中的数据源/数据库。您正在这样做:

  1. 违反了Single Responsibility Principle (S in SOLID)。您的控制器不得知道数据源。它仅负责响应HTTP请求或冥想您的应用程序和HTTP。
  2. 您违反了Liskov换人原则
  3. 您违反了依赖倒置原则。

因此,这就是为什么您不仅应使用存储库模式,而且应实施SOLID原则的原因。 那该怎么办呢?将数据源访问权包装在其他位置,“存储库”是最佳选择。 假设您正在使用以下代码吸引用户:

User::where('id', $id)->where('company_id', $companyId)->get();

如果您在需要的所有控制器中编写此代码,则无法执行以下操作:

  1. 如果不更改该行,就无法更改数据源
  2. 您无法测试代码
  3. 最终将很难维持

2:我能以某种方式将找到的模型传递回存储库并增加计数器(这与这种存储库模式方法相矛盾吗?)

您正在正确执行自己的代码段。 实际上,您既要获得Laravel提供的便利,又要获得Patterns的好处。 您可能知道您必须为另一件事牺牲一些东西。 Drivers driving on the easy to drive roads can not become good drivers。因此,我建议您遵循设计模式和SOLID原则,并保留Laravel提供的“简便性”。否则,这种所谓的“轻松”会给您带来很多麻烦,您甚至无法维护您的项目,所有一切都会消失。

关于使用事件的最后一点:

事件不过是观察者模式。在您的情况下,似乎无需使用观察者模式,因此应避免使用它。

观察者模式/事件的最佳人选是您要向客户收费,成功收费后,您想通过电子邮件发送当前收费金额明细以及先前的大量计算。因为这将花费时间,并且您不想在完成所有这些繁重的处理后向用户显示页面重新加载。因此,您可以向用户收费,触发事件,通过成功消息将用户重定向到您的站点,并让事件处理程序承担繁重的工作,而用户可以执行其他操作。

如果愿意,您可以问其他任何问题!

相关问题