Laravel - Laravel を使ったWebアプリケーション の基本パターンを実装してみた ( Service / Repository Pattern)

## はじめに

Laravel を使用した WebApplication を作成するときに、毎回同じようなセットアップを行うので、共通部分を作成してみました。

## 概要

コードは以下で公開しています。

- [anfangd/blog-sample - GitHub](https://github.com/anfangd/blog-sample)
- [anfangd/blog-sample - Wiki - GitHub](https://github.com/anfangd/blog-sample/wiki)

## システムの全体像

### システム構成

このコードは次のようなシステム構成において、Application Server にて実行されることを想定しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79222/ae4710e0-24c9-fdca-2246-82b789d4756c.png)

### アプリケーション・アーキテクチャ

Laravel で実現するアプリケーションのアーキテクチャは、次のような構成となります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79222/13ac2184-bd58-65a9-0cc2-e3e2c75f2df2.png)

### Entity Relation

仮で作成するエンティティは次のような構成です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79222/94e5b524-9c00-01c6-13b4-2bd3926a4b84.png)

## 開発環境

開発環境は次のようなソフトウェア・スタックです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79222/5775cab5-0c35-dfbd-f647-1ab93d98be0f.png)

Laravel と 関連ツールという側面でもう少し絞ったソフトウェア・スタックは次のような構成です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79222/e2458c37-953d-49c8-50c3-5ce50d833c1e.png)

上記の開発環境の構築については、以下の手順を参照してください。

- [docker - ローカル開発環境の構築 / Laravel on Vagrant + VirtualBox with Docker](https://qiita.com/anfangd/items/8c57f3d753117ea288b9)
- [Visual Studio Code - PHPの開発環境を VSCode と Vagrant + Docker で構築する時のツール類](https://qiita.com/anfangd/items/0684ddf34f747873c511)

## 手順

### 概要

今回作成するアプリケーションの作成手順は次の通りです。

1. 事前準備
  1. 開発環境を用意する（仮想環境、エディタ）
  2. Laravel プロジェクトの新規作成と関連ツールの導入
2. 構築を始める
  1. Model の作成
  2. Controller の作成
  3. カスタムログクラスの作成
  4. Repository クラスの作成
  5. Service クラスと Controller クラスの結合
  6. Service クラスの作成 と Repository クラスとの結合
3. 動作確認する

1.) については、「開発環境」にて記載したリンクの手順で事前に作成している前提で、それ以降について詳細を記載します。

### 1-1. 開発環境を用意する（仮想環境、エディタ）

- VirtualBox + Vagrant
- Visual Studio Code

### 1-2. Laravel プロジェクトの新規作成と関連ツールの導入

- [docker - ローカル開発環境の構築 / Laravel on Vagrant + VirtualBox with Docker - Qiita](https://qiita.com/anfangd/items/8c57f3d753117ea288b9)
- [Visual Studio Code - PHPの開発環境を VSCode と Vagrant + Docker で構築する時のツール類 - Qiita](https://qiita.com/anfangd/items/0684ddf34f747873c511)

### 2-1. Model の作成

次のコマンドで Model と migration のファイルを作成します。

```bash:command
# Create Model and Migration Files
php artisan make:model Models/Table/TblUser --migration
php artisan make:model Models/Table/TblBlogPost --migration
```

作成した migration ファイルに必要なカラムを追加するよう追記します。

```php:database/migrations/XXXXX_create_tbl_users_table.php
    public function up()
    {
        Schema::create('tbl_users', function (Blueprint $table) {
            $table->bigIncrements('id')->unsigned();
            $table->string('name');
            $table->timestamps();
            $table->softDeletes();
        });
    }
```
```php:database/migrations/XXXXX_create_tbl_blog_posts_table.php
    public function up()
    {
        Schema::create('tbl_blog_posts', function (Blueprint $table) {
            $table->bigIncrements('id')->unsigned();
            $table->unsignedBigInteger('user_id');
            $table->string('title');
            $table->text('content');
            $table->timestamps();
            $table->softDeletes();

            //外部キー制約
            $table->foreign('user_id')
                    ->references('id')
                    ->on('tbl_users')
                    ->onDelete('cascade');
        });
    }

```

作成した migration ファイルを実行します。

```bash:command
# execute migration
php artisan migrate

# You can rollback migration if you want.
php artisan migrate:rollback 
```

- [Laravelでモデル作成 - Qiita](https://qiita.com/mikaku/items/49625dc04073bf55dd75)
- [データベース：マイグレーション 5.5 Laravel](https://readouble.com/laravel/5.5/ja/migrations.html)
- [Laravelお役立ちネタ6連発](https://qiita.com/mpyw/items/5c1943ed9edd381fe50d)
- [Laravelアプリで開発テスト - 超準備編 - Qiita](https://qiita.com/wantonbe/items/9a7e13ab362457bdbed1)
- [Laravelアプリで開発テスト2 - データベースのテスト編（前編） - Qiita](https://qiita.com/wantonbe/items/630ed0c3290b4da5bcd9#_reference-3088c719279372509af4)
- [PHP dotenvの読み込み方が変わってた - Qiita](https://qiita.com/sngazm/items/d27639f54e0abe723da9)

### 2-2. Controller の作成

`artisan` コマンドで REST API を実現する基本メソッドを実装した Controler を自動生成します。

```bash:command
php artisan make:controller UserController --resource
php artisan make:controller BlogPostController --resource
```

作成したコントローラへのルートを追加する。

```php:routes/api.php
# 次のルートを追加する。
Route::resource('users', 'UserController');
Route::resource('blog-posts', 'BlogPostController');
```

作成したルートの設定を確認します。

```
# php artisan route:list
+--------+-----------+---------------------------------+--------------------+-------------------------------------------------+--------------+
| Domain | Method    | URI                             | Name               | Action                                          | Middleware   |
+--------+-----------+---------------------------------+--------------------+-------------------------------------------------+--------------+
|        | GET|HEAD  | /                               |                    | Closure                                         | web          |
|        | POST      | api/blog-posts                  | blog-posts.store   | App\Http\Controllers\BlogPostController@store   | api          |
|        | GET|HEAD  | api/blog-posts                  | blog-posts.index   | App\Http\Controllers\BlogPostController@index   | api          |
|        | GET|HEAD  | api/blog-posts/create           | blog-posts.create  | App\Http\Controllers\BlogPostController@create  | api          |
|        | GET|HEAD  | api/blog-posts/{blog_post}      | blog-posts.show    | App\Http\Controllers\BlogPostController@show    | api          |
|        | PUT|PATCH | api/blog-posts/{blog_post}      | blog-posts.update  | App\Http\Controllers\BlogPostController@update  | api          |
|        | DELETE    | api/blog-posts/{blog_post}      | blog-posts.destroy | App\Http\Controllers\BlogPostController@destroy | api          |
|        | GET|HEAD  | api/blog-posts/{blog_post}/edit | blog-posts.edit    | App\Http\Controllers\BlogPostController@edit    | api          |
|        | GET|HEAD  | api/user                        |                    | Closure                                         | api,auth:api |
|        | GET|HEAD  | api/users                       | users.index        | App\Http\Controllers\UserController@index       | api          |
|        | POST      | api/users                       | users.store        | App\Http\Controllers\UserController@store       | api          |
|        | GET|HEAD  | api/users/create                | users.create       | App\Http\Controllers\UserController@create      | api          |
|        | GET|HEAD  | api/users/{user}                | users.show         | App\Http\Controllers\UserController@show        | api          |
|        | PUT|PATCH | api/users/{user}                | users.update       | App\Http\Controllers\UserController@update      | api          |
|        | DELETE    | api/users/{user}                | users.destroy      | App\Http\Controllers\UserController@destroy     | api          |
|        | GET|HEAD  | api/users/{user}/edit           | users.edit         | App\Http\Controllers\UserController@edit        | api          |
+--------+-----------+---------------------------------+--------------------+-------------------------------------------------+--------------+
```

作成したコントローラのテストコードを作成します。

```bash:command
# Feature Test
# API 単位のテスト
php artisan make:test Http/Controllers/UserControllerTest
php artisan make:test Http/Controllers/BlogPostController

# Unit Test
# Method 単位のテスト
php artisan make:test Http/Controllers/UserControllerTest --unit
php artisan make:test Http/Controllers/BlogPostControllerTest --unit
```

テストコードを実行します。

```bash:command
composer exec -v phpunit
> __exec_command: phpunit
PHPUnit 7.5.14 by Sebastian Bergmann and contributors.

..............                                                    14 / 14 (100%)

Time: 1.95 seconds, Memory: 18.00 MB

OK (14 tests, 14 assertions)
```

- [LaravelでRestAPIをつくる - Qiita](https://qiita.com/bmf_san/items/90a03e89cfef7ff0f295)
- [[PHP] Laravel5.6で、サクッとRESTful APIを作成する](https://www.yoheim.net/blog.php?q=20180502)
- [Laravel 5.4 テスト: テストの準備 - Laravel](https://readouble.com/laravel/5.4/ja/testing.html)
- [Laravel5でテスト(PHPUnit) - Qiita](https://qiita.com/zaburo/items/839c81a1e166a48fe3fa)

### 2-3. Custom Log Class の追加

次の Merge Request を参照してください。

- [Merge pull request #11 from anfangd/feature/#10_create_Custom_Logging - GitHub](https://github.com/anfangd/blog-sample/commit/da7813782d5b7ee0b1f36ec474a1418b9cb94f18?diff=split)

cf.

- [Laravelでログのフォーマット変更する方法 - ノーバグノーライフ](https://blog.nobug-nolife.com/entry/20181202/1543762300)
- [PHPのロガーMonologを理解しよう - QUARTETCOM TECH BLOG ](https://tech.quartetcom.co.jp/2018/05/31/monolog/)
- [Laravel キャッシュクリア系コマンドなど - Qiita](https://qiita.com/Ping/items/10ada8d069e13d729701)
- [Laravel 5.6 ログ](https://readouble.com/laravel/5.6/ja/logging.html)
- [【Laravel】ログファイルのフォーマットを LTSV に変更する - Qiita](https://qiita.com/okashoi/items/452486cc036c02a6e8f4)

### 2-4. Repository クラスの作成

次の Merge Request を参照してください。

- [Merge pull request #13 from anfangd/feature/#12_create_Repository - GitHub](https://github.com/anfangd/blog-sample/commit/83c334bbd0eb3fb184aaea306c1b5fc1f60cc8d4)

cf. 

- [Laravel リポジトリパターンの実装 - Qiita](https://qiita.com/bmf_san/items/c8d7b38b5f1f5747c2fd)


### 2-5. Service クラスと Controller クラスの結合

次の Merge Request を参照してください。

- [Merge pull request #15 from anfangd/feature/#14_create_Service_Classes - GitHub](https://github.com/anfangd/blog-sample/commit/20da1c465a8f20d0a8b76a4b3ae46322778fffec)

cf. 

- [【Laravel】サービスクラス作成手順 - ブロックチェーンエンジニアのブログ](https://daiki-sekiguchi.com/2018/08/31/laravel-how-to-make-service-class/)
- [Laravel で Service を使う - Qiiat](https://qiita.com/ekzemplaro/items/354166c8612e45dbe822)

### 2-6. Service クラスの作成 と Repository クラスとの結合

次の Merge Request を参照してください。

- [[update] connect service class and repository class #17 - GitHub](https://github.com/anfangd/blog-sample/pull/17/files)

### 3. 動作確認する

ブラウザで以下の URL にアクセスし、ログ・ファイルに出力されるログを確認する。

- http://192.168.33.10:8080/api/users
- http://192.168.33.10:8080/api/blog-posts

## その他

### テストカバレッジ

次のコマンドでテストカバレッジを HTML 出力することができます。

```bash:command
# Execute at project root
# see PROJECT_ROOT/coverage/
composer test:coverage
```

### UnitTest 用のデータベース

テスト実行時用のデータベースを作成し、ユニットテスト実行時にはそちらのDBを参照するようにします。

まずは次のように、テスト用の `.env` を作成します。（変更箇所のみ記載）

```php:.env.testing

# ここを testing に変更
APP_ENV=testing

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
# db をテスト用のDBに変更
DB_DATABASE={{ db_test }}
DB_USERNAME={{ db_user }}
DB_PASSWORD={{ db_password }}
```

以下のコマンドでテスト環境向けDBをマイグレーションした上で、その環境を使用してテストを実行することができます。

```bash:command
# Migrate to test schema
php artisan migrate --env=testing

# Run Test Code using `APP_ENV=testing`
composer test:run
```


## Todo

- Request / Response クラスの実装に関する解説
- Unit Test Code の作成

## おわりに

### 2019/08/19

本当にざっくりの構成だけ作成しました。
途中から Unit Test Code の作成がおざなりになってしまいました。
もう少し拡張する予定です。

## 参考 

- [docker - ローカル開発環境の構築 / Laravel on Vagrant + VirtualBox with Docker](https://qiita.com/anfangd/items/8c57f3d753117ea288b9)
- [Visual Studio Code - PHPの開発環境を VSCode と Vagrant + Docker で構築する時のツール類](https://qiita.com/anfangd/items/0684ddf34f747873c511)