Laravel 5.8 中正確地應用 Repository 設計模式

守候2009發表於2020-08-27

在本文中,我會向你展示如何在 Laravel 中從頭開始實現 repository 設計模式。我將使用 Laravel 5.8.3 版,但 Laravel 版本不是最重要的。在開始寫程式碼之前,你需要了解一些關於 repository 設計模式的相關資訊。

repository 設計模式允許你使用物件,而不需要了解這些物件是如何持久化的。本質上,它是資料層的抽象。

這意味著你的業務邏輯不需要了解如何檢索資料或資料來源是什麼,業務邏輯依賴於 repository 來檢索正確的資料。

關於這個模式,我看到有人將它誤解為 repository 被用來建立或更新資料。 這不是 repository 應該做的,repository 不應該建立或更新資料,僅僅用於檢索資料。

理解透了吧?接下來一起寫程式碼

既然我們從頭開始,那麼我們先建立一個新的 Laravel 專案吧:

composer create-project --prefer-dist laravel/laravel repository

對於本教程,我們將構建一個小型的部落格應用。現在我們已經建立好了一個新的 Laravel 專案,接下來應該為它建立一個控制器和模型。

php artisan make:controller BlogController

這將在 app/Http/Controllers 目錄中建立 BlogController

php artisan make:model Models/Blog -m

提示:
-m 選項會建立一個對應的資料庫遷移,你可以在 *database/migrations
目錄中找到所生成的遷移。*

現在你應該能在 app/Models 目錄中找到剛生成的模型 Blog 了吧。這只是一種我喜歡的存放模型的方式。

現在我們有了控制器和模型,是時候看看我們建立的遷移檔案了。除了預設的 Laravel 時間戳欄位外,我們的部落格只需要 標題、內容使用者ID 欄位。

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateBlogsTable extends Migration
{
    public function up()
    {
        Schema::create('blogs', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('title');
            $table->text('content');
            $table->integer('user_id');
            $table->timestamps();

            $table->foreign('user_id')
                  ->references('id')
                  ->on('users');
        });
    }

    public function down()
    {
        Schema::dropIfExists('blogs');
    }
}

提示:
如果你使用的是 Laravel 5.8 以下的舊版本,請將

$table->bigIncrements('id');

替換為:

$table->increments('id');

設定資料庫

我將使用 MySQL 資料庫作為示例,第一步就是建立一個新的資料庫。

mysql -u root -p 
create database laravel_repository;

以上命令將會建立一個叫 laravel_repository 的新資料庫。接下來我們需要新增資料庫資訊到 Laravel 根目錄的 .env 檔案中。

DB_DATABASE=laravel_repository
DB_USERNAME=root
DB_PASSWORD=secret

當你更新了 .env 檔案後我們需要清空快取:

php artisan config:clear

執行遷移

現在我們已經設定好了資料庫,可以開始執行遷移了:

php artisan migrate

這將會建立 blogs 表,包含了我們在遷移中宣告的 title , contentuser_id 欄位。

實現 repository 設計模式

一切就緒,我們現在可以開始實現 repository 設計風格了。我們將會在 app 目錄中建立 Repositories 目錄。我們將要建立的第二個目錄是 Interfaces 目錄,這個目錄位於 Repositories 目錄中。

Interfaces 檔案中我們將建立一個包含兩個方法的 BlogRepositoryInterface 介面。

  1. 返回所有部落格文章的 all 方法
  2. 返回特定使用者所有部落格文章的 getByUser 方法
<?php

namespace App\Repositories\Interfaces;

use App\User;

interface BlogRepositoryInterface
{
    public function all();

    public function getByUser(User $user);
}

我們需要建立的最後一個類是將要實現 BlogRepositoryInterfaceBlogRepository ,我們會寫一個最簡單的實現方式。

<?php

namespace App\Repositories;

use App\Models\Blog;
use App\User;
use App\Repositories\Interfaces\BlogRepositoryInterface;

