簡體   English   中英

使用Laravel實現存儲庫模式

[英]Repository pattern implementation with Laravel

最近我開始研究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 (或將其注入訂閱者的構造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進行重復調用,這種方法是否正確(模型持久性)?

這些事情,這些是我的問題。 我想聽聽答案,想法,評論。 也許,我應用模式的方法不正確? 現在它引起了比解決數據映射問題更多的麻煩。

我還閱讀了一些關於Repository實現的文章:

  1. http://heera.it/laravel-repository-pattern#.VFaKu8lIRLe
  2. http://vegibit.com/laravel-repository-pattern

但它並沒有解決我的誤解

存儲庫模式有它的優點和缺點。

從我最近采用的模式開始,它可以提供更簡單的測試體驗 - 特別是在利用繼承和多態時。

下面是我使用的近乎全面的存儲庫合同的摘錄。

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

我盡量不將所有存儲庫聚集在一起,而是按功能對應用程序進行分組,以保持我的目錄結構不那么混亂。

我正在跳過很多細節,但是我不想過多地使用繼承和多態來試驗你可以用Repositories實現更好的工作流程。

在我當前的工作流程中,我的測試有自己的抽象類專門用於基礎存儲庫合同,所有實體存儲庫實現在最初的幾個障礙之后使測試變得輕而易舉。

祝你好運!

它工作正常!

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'));
    }
}

@likerRr你問:

以這種方式使用Repository模式重寫此示例是否正確:

首先,您應該考慮為什么我們使用Desing Patterns,特別是Repository Pattern? 我們使用Repository模式來實現SOLID原則(全部或少數)。 第一件事是不應該訪問控制器中的數據源/數據庫。 這樣做你是:

  1. 違反Single Responsibility Principle (S in SOLID) 您的控制器必須不知道數據源。 它只負責響應HTTP請求或冥想您的應用程序和HTTP。
  2. 你違反了利斯科夫替代原則
  3. 你違反了依賴倒置原則。

因此,這就是為什么您不僅應該使用存儲庫模式,還要實現SOLID原則。 那怎么辦? 將數據源訪問權限包含在其他位置,並且存儲庫是最佳位置。 假設您使用以下代碼獲取用戶:

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

如果您在所需的所有控制器中編寫此代碼,則無法執行以下操作:

  1. 如果不更改該行,則無法更改數據源
  2. 你無法測試你的代碼
  3. 最終這將難以維持

2:我可以以某種方式將找到的模型傳遞回存儲庫並增加其計數器(它是否與存儲庫模式的這種方法相矛盾?)

你在你的片段中做得很好。 實際上,你想要獲得Laravel提供的便利和模式的好處。 你可能知道你必須為了另一件事而犧牲一些東西。 Drivers driving on the easy to drive roads can not become good drivers 因此,我建議您遵循設計模式和SOLID原則,並保留Laravel提供的“輕松”。 否則,這種所謂的“輕松”會給你帶來很多麻煩,你甚至無法維護你的項目,一切都會消失。

關於使用事件的最后事情:

事件只是觀察者模式。 在您的情況下,似乎不需要使用觀察者模式,因此您應該避免使用它。

觀察者模式/活動的最佳候選人將是您向客戶收費,並且在您成功收費后,您希望通過電子郵件發送當前收費的金額詳細信息以及之前的繁重計算。 因為這需要時間,並且您不希望在完成所有這些繁重的處理時向用戶顯示頁面重新加載。 因此,您可以向用戶收費,發起活動,通過成功消息將用戶重定向到您的站點,讓事件處理程序完成繁重工作,您的用戶可以執行其他操作。

如果你願意,你可以問任何其他問題!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM