建立專案
建立專案。
1
   | laravel new laravel-abstract-repository-example
   | 
 
資料庫
使用 SQLite 資料庫,在 database 資料夾新增 database.sqlite 檔,修改 .env 檔如下:
1 2 3 4 5 6
   | DB_CONNECTION=sqlite DB_HOST=127.0.0.1 DB_PORT=3306 # DB_DATABASE=laravel DB_USERNAME=root DB_PASSWORD=
   | 
 
資料夾結構
資料夾結構如下:
1 2 3 4 5 6 7
   | |- app/   |- Repositories/     |- Contracts/       |- RepositoryInterface.php        |- UserRepositoryInterface.php      |- Repository.php      |- UserRepository.php 
   | 
 
介面
在 app/Repositories/Contracts 資料夾新增一個 RepositoryInterface.php 介面,規定所有模型共用的資料庫存取方法。
1 2 3 4 5 6 7 8
   | namespace App\Repositories\Contracts;
  use Illuminate\Database\Eloquent\Model;
  interface RepositoryInterface {     public function getById(int $id): Model; }
   | 
 
在 app/Repositories/Contracts 資料夾新增一個 UserRepositoryInterface.php 介面,規定 User 模型專屬的資料庫存取方法。
1 2 3 4 5 6 7 8 9
   | namespace App\Repositories\Contracts;
  use Illuminate\Database\Eloquent\Model;
  interface UserRepositoryInterface {     public function getById(int $id): Model;     public function getByEmail(string $email): Model; }
   | 
 
實作
在 app/Repositories 資料夾新增一個 Repository 抽象類別,實作 RepositoryInterface 介面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
   | namespace App\Repositories;
  use App\Repositories\Contracts\RepositoryInterface; use Illuminate\Database\Eloquent\Model;
  abstract class Repository implements RepositoryInterface {     
 
      protected Model $model;
      
 
      abstract public function model(): string;
      
 
 
 
      public function __construct()     {         $this->model = app($this->model());     }
      
 
 
      public function getById(int $id): Model     {         return $this->model->findOrFail($id);     } }
   | 
 
在 app/Repositories 資料夾新增一個 UserRepository 具象類別,擴展 Repository 抽象類別,並實作 UserRepositoryInterface 介面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
   | namespace App\Repositories;
  use App\User; use App\Repositories\Contracts\UserRepositoryInterface; use Illuminate\Database\Eloquent\Model;
  class UserRepository extends Repository implements UserRepositoryInterface {     
 
      public function model(): string     {         return User::class;     }
      
 
 
      public function getByEmail(string $email): Model     {         return $this->model->where('email', $email)->firstOrFail();     } }
   | 
 
服務提供者
新增一個 RepositoryServiceProvider 服務提供者。
1
   | php artisan make:provider RepositoryServiceProvider
   | 
 
在服務提供者註冊容器綁定,並且實作 DeferrableProvider 介面延遲加載:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
   | namespace App\Providers;
  use App\Repositories\Contracts\UserRepositoryInterface; use App\Repositories\UserRepository; use Illuminate\Contracts\Support\DeferrableProvider; use Illuminate\Support\ServiceProvider;
  class RepositoryServiceProvider extends ServiceProvider implements DeferrableProvider {     
 
 
 
      public function register()     {         $this->app->bind(             UserRepositoryInterface::class,             UserRepository::class         );     }
      
 
 
 
      public function boot()     {              }
      
 
 
 
      public function provides()     {         return [             UserRepositoryInterface::class,         ];     } }
   | 
 
- 由於此服務提供者只有做容器綁定的註冊,因此可以延遲載入,以提升系統效能。Laravel 會在綁定的服務被解析時,到 
bootstrap/cache/services.php 檔中加載對應的服務提供者。 
將服務提供者註冊到 config 資料夾的 app.php 中:
1 2 3 4 5 6 7 8 9 10
   | return [
      'providers' => [
                   App\Providers\RepositoryServiceProvider::class,
      ],
  ];
   | 
 
重新產生 Composer 自動載入檔案。
路由
修改 routes 資料夾的 web.php 檔:
1 2
   | Route::get('/id/{id}', 'UserController@getById'); Route::get('/email/{email}', 'UserController@getByEmail');
  | 
 
控制器
新增一個 UserController 控制器。
1
   | php artisan make:controller UserController
   | 
 
修改控制器,將 UserRepositoryInterface 介面注入到建構子中,並實作相關方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
   | namespace App\Http\Controllers;
  use App\Repositories\Contracts\UserRepositoryInterface; use App\User;
  class UserController extends Controller {     private $repository;
      public function __construct(UserRepositoryInterface $repository)     {         $this->repository = $repository;     }
      public function getById($id): User     {         return $this->repository->getById($id);     }
      public function getByEmail($email): User     {         return $this->repository->getByEmail($email);     } }
   | 
 
測試
新增一個 UserControllerTest 測試案例。
1
   | php artisan make:test UserControllerTest
   | 
 
修改測試案例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
   | namespace Tests\Feature;
  use App\User; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\WithFaker; use Tests\TestCase;
  class UserControllerTest extends TestCase {     use RefreshDatabase;
      
 
      public function testGetById()     {         $user = factory(User::class)->create();
          $response = $this->get('/id/'.$user->id);
          $response             ->assertStatus(200)             ->assertJson($user->toArray());     }
      
 
      public function testGetByEmail()     {         $user = factory(User::class)->create();
          $response = $this->get('/email/'.$user->email);
          $response->assertStatus(200)             ->assertJson($user->toArray());     } }
   | 
 
執行測試。
程式碼