class BlogRepository implements BlogRepositoryInterface
{
    public function all()
    {
        return Blog::all();
    }

    public function getByUser(User $user)
    {
        return Blog::where('user_id',$user->id)->get();
    }
}

你的 Repositories 目錄應該像這樣:

app/
└── Repositories/
    ├── BlogRepository.php
    └── Interfaces/
        └── BlogRepositoryInterface.php

你現在已經成功建立了一個 repository 了。但是我們還沒有完成,是時候開始使用我們的 repository 了。

在控制器中使用 Repository

要開始使用 BlogRepository ,我們首先需要將其注入到 BlogController 。由於 Laravel 的依賴注入,我們很容易用另一個來替換它。這就是我們控制器的樣子:

<?php

namespace App\Http\Controllers;


use App\Repositories\Interfaces\BlogRepositoryInterface;
use App\User;

class BlogController extends Controller
{
    private $blogRepository;

    public function __construct(BlogRepositoryInterface $blogRepository)
    {
        $this->blogRepository = $blogRepository;
    }

    public function index()
    {
        $blogs = $this->blogRepository->all();

        return view('blog')->withBlogs($blogs);
    }

    public function detail($id)
    {
        $user = User::find($id);
        $blogs = $this->blogRepository->getByUser($user);

        return view('blog')->withBlogs($blogs);
    }
}

如你所見,控制器中的程式碼很簡短,可讀性非常的高。不需要十行程式碼就可以獲取到所需的資料,多虧了 repository ,所有這些邏輯都可以在一行程式碼中完成。這對單元測試也很好,因為 repository 的方法很容易複用。

repository 設計模式也使更改資料來源變得更加容易。在這個例子中,我們使用 MySQL 資料庫來檢索我們的部落格內容。我們使用 Eloquent 來完成查詢資料庫操作。但是假設我們在某個網站上看到了一個很棒的部落格 API,我們想使用這個 API 作為資料來源,我們所要做的就是重寫 BlogRepository 來呼叫這個 API 替換 Eloquent

RepositoryServiceProvider

我們將注入 BlogController 中的 BlogRepository ,而不是注入 BlogController 中的 BlogRepositoryInterface ,然後讓服務容器決定將使用哪個儲存庫。這將在 AppServiceProviderboot 方法中實現,但我更喜歡為此建立一個新的 provider 來保持整潔。

php artisan make:provider RepositoryServiceProvider

我們為此建立一個新的 provider 的原因是,當您的專案開始發展為大型專案時,結構會變得非常凌亂。設想一下,一個擁有 10 個以上模型的專案,每個模型都有自己的 repository ,你的 AppServiceProvider 可讀性將會大大降低。

我們的 RepositoryServiceProvider 會像下面這樣:

<?php

namespace App\Providers;

use App\Repositories\BlogRepository;
use App\Repositories\Interfaces\BlogRepositoryInterface;
use Illuminate\Support\ServiceProvider;

class RepositoryServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->bind(
            BlogRepositoryInterface::class, 
            BlogRepository::class
        );
    }
}

留意用另一個 repository 替代 BlogRepository 是多麼容易!

不要忘記新增 RepositoryServiceProviderconfig/app.php 檔案的 providers 列表中。完成了這些後我們需要清空快取:

'providers' => [
    //測試¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥
  \App\Providers\RepositoryServiceProvider::class

],
php artisan config:clear

就是這樣

現在你已經成功實現了 repository 設計模式,不是很難吧?

你可以選擇增加一些路由和檢視來擴充程式碼,但本文將在這裡結束,因為本文主要是介紹 repository 設計模式的。

如果你喜歡這篇文章,或者它幫助你實現了 repository 設計模式,請確保你也檢視了我的其他文章。如果你有任何反饋、疑問,或希望我撰寫另一個有關 Laravel 的主題,請隨時發表評論。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章