0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Posted at

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

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

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

完成品

test.gif

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

リポジトリパターンとは?

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

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

インストール

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

初期設定

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

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

タイムゾーン

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

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

言語設定

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

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

DBの文字コード

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

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

エラーメッセージの日本語訳

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

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

任意修正箇所

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

スクリーンショット 2021-03-29 12.46.06.png

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

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

スクリーンショット 2021-03-29 12.48.11.png

Models/User.phpの設置

ディレクトリを作成し、User.phpを移動する

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

ファイルの修正

App/Models/User.php(3行目)
namespace App\Models;
App/Http/Controllers/Auth/RegisterController.php(7行目)
use App\Models\User;
App/Http/Controllers/Auth/RegisterController.php(7行目)
use App\Models\User;
config/auth.php(68行目)
'providers' => [
    'users' => [
      'driver' => 'eloquent',
      'model' => App\Models\User::class,
    ],
],
database/factories/UserFactory.php(5行目)
use App\Models\User;

まずはModelとMigrationを作成しよう

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

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

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

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に値を代入してます。

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

【今回の本題】Repositoryの作成をしよう

フォルダの構成は以下のようになります。

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

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

ターミナル
// 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

インターフェイス設計

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);
}

インプリメントクラス

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ファイルにインターフェイスとインプリメントを登録

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
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>

コントローラーを作成

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

ターミナル
$ docker-compose exec app bash
$ php artisan make:controller TaskController --resource
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");
  }
}

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

routes/web.php
<?php

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

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

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

感想

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

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?