iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 6
1

開始之前,如果有對 MVC 不熟的大大可以參考文章。在 MVC 當中,其實只有定義了 model、view 和 controller 而已,意思就是說不需要接下來要說的 repository 與 service 依然可以執行各種商業邏輯。

但我們可以想像,把所有商業邏輯都寫在同一個區塊是不是有點過於臃腫,且各項商業邏輯重複的部分也不少,未來光是維護的困難性就頗高,更不用說測試的部分。那我們把大部分邏輯都寫到 model 中吧(嗎)?! 然而 model 的定義是資料表以及資料紀錄 (table record) 對應的類別,所以也不適合寫在這裡面。因此為了將 controller 瘦身,又保持 model 的乾淨,許多人會在 model 與 controller 中間又增加了 repository 和 service 兩層。

其中 repository 主要負責商業邏輯中各種資料庫 CRUD 的部分,而 service 則是統合各項資源與應用 (service 部分明天再談),以下我們會用 Post 作為範例解釋 repository 並進行重構。

  1. 我們建立一個 PostRepository,並且加入簡單的 CRUD functions。
namespace App\Repositories;

use App\Models\Post;

class PostRepository
{
    public function create($data)
    {
        $newPost = new Post();
        $newPost->user_id = $data['user_id'];
        $newPost->title = $data['title'];
        $newPost->content = $data['content'];
        $newPost->published_at = $data['published_at'];
        $saveSuccess = $newPost->save();
        if ($saveSuccess) {
            return $newPost;
        }
        throw new \Exception("create failed");
    }

    public function update($id, $data)
    {
        $post = Post::find($id);
        $post->title = $data['title'];
        $updateSuccess = $post->save();
        if ($updateSuccess) {
            return $post;
        }
        throw new \Exception("update failed");
    }

    public function delete($id)
    {
        return Post::destroy($id);
    }

    public function readById($id, $collumns = ['*'])
    {
        return Post::find($id, $collumns);
    }

    // ...
}
  1. 進一步,我們可以想像,一些基礎、常用的 CRUD 「邏輯」皆相同,因此不論是 UserReposetory 或是其他 repositories 都差不多,唯一不同的是,每個依賴的 model 皆不同,例如 UserRepository 會長像這樣:
class UserRepository
{
    public function create($data)
    {
        $newUser = new User();
        $newUser->name = $data['name'];
        $newUser->email = $data['email'];
        $newUser->password = $data['password'];
        $saveSuccess = $newUser->save();
        if ($saveSuccess) {
            return $newUser;
        }
        throw new \Exception("create failed");
    }

    // ... 
  1. 因此重構的第一步,先建立 interface 定義有哪些共同及常用的資料表存取方法。
namespace App\Repositories;

interface IRepository
{
    public function create(array $data);

    public function update($id, array $data);

    public function delete($id);

    public function readById($id);
}
  1. 接著將 interface 中的方法實作於 abstract class 中。需要注意的是,由於每一個 repository 對應的 model 都不同,因此我們保留 model() 這個方法到個別 repository 再實作。
namespace App\Repositories;

use Illuminate\Container\Container as App;

abstract class BaseRepository implements IRepository
{
    private $app;
    private $modelClass;

    public function __construct(App $app)
    {
        $this->app = $app;
        $this->modelClass = $this->model();
    }

    // 回傳各別 repository 要用的 model
    protected abstract function model();

    public function create(array $data)
    {
        $newModelInstance = $this->app->make($this->$modelClass);
        return $this->setModelInstance($newModelInstance, $data);
    }

    public function update($id, array $data)
    {
        $modelInstance = $this->$modelClass::find($id);
        return $this->setModelInstance($modelInstance, $data);
    }

    protected function setModelInstance($instance, array $data = [])
    {
        if (isset($instance)) {
            foreach($data as $property => $value) {
                if (isset($value)) {
                    $instance[$property] = $value;
                }
            }

            $saveSuccess = $instance->save();
            if ($saveSuccess) {
                return $instance;
            }
        }
        throw new \Exception("create/update failed");
    }

    public function delete($id)
    {
        $this->modelClass::destroy($id);
    }

    public function readById($id, $collumns = ['*'])
    {
        return $this->modelClass::find($id, $collumns);
    }

    // ...
}
  1. 最後重構 PostRepository 成下列內容。基本的 CRUD 我們近乎只要寫一次即可,各別的 repository 就專注在自己特殊或是常用的存取邏輯。
class PostRepository extends BaseRepository
{
    protected function model() {
        return Post::class;
    }
    // ...
}

Repository 主要的重點在於為 controller 瘦身,同時主要關注在資料表的存取。因此假若未來更換資料庫,例如從 MySql 改為 MongoDB 我們只要更新各個 repositories 即可,不影響到原本的商業邏輯!另外,透過實作 interface 與繼承 abstract class 可以讓各個 repositories 更簡潔並專注在自己特例的部分。

明天我們將繼續介紹 service,把商業邏輯補齊並讓 controller 減重成功! 順帶一提,範例中各種 Eloquent Model 的功能可以參考官方 API


上一篇
Day 05. 一不小心就會扯遠的依賴注入 (DI)
下一篇
Day 07. Controller 減重計畫 (Service 篇)
系列文
RRR撞到不負責之 Laravel + Nuxt.js 踩坑全紀錄31

尚未有邦友留言

立即登入留言