【ハンズオン / 簡単Todoリスト】Laravelでリポジトリパターンの実装をやってみよう

業務では何かしらのデザインパターンを取り入れることが多いです。
特に大規模なアプリではどこに何のコードがあるのかわかりづらくなりがちなので、
一定の規則を定めてコーディングします。

この規則は様々なものがあり、デザインパターンと呼ばれています。

http://www.itsenka.com/contents/development/designpattern/

今回は、Laravelのプロジェクトで最も採用されているデザインパターンであるリポジトリパターンを取り入れてみましょう。
そして初心者の方でもわかりやすいようハンズオン形式でTodoリストを作っていきましょう！

## 完成品
![test.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838372/da473bcd-0f1a-ad0a-bdb3-fb2d81954884.gif)

一部JSのコードも入っているので、JS以外の部分を解説していきます。

## リポジトリパターンとは？
リポジトリパターンとはビジネスロジックとデータ操作のロジックを切り離し、
データ操作を抽象化したレイヤに任せるデザインパターンのことです。

・テストがしやすくなる
・データ操作が1つにまとまるため保守管理がしやすくなる
といった、メリットがあります。

## インストール

https://github.com/ssk9597/Docker-php-node-nginx-mysql

3行で環境開発ができるためこちらを参考にしてください。
もしエラーが発生した場合はメッセージください。

## 初期設定
Laravelでやっておくべき初期設定は以下の5つです。

1. タイムゾーン
2. 言語設定
3. DBの文字コード
4. エラーメッセージの日本語訳
5. Models/User.phpの設置

### タイムゾーン
タイムゾーンを日本時間に変更します。

```php:config/app.php(70行目あたり)
'timezone' => 'Asia/Tokyo'
```

### 言語設定
言語設定を日本語に変更します。

```php:config/app.php(83行目あたり)
'locale' => 'ja'
```

### DBの文字コード
文字コードをUTF-8に変更します。

```php:config/database.php(55行目~56行目あたり)
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci'
```

### エラーメッセージの日本語訳
`resources/lang/en`内にエラーメッセージのファイルがあるがこれらすべて英語となっています。
そのため、日本語訳するために以下のサイトからダウンロードします。

https://github.com/minoryorg/laravel-resources-lang-ja


ダウンロードしたファイルを`resources/lang/ja`内に格納します。

#### 任意修正箇所

エラーメッセージが一部英語で表示されます。

![スクリーンショット 2021-03-29 12.46.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838372/bd6e1cff-e9db-6279-1032-f1d7f10bc3f7.png)

エラーメッセージの変更は以下のように行います。

```php:resources/lang/ja/validation.php
"attributes" => [
  "password" => "パスワード"
]
```

![スクリーンショット 2021-03-29 12.48.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838372/ad7f5562-cc7b-8eab-6ca1-650af8e4e5f0.png)

### Models/User.phpの設置
**ディレクトリを作成し、User.phpを移動する**

```terminal:ターミナル
$ mkdir Models
$ mv User.php Models
```

**ファイルの修正**

```php:App/Models/User.php(3行目)
namespace App\Models;
```

```php:App/Http/Controllers/Auth/RegisterController.php(7行目)
use App\Models\User;
```

```php:App/Http/Controllers/Auth/RegisterController.php(7行目)
use App\Models\User;
```

```php:config/auth.php(68行目)
'providers' => [
    'users' => [
      'driver' => 'eloquent',
      'model' => App\Models\User::class,
    ],
],
```

```php:database/factories/UserFactory.php(5行目)
use App\Models\User;
```

## まずはModelとMigrationを作成しよう
以下のコマンドで、まずはモデルファイルとマイグレーションファイルを作成します。

```terminal:ターミナル
$ docker-compose exec app bash
$ php artisan make:model Models/Task --migration
```

### マイグレーションファイルに追記

```php:database/migration/2021_04_20_130021_create_tasks_table.php
class CreateTasksTable extends Migration
{
  public function up()
  {
    Schema::create('tasks', function (Blueprint $table) {
      // id
      $table->bigIncrements('id')->comment("id");
      // タスク
      $table->string("task", 255)->comment("タスク");
      // ステータス
      $table->boolean("status")->default(false)->comment("状況(0: 作業中, 1:完了)");
      // タイムスタンプ
      $table->timestamps();
    });
  }

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

### モデルファイルに追記
データベースの操作で、**fill**を使うのでfillableに値を代入してます。

```php:app/Models/Task.php
class Task extends Model
{
  // $fillable
  protected $fillable = ["task", "status"];
}
```

## 【今回の本題】Repositoryの作成をしよう
フォルダの構成は以下のようになります。

```
├── Models
│   ├── Task.php
│   
├── Repositories
    └── Task
        ├── TaskRepository.php
        └── TaskRepositoryInterface.php
```

ということで、フォルダとファイルを作成していきましょう。

```terminal:ターミナル
// appディレクトリに移動
$ cd src/app

// Repositoriesフォルダを作成
$ mkdir Repositories

// Repositories/Taskフォルダを作成
$ mkdir Repositories/Task

// TaskRepository.phpを作成
$ touch Repositories/Task/TaskRepository.php

// TaskRepositoryInterface.phpを作成
$ touch Repositories/Task/TaskRepositoryInterface.php
```

### インターフェイス設計

```php:app/Repositories/Task/TaskRepositoryInterface.php
<?php

namespace App\Repositories\Task;

interface TaskRepositoryInterface
{
  // tasksテーブルをすべて取得
  public function getTasks();

  // tasksテーブルに追加
  public function createTask($request);

  // tasksテーブルの要素を更新
  public function updateTask($id);

  // tasksテーブルの要素を削除
  public function destroyTask($id);
}
```

### インプリメントクラス

```php:app/Repositories/Task/TaskRepository.php
<?php

namespace App\Repositories\Task;

// models
use App\Models\Task;

class TaskRepository implements TaskRepositoryInterface
{
  // tasksテーブルをすべて取得
  public function getTasks()
  {
    return Task::all();
  }

  // tasksテーブルに追加
  public function createTask($request)
  {
    // instance
    $task = new Task;

    // 値の代入
    return $task->fill($request->all())->save();
  }

  // tasksテーブルの要素を更新
  public function updateTask($id)
  {
    // 取得
    $value = Task::find($id);

    // ステータスを更新
    return $value->fill(["status" => !$value->status])->save();
  }

  // tasksテーブルの要素を削除
  public function destroyTask($id)
  {
    // 取得
    $value = Task::find($id);

    // 取得して削除
    return $value->delete();
  }
}
```

## Providerファイルにインターフェイスとインプリメントを登録

```php:app/Providers/AppServiceProvider.php
class AppServiceProvider extends ServiceProvider
{
  public function register()
  {
    //Task
    $this->app->bind(
      \App\Repositories\Task\TaskRepositoryInterface::class,
      \App\Repositories\Task\TaskRepository::class
    );
  }

  public function boot()
  {
  }
}
```

## Viewファイルを作成
フォルダの構成は以下のようになります。

```
├── views
    └── tasks
        ├── index.blade.php
```

```php:resources/views/tasks/index.blade.php
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <title>Laravel</title>
</head>

<body>
    <h2>ToDoリスト</h2>
    <form name="sort">
        <input type="radio" name="type" id="all" checked value="すべて" onclick="clickType()">
        <label for="all">すべて</label>
        <input type="radio" name="type" id="work" value="作業中" onclick="clickType()">
        <label for="work">作業中</label>
        <input type="radio" name="type" id="complete" value="完了" onclick="clickType()">
        <label for="complete">完了</label>
    </form>

    <table>
        <thead>
            <tr>
                <th>ID</th>
                <th>コメント</th>
                <th>状態</th>
            </tr>
        </thead>
        <tbody>
            <form action="{{ route('tasks.index') }}" method="GET">
                @csrf
                @foreach ($values as $value)
                    @if ($value->status === 0)
                        <tr class="workTask">
                            <td>{{ $loop->iteration - 1 }}</td>
                            <td>{{ $value->task }}</td>
                            <form action="{{ route('tasks.update', $value->id) }}" method="POST">
                                @csrf
                                @method("PUT")
                                <td><input type="submit" value="作業中"></td>
                            </form>
                            <form action="{{ route('tasks.destroy', $value->id) }}" method="POST">
                                @csrf
                                @method("DELETE")
                                <td><input type="submit" value="削除"></td>
                            </form>
                        </tr>
                    @else
                        <tr class="completeTask">
                            <td>{{ $loop->iteration - 1 }}</td>
                            <td>{{ $value->task }}</td>
                            <form action="{{ route('tasks.update', $value->id) }}" method="POST">
                                @csrf
                                @method("PUT")
                                <td><input type="submit" value="完了"></td>
                            </form>
                            <form action="{{ route('tasks.destroy', $value->id) }}" method="POST">
                                @csrf
                                @method("DELETE")
                                <td><input type="submit" value="削除"></td>
                            </form>
                        </tr>
                    @endif
                @endforeach
            </form>
        </tbody>
    </table>

    <h2>新規タスクの追加</h2>
    <form action="{{ route('tasks.store') }}" method="POST">
        @csrf
        <input type="text" name="task">
        <input type="submit" value="追加">
    </form>
    @error('task')
        <span>
            <strong style="color: red">{{ $message }}</strong>
        </span>
    @enderror

    <script src="{{ asset('/js/index.js') }}"></script>
</body>

</html>
```

## コントローラーを作成
実装したリポジトリパターンを呼び出しましょう！
インターフェイスをインジェクションするだけなので簡単です。

```terminal:ターミナル 
$ docker-compose exec app bash
$ php artisan make:controller TaskController --resource
```

```php:app/Http/Controllers/TaskController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

// Validation
use App\Http\Requests\StoreTaskPost;

// Repositories
use App\Repositories\Task\TaskRepositoryInterface;

class TaskController extends Controller
{
  // construct
  public function __construct(TaskRepositoryInterface $task_repository)
  {
    $this->task_repository = $task_repository;
  }

  public function index()
  {
    // Repositories->getTasks()
    $values = $this->task_repository->getTasks();

    return view("tasks.index", compact("values"));
  }

  public function store(StoreTaskPost $request)
  {
    // Repositories->createTask()
    $this->task_repository->createTask($request);

    return redirect("/tasks");
  }

  public function update(Request $request, $id)
  {
    // Repositories->updateTask()
    $this->task_repository->updateTask($id);

    return redirect("/tasks");
  }

  public function destroy($id)
  {
    // Repositories->destroyTask()
    $this->task_repository->destroyTask($id);

    return redirect("/tasks");
  }
}
```

## 最後にルーティング処理を記載しましょう

```php:routes/web.php
<?php

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
*/

// Task::resource
Route::resource('tasks', 'TaskController', ['only' => ['index', 'store', 'update', 'destroy']]);
```

これで完了です。
JavaScriptとバリデーションの部分を省略してますが、、
ここが気になる方がいましたらご連絡ください。

## 感想
モデルとコントローラーがスッキリするとともに管理しやすくなった気がします！