1. 程式人生 > >repository實作

repository實作

轉:https://www.jianshu.com/p/dcaaf801c294

Repository緣由

本文將介紹Repository的實作,基於的github專案是:l5-repository,原始碼是做好的教科書,程式碼面前所有設計意圖都無所遁形。

我們首先來明確下需要解決的問題是什麼,為什麼會出現l5-repository這個專案。

我想你肯定遇到過這個問題:剛開始我們的Model只有幾百行,但是呢,隨著專案功能的不斷複雜,model需要實現的功能也越來越多,當我們有一天突然回過頭去看的時候,發現Model已經上千行了,我們自己也想不起來裡面都有哪些功能了。

那model為什麼會越來越胖

呢?我們來看下model中都可能會有哪些功能。

  • php寫的業務邏輯
  • 依據顯示需求,我們做的資料格式轉換,欄位的選擇性返回
  • 各種條件的查詢
  • 對於建立引數、查詢引數的驗證
  • …..

根據分類,我們可以歸納為下面幾類

  1. presenter,顯示需求
  2. repository,存取需求
  3. validator,引數驗證需求
  4. services,業務邏輯

於是我們就有了下面的架構圖:

圖片來自電燈坊

圖片來自電燈坊

有了這個圖以後,我們再來看l5-repository這個專案的實現,就會容易理解很多。

下面我會從3個方面來講解l5-repository

的實現:repository,presenter,validator。我們先來看repository的實現。

repository原理

開始時,我也免不了一般的套路,先來個repository的定義

A Repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection. Client objects construct query specifications declaratively and submit them to Repository for satisfaction. Objects can be added to and removed from the Repository, as they can from a simple collection of objects, and the mapping code encapsulated by the Repository will carry out the appropriate operations behind the scenes.

其核心有兩點

  • repository類似於集合,負責物件的存取,隱藏了具體的data mapping的實現
  • repository能根據client構建的查詢條件,返回特定的資料

我們先來看第一點,物件存取,說到底就是CRUD操作,於是就有了repository的介面定義RepositoryInterface,具體的介面有:

對於CRUD來說,其最難的是R操作,因為會有各種各樣的查詢方式,提供的查詢介面有:

public function find($id, $columns = ['*']);
public function findByField($field, $value, $columns = ['*']);
public function findWhere(array $where, $columns = ['*']);
public function findWhereIn($field, array $values, $columns = ['*']);
public function findWhereNotIn($field, array $values, $columns = ['*']);

然後預設的實現是Prettus\Repository\Eloquent\BaseRepository,基本上就是對Eloquent\Builder的一個封裝。

但是一些更復雜的查詢怎麼滿足呢?我們難道需要每次有新的查詢都去新增findCondition介面嗎?顯然我們不能這麼做,這個時候Criteria就隆重登場了,先看個介面:

interface CriteriaInterface
{
    /**
     * Apply criteria in query repository
     *
     * @param                     $model
     * @param RepositoryInterface $repository
     *
     * @return mixed
     */
    public function apply($model, RepositoryInterface $repository);
}

所有的Criteria都需要實現apply方法,看一個可能是實現:

class LengthOverTwoHours implements CriteriaInterface {
    public function apply($model, Repository $repository)
    {
        $query = $model->where('length', '>', 120);
        return $query;
    }
}

通過定義LengthOverTwoHours來對Model新增查詢,這樣子我們每次有新的查詢條件,只要新建Criteria即可,滿足了開放封閉原則。

接著我們來使用下l5-repository。首先通過命令php artisan make:entity Book來生成檔案,然後在[email protected]中新增

$this->app->register( RepositoryServiceProvider::class);

接著產生一個Controller

php artisan make:controller -r BookController

在裡面我們可以使用注入進Repository

public function __construct( BookRepository $bookRepository )
{
    $this->bookRepository = $bookRepository;
}

然後一些具體的操作可以去看https://github.com/andersao/l5-repository,寫的非常詳細。

最後介紹下怎麼產生criteria,通過下面的命令

php artisan make:criteria My

然後新增下面程式碼

class MyCriteria implements CriteriaInterface
{
    /**
     * Apply criteria in query repository
     *
     * @param Builder                    $model
     * @param RepositoryInterface $repository
     *
     * @return mixed
     */
    public function apply($model, RepositoryInterface $repository)
    {
        $model = $model->where('user_id','=', \Auth::user()->id );
        return $model;
    }
}

就能夠使用了,然後在controller中,我們通過下面的方式查詢

public function index()
{
  $this->repository->pushCriteria(new MyCriteria());
  $books = $this->repository->all();
  ...
}

Presenters優化

接著我們講Presenters部分。

我們再強調下Presenter解決的問題:把日期、金額、名稱之類的呈現(presentation)邏輯抽離出來。在l5-repository這個功能其實不是很滿意,我們希望的是1 Presenter中介紹的那種樣子,原先樣子是:

class Article extends Eloquent
{
    public function getDate(){/*...*/}

    public function getTaiwaneseDateTime(){/*...*/}

    public function getWesternDateTime(){/*...*/}

    public function getTaiwaneseDate(){/*...*/}

    public function getWesternDate(){/*...*/}
}

抽離出來後是:

class Article extends Eloquent
{
    public function present()
    {
        return new ArticlePresenter($this);
    }
}

class ArticlePresenter extends Presenter {

    public function getTaiwaneseDateTime(){/*...*/}

    public function getWesternDateTime(){/*...*/}

    public function getTaiwaneseDate(){/*...*/}

    public function getWesternDate(){/*...*/}
}

下面是一些實現方案

https://github.com/laracasts/Presenter

https://github.com/robclancy/presenter

https://github.com/laravel-auto-presenter/laravel-auto-presenter

引數驗證

最後我們介紹validator

validator的邏輯從model中抽離出來,單獨放入一個類中,

use \Prettus\Validator\Contracts\ValidatorInterface;
use \Prettus\Validator\LaravelValidator;

class PostValidator extends LaravelValidator {

    protected $rules = [
        ValidatorInterface::RULE_CREATE => [
            'title' => 'required',
            'text'  => 'min:3',
            'author'=> 'required'
        ],
        ValidatorInterface::RULE_UPDATE => [
            'title' => 'required'
        ]
   ];

}

能夠制定create和update操作的時候不同的驗證規則。

總結

以上就是repository的全部,文章開頭由實際專案中model越來越胖引出如何給model瘦身,接著對model中的功能進行了劃分,給出了合理的專案組織方式,接著通過從repository,presenter,validator分析了具體的一些優化方式。

最後本文只是簡單的對l5-repository進行了介紹,更詳細的功能,更多的實現細節,你都可以clone專案下來,自己好好去看,相信會學到很多。