Lesson 11

TODOアプリを作成

Lesson 11 Chapter 1
TODO作成用クエリの実装

Lesson11では各クエリを準備しTODOの一連の操作が行えるよう実装していきましょう。

TODO作成準備

まず初めにTODO作成の処理から実装していきます。TODO作成画面(URL:http://127.0.0.1:8000/create)を開いてください。現状では登録処理はまだ行えない為、必要な処理を準備していきましょう。

TODO作成画面

Enumの作成

タスク追加フォーム内にある「時間」のセレクトボックスに注目してください。こちらは時間を選択できる項目ではありますが、まだ値が存在しません。その為、時間の値を管理しておくEnumファイルの作成を行います。

1. Enumファイルの作成コマンドの実行

Enumファイルを作成するにはEnumのコマンドを有効にする必要があります。そのためにcomposerコマンドを実行しましょう。

ターミナル、コマンドプロンプト
composer require bensampo/laravel-enum

続いてはEnumファイル作成のコマンドを実行します。末尾は作成するファイル名なので任意になります。

ターミナル、コマンドプロンプト
php artisan make:enum SelectTime

2. Enumファイルの編集

作成されたEnumファイルSelectTime.phpを開いてみましょう。

app > Enums > SelectTime.php

SelectTime.php
<?php

namespace App\Enums;

use BenSampo\Enum\Enum;

/**
 * @method static static OptionOne()
 * @method static static OptionTwo()
 * @method static static OptionThree()
 */
final class SelectTime extends Enum
{
    const OptionOne = 0;
    const OptionTwo = 1;
    const OptionThree = 2;
}

作成されたSelectTime.phpの中にメソッドの作成と定数の定義を行っていきます。記述が完了したSelectTime.phpファイルは以下になります。

SelectTime.php
<?php

namespace App\Enums;

use BenSampo\Enum\Enum;

/**
 * @method static static OptionOne()
 * @method static static OptionTwo()
 * @method static static OptionThree()
 */
final class SelectTime extends Enum
{
    // 時間
    const ONE_OCLOCK = "1:00";
    const TWO_OCLOCK = "2:00";
    const THREE_OCLOCK = "3:00";
    const FOUR_OCLOCK = "4:00";
    const FIVE_OCLOCK = "5:00";
    const SIX_OCLOCK = "7:00";
    const SEVEN_OCLOCK = "7:00";
    const EIGHT_OCLOCK = "8:00";
    const NINE_OCLOCK = "9:00";
    const TEN_OCLOCK = "10:00";
    const ELEVEN_OCLOCK = "11:00";
    const TWELVE_OCLOCK = "12:00";
    const THIRTEEN_OCLOCK = "13:00";
    const FOURTEEN_OCLOCK = "14:00";
    const FIFTEEN_OCLOCK = "15:00";
    const SIXTEEN_OCLOCK = "16:00";
    const SEVENTEEN_OCLOCK = "17:00";
    const EIGHTEEN_OCLOCK = "18:00";
    const NINETEEN_OCLOCK = "19:00";
    const TWENTY_OCLOCK = "20:00";
    const TWENTYONE_OCLOCK = "21:00";
    const TWENTYTWO_OCLOCK = "22:00";
    const TWENTYTHREE_OCLOCK = "23:00";
    const TWENTYFOUR_OCLOCK = "24:00";

    public static function getDescription($value): string
    {
        switch ($value) {
            case self::ONE_OCLOCK:
                return "1:00";

            case self::TWO_OCLOCK:
                return "2:00";

            case self::THREE_OCLOCK:
                return "3:00";

            case self::FOUR_OCLOCK:
                return "4:00";

            case self::FIVE_OCLOCK:
                return "5:00";

            case self::SIX_OCLOCK:
                return "6:00";

            case self::SEVEN_OCLOCK:
                return "7:00";

            case self::EIGHT_OCLOCK:
                return "8:00";

            case self::NINE_OCLOCK:
                return "9:00";

            case self::TEN_OCLOCK:
                return "10:00";

            case self::ELEVEN_OCLOCK:
                return "11:00";

            case self::TWELVE_OCLOCK:
                return "12:00";

            case self::THIRTEEN_OCLOCK:
                return "13:00";

            case self::FOURTEEN_OCLOCK:
                return "14:00";

            case self::FIFTEEN_OCLOCK:
                return "15:00";

            case self::SIXTEEN_OCLOCK:
                return "16:00";

            case self::SEVENTEEN_OCLOCK:
                return "17:00";

            case self::EIGHTEEN_OCLOCK:
                return "18:00";

            case self::NINETEEN_OCLOCK:
                return "19:00";

            case self::TWENTY_OCLOCK:
                return "20:00";

            case self::TWENTYONE_OCLOCK:
                return "21:00";

            case self::TWENTYTWO_OCLOCK:
                return "22:00";

            case self::TWENTYTHREE_OCLOCK:
                return "23:00";

            case self::TWENTYFOUR_OCLOCK:
                return "24:00";
        }
    }
}

今回は1時間毎に定数を定義しました。定義した定数の使い方に関しては使用していく中で解説していきます。Enumファイルの作成と準備は以上になります。

3. ControllerでEnumの呼び出し

作成したEnumファイルをControllerで使用していきます。TodosControllerを開きましょう。

app > Http > Controllers > TodosController.php

TodosController.php
<?php

namespace App\Http\Controllers;
// 追加
use App\Enums\SelectTime;
use Illuminate\Http\Request;

class TodosController extends Controller
{
    public function TodoLists()
    // 省略

    public function TodoCreate()
    {
        // Enumの呼び出し
        $selectTime = SelectTime::asSelectArray();

        return view("create", compact("selectTime"));
    }

    public function TodoEdit()
    // 省略
}

Enumで定義した定数はcreate.blade.phpのセレクトタグ内で使用をしますので、create.blade.phpの画面表示処理を行っているTodoCreateメソッドでEnumの呼び出しを行います。Enumの定数を全て取り出すメソッドとしてasSelectArrayメソッドが用意されていますので、TodoCreateメソッドを参考に記述を行なってください。定義した変数はviewメソッドの第二引数にcompact("変数名")を指定することで任意のblade.phpへ変数を渡すことができます。今回の場合「view("create", compact("selectTime"))」この記述によってcreate.blade.phpへ変数selectTimeが渡されました。又、SelectTimeを使用する為にはuse宣言も忘れずに行いましょう。

4. create.blade.phpでEnumの呼び出し

TodoCreateメソッドから送った変数selectTimeをcreate.blade.phpで使用していきます。create.blade.phpを開きましょう。

resorces > views > create.blade.php

create.blade.php
<select class="w-25 text-center date_box">
  <option selected disabled>時間</option>
  @foreach($selectTime as $time)
  <option value={{ $time }}>{{ $time }}</option>
  @endforeach
</select>

foreachディレクティブの第一引数に注目していただけると分かるように変数$selectTimeを呼び出すことができています。compactで変数をbladeに渡していない場合はblade.phpでも使用できないので記入漏れのないように注意しましょう。又、selectタグ内のoptionタグが時間の表示を行なってくれるのでoptionタグを定数の数だけforeachディレクティブを使用しループ処理を行っています。

時間のセレクトボックス

これで表示の部分に関する処理は完了となります。Enumファイルを使用せずに時間を表示させることも可能ですが、定数を管理しておくEnumファイルを使用したことでcreate.blade.phpとTodosControllerの記述量も少なく見やすい状態で実装を行うことができました。このように変化することのない値の管理はEnumファイルで定義し必要に応じて呼び出すことで、チームにとっても見やすいコードで、新たな値の追加や修正なども容易に行うことが可能となります。

create.blade.phpの修正

表示に関する修正は完了しました。続いては実際に登録処理が行えるようにファイルを修正していきます。

resorces > views > create.blade.php

create.blade.php
<form action={{ route("todo.store") }} method="post">
<div class="w-100 m-auto p-3 text-left">
    <!-- 優先度のセレクトボックス -->
    <select class="w-25 text-center priority_box" name="priority">
      <option selected disabled>優先度</option>
      <option value="1">低</option>
      <option value="2">中</option>
      <option value="3">高</option>
    </select>

    <!-- 時間のセレクトボックス -->
    <select class="w-25 text-center date_box" name="time">
      <option selected disabled>時間</option>
      @foreach($selectTime as $time)
      <option value={{ $time }}>{{ $time }}</option>
      @endforeach
    </select>

</div>
<div class="w-100 m-auto">
    <div class="w-100 p-3">

        <!-- TODO入力欄 -->
        <input
          type="text"
          class="w-75 task_input"
          placeholder="タスクを入力"
          name="todo"
        >
        <button
          type="submit"
          class="btn btn-primary"
        >
          追加
        </button>
        </div>

    </div>
    @csrf
</form>

以下は変更箇所になります。

formタグ
<form action={{ route("todo.store") }} method="post">

formタグのaction属性とmethod属性をそれぞれ追記しました。ルーティングについては後ほど設定していきます。

selectタグ(優先度)
<select class="w-25 text-center priority_box" name="priority">
    <option value="1">低</option>
    <option value="2">中</option>
    <option value="3">高</option>

selectタグにname属性の設定とoptionタグのvalue属性の設定を行いました。

selectタグ(時間)
<select class="w-25 text-center date_box" name="time">

セレクトタグにname属性の追加を行いました。

inputタグ
<input
  type="text"
  class="w-75 task_input"
  placeholder="タスクを入力"
  name="todo"
>

inputタグにname属性の追加を行いました。

create.blade.phpの修正は以上で終了になります。

TODOを作成するクエリ作成

続いてはTODOを作成するクエリをTodosControllerへ準備していきます。

app > Http > Controllers > TodosController.php

TodosController.php
<?php

namespace App\Http\Controllers;

// 追加
use App\Models\Todo;
use App\Enums\SelectTime;
use Illuminate\Http\Request;
// 追加
use Illuminate\Support\Facades\Auth;

class TodosController extends Controller
{

    // 省略

    public function TodoStore(Request $request)
    {
        Todo::create(
            [
                "user_id" => Auth::id(),
                "todo_time" => $request->time,
                "todo_priority" => $request->priority,
                "todo" => $request->todo,
                "completed_flag" => false
            ]
        );
        return redirect()->route("todo.lists");
    }
}

以下が解説になります。

TodoStoreメソッド
TodoStore(Request $request)

TodoStoreメソッドを作成しました。usersの作成を同じようにフォームから送られてきたデータを変数$requestで受け取っています。

Todo::create
Todo::create

今回はtodosテーブルへデータの作成を行うのでTodoモデルに対してcreateメソッドを実行しています。

"user_id" => Auth::id(),
"user_id" => Auth::id(),

Auth::id()とすることでログインユーザーのidを取得することができます。この1行ではtodosテーブルのuser_idカラムにログインユーザーのidを追加する処理となっています。

$request->name属性
$request->name属性

こちらもusersの登録処理で行いました。$request->name属性とすることで単一のデータを取得することができます。

"completed_flag" => false
"completed_flag" => false

Todoが完了したかどうかの判定を行うカラムになります。追加した段階で完了していることはないので、falseを指定しています。こちらは今後「完了」ボタンを押すことでtrueに切り替えたりしていきます。

return redirect()->route("todo.lists")
return redirect()->route("todo.lists")

処理が完了したらルーティングのtodo.lists、つまりトップページへリダイレクトする処理を実行しています。

use宣言
use App\Models\Todo;
use Illuminate\Support\Facades\Auth;

2つのクラスを使用しているのでuse宣言も忘れずにしておきましょう。

以上でTODO作成用の処理であるTodoStoreメソッドの作成は完了です。

TODO作成用のルーティング設定

「追加ボタン」を押した際にTodoStoreメソッドが実行されるようにルーティングの設定を行います。編集画面表示のルーティングの下に1行追加してください。

routes > web.php

web.php
Route::get("/edit", [App\Http\Controllers\TodosController::class, "TodoEdit"])->name("todo.edit");

// TODO作成処理
Route::post("/store", [App\Http\Controllers\TodosController::class, "TodoStore"])->name("todo.store");

以上でルーティングの設定は完了になります。

バリデーションの設定

タスク追加フォームでは全ての項目を必須項目としている為、バリデーションの設定も行いましょう。新にFormRequestを作成します。

ターミナル、コマンドプロンプト
php artisan make:request TodoStoreRequest

続いて作成されたTodoStoreRequestにバリデーションを設定していきます。

app > Http > Requests > TodoStoreRequest.php

TodoStoreRequest.php
<?php

namespace App\Http\Requests;

use App\Enums\SelectTime;
use Illuminate\Foundation\Http\FormRequest;

class TodoStoreRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, mixed>
     */
    public function rules()
    {
        return [
            "time" => "required | string | enum_value:" . SelectTime::class . ",false",
            "priority" => "required | integer | between:1,3",
            "todo" => "required | max:50",
        ];
    }

    public function messages()
    {
        return [
            "string" => "文字列で入力してください。",
            "integer" => "整数で入力してください。",
            "required" => "必須項目です。",
            "enum_value" => "存在しない値です。",
            "between" => "存在しない値です。",
            "max" => "50文字以下で入力してください。"
        ];
    }
}

以下が解説になります。

enum_value:" . SelectTime::class . ",false"
enum_value:" . SelectTime::class . ",false"

enum_valueをバリデーションに設定しておくことでSelectTimeに定義した定数の値のみ受付を行います。

between:1,3"
between:1,3"

betweenを指定することで1から3の間の整数のみ受付を行います。優先度の値は1~3のデータしか存在しないため、1から3で設定しています。

以上でTodo作成用のFormRequestの作成と設定は完了になります。

1. TodoStoreメソッドでTodoStoreRequestの呼び出し

usersでもFormRequestを実装しました。Controllerでの呼び出し方は同じようにメソッドの第一引数に設定してあげます。

app > Http > Controllers > TodosController.php

TodosController.php
<?php

namespace App\Http\Controllers;

use App\Models\Todo;
use App\Enums\SelectTime;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Http\Requests\TodoStoreRequest;

class TodosController extends Controller
{

    // 省略

    public function TodoStore(TodoStoreRequest $request)
    {
        Todo::create(
            [
                "user_id" => Auth::id(),
                "todo_time" => $request->time,
                "todo_priority" => $request->priority,
                "todo" => $request->todo,
                "completed_flag" => false
            ]
        );
        return redirect()->route("todo.lists");
    }
}

TodoStoreRequestのuse宣言も忘れずに記載しておきます。以上でcontrollerでFormRequestの呼び出しは完了になります。

2. create.blade.phpにバリデーションメッセージの表示

バリデーションが発生した場合にバリデーションメッセージを表示できるようにblade.phpの任意の場所に記述をしておきます。

resorces > views > create.blade.php

create.blade.php
<form action={{ route("todo.store") }} method="post">
<div class="w-100 m-auto p-3 text-left">
    <!-- 優先度のセレクトボックス -->
    <select class="w-25 text-center priority_box" name="priority">
      <option selected disabled>優先度</option>
      <option value="1">低</option>
      <option value="2">中</option>
      <option value="3">高</option>
    </select>

    <!-- 時間のセレクトボックス -->
    <select class="w-25 text-center date_box" name="time">
      <option selected disabled>時間</option>
      @foreach($selectTime as $time)
      <option value={{ $time }}>{{ $time }}</option>
      @endforeach
    </select>

</div>

<!-- 2つのセレクトボックスのバリデーションメッセージ -->
@if($errors->first("priority"))
    <span class="text-danger">{{ $errors->first("priority") }}</span>
@endif
@if($errors->first("time"))
    <span class="text-danger">{{ $errors->first("time") }}</span>
@endif

<div class="w-100 m-auto">
    <div class="w-100 p-3">

        <!-- TODO入力欄 -->
        <input
          type="text"
          class="w-75 task_input"
          placeholder="タスクを入力"
          name="todo"
        >
        <button
          type="submit"
          class="btn btn-primary"
        >
          追加
        </button>
        </div>

        <!-- todo入力欄のバリデーションメッセージ -->
        @if($errors->first("todo"))
            <span class="text-danger">{{ $errors->first("todo") }}</span>
        @endif

    </div>
    @csrf
</form>

TODO作成の動作確認

これまでの実装でTODOの作成ができるようになっているはずです。TODO作成画面(http://127.0.0.1:8000/create)を開きましょう。

TODO作成画面

それぞれの項目を入力し追加ボタンを押下します。

TODO作成画面入力

登録処理がエラーなく実行されるとTODO作成画面へリダイレクトされます。

Todoトップページ

実際に登録処理が問題なく実行されているかphpMyAdminで確認しましょう。

phpMyAdmin画面

問題なく登録処理が実行されていることの確認ができました。

バリデーションの動作確認

設定したバリデーションも問題なく動作しているか確認しておきましょう。

TODO作成時のバリデーションチェック

セレクトボックスを未選択の状態、入力欄に50文字以上のテキストを入力しバリデーションメッセージの表示も確認できました。

実装時に注意すべきポイント

最後に作成処理の実装時に注意しておくべきポイントですが、登録処理では主にバリデーションの設定に注意して実装を行っていくと良いでしょう。TodoStoreRequestを確認してください。

app > Http > Requests > TodoStoreRequest.php

TodoStoreRequest.php
<?php

namespace App\Http\Requests;

use App\Enums\SelectTime;
use Illuminate\Foundation\Http\FormRequest;

class TodoStoreRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, mixed>
     */
    public function rules()
    {
        return [
            "time" => "required | string | enum_value:" . SelectTime::class . ",false",
            "priority" => "required | integer | between:1,3",
            "todo" => "required | max:50",
        ];
    }

    public function messages()
    {
        return [
            "string" => "文字列で入力してください。",
            "integer" => "整数で入力してください。",
            "required" => "必須項目です。",
            "enum_value" => "存在しない値です。",
            "between" => "存在しない値です。",
            "max" => "50文字以下で入力してください。"
        ];
    }
}

注目すべき点としては以下2つのバリデーションになります。

enum_value

enum_valueはEnumファイルに作成した値のみ受け付ける処理になります。実際にどのような場面で動作するのかを確認してみましょう。検証ツールを表示してください。

検証ツールでの操作

検証ツールでは、画像のように手動で値を変更することができてしまいます。もしバリデーションを設けていない場合、検証ツールから入力した値がDBに登録されてしまい、予期しないエラーの原因にもなってしまいます。そのため、予めEnumで定義されている値のみ受け付けるバリデーションを設けました。この状態で追加ボタンを押下した場合、以下の表示になります。

検証ツールでの操作

between

2つ目はこちらのバリデーションルールになります。優先度のセレクトボックスも上記と同じような操作が可能です。しかし今回、優先度はEnumで実装していないので、上記で使用したenum_valueが使用できません。そのような状況の時にbetweenを設けて許可する値の範囲を指定してあげます。同じように検証ツールから操作し、追加ボタンを押下した結果は以下の表示になります。

検証ツールでの操作 検証ツールでの操作

以上でTODO作成画面のクエリ作成と関する機能の実装は完成になります。

Lesson 11 Chapter 2
TODO一覧取得用クエリの実装

chapter2ではDBに登録されているデータの表示を行なっていきましょう。

TODO一覧取得準備

TODO一覧画面(URL:http://127.0.0.1:8000/top)を開いてください。現状では何も表示されていません。DBのデータを表示させるよう各ファイルの修正を行なっていきます。

TODO一覧トップページ

TODOを取得するクエリ作成

それでは早速TODO一覧を取得する処理を実装していきます。TodosControllerを開きましょう。

app > Http > Controllers > TodosController.php

TodosController.php
<?php

namespace App\Http\Controllers;

use App\Models\Todo;
use App\Enums\SelectTime;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Http\Requests\TodoStoreRequest;

class TodosController extends Controller
{
  public function TodoLists()
  {
    return view("top");
  }

    // 省略
}

表示の処理といえばTODO作成用画面で実装したSelectTimeと流れはほとんど同じになります。その為、今回TODO一覧を表示させたいページはTODO一覧画面(トップページ)になるのでTODO一覧画面の表示を行なっているメソッドTodoListsへ処理を追加していきます。以下が修正後のファイルになります。

TodosController.php
<?php

namespace App\Http\Controllers;

use App\Models\Todo;
use App\Enums\SelectTime;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Http\Requests\TodoStoreRequest;

class TodosController extends Controller
{
  public function TodoLists()
  {
    $todos = Todo::get()->sortBy("todo_time");

    return view("top", compact("todos"));
  }

    // 省略
}

追加した部分は「$todos = Todo::get()->sortBy("todo_time");」とviewメソッド内の「compact("todos")」2箇所になります。全て取得したい場合はTodoモデルに対してgetメソッドを使用してあげることで全てのtodosテーブルのデータを取得できます。仮にusersのデータを全取得したい場合はUser::get()とすることで取得することが可能です。又、コレクションの並び替えを行う場合は「sortBy("カラム")」を指定することで時間が早い(asc)順に並び替えを行うことができます。逆に遅い順に変更したい場合はsortByDescというメソッドも存在します。今回は時間の早い順にTODO一覧の表示を行う為、sortByを指定しました。Controllerの修正はこれで以上になります。

実行されているSQLの確認

LaravelにおいてはEloquentの機能を利用することでDB操作を行うことが可能ですが、場合によっては実行されているSQL文を確認したい時もあるかと思います。その場合はLesson6のORM項目で学習したDBファサードにtoSqlメソッドを使用することで確認することが可能です。

TodosController.php
use Illuminate\Support\Facades\DB;

// 省略

public function TodoLists()
{
    // toSqlメソッドでSQLの確認
    dd(DB::table("todos")->toSql());

    return view("top", compact("todos"));
}
実行結果
"select * from `todos`"

必要に応じてtoSqlメソッドも利用しながら実行されているSQLの確認も行ってみましょう。

top.blade.phpの修正

TodoListsメソッドからtop.blade.phpへ変数todosを渡しているので、todosを使い一覧を表示していきます。以下は修正後のtop.blade.phpになります。

top.blade.php
<table class="table">
<thead>
  <tr>
    <th>ID</th>
    <td class="text-center">タスク</td>
    <td class="text-center">優先度</td>
    <td class="text-center">時間</td>
    <td class="text-center">登録日時</td>
    <td></td>
    <td></td>
  </tr>
</thead>

<tbody>
@foreach($todos as $todo)
  <tr>
    <!-- idの表示 -->
    <th>{{ $todo->id }}</th>
    <!-- todoの表示 -->
    <td class="text-center">{{ $todo->todo }}</td>

    <!-- 優先度の表示 -->
    @if($todo->todo_priority == "3")
    <td class="text-center">高</td>
    @elseif($todo->todo_priority == "2")
    <td class="text-center">中</td>
    @else
    <td class="text-center">低</td>
    @endif

    <!-- 時間の表示 -->
    <td class="text-center">{{ date('G:i', strtotime($todo->todo_time)) }}</td>
    <!-- 登録時間の表示 -->
    <td class="text-center">{{ $todo->created_at->format("Y-m-d") }}</td>
    <!-- 各ボタンの表示 -->
    <td class="text-center">
      <span>
        <button
          type="button"
          onclick=""
          class="btn btn-primary m-auto"
        >
          編集
        </button>
      </span>
      <span>
        <button
          type="button"
          onclick=""
          class="btn btn-danger m-auto"
        >
          削除
        </button>
      </span>
      <span>
      <button
        type="button"
        onclick=""
        class="btn btn-success m-auto"
      >
        完了
      </button>
    </span>
    </td>
  </tr>
@endforeach
</tbody>

</table>

TodoListsメソッドの処理から渡ってきた変数$todosをforeachディレクティブの第一引数にセットし、$todoとして一つずつ表示を行なっています。「$todo->カラム名」として表示を行なっているので、もしカラム名が忘れてしまっている場合はphpMyAdminを見ながら今後は処理を書いていくとスムーズに進められます。

(※phpMyAdmin画面) todosテーブルのカラム

@if($todo->todo_priority)

top.blade.php
@if($todo->todo_priority == "3")
  <td class="text-center">高</td>
@elseif($todo->todo_priority == "2")
  <td class="text-center">中</td>
@else
  <td class="text-center">低</td>
@endif

ifディレクティブを使用し、優先順位を高(todo_priority = 3)/中(todo_priority = 2)/低(3,2以外の時)とステータスの表示を切り替えています。

date('G:i', strtotime($todo->todo_time))

top.blade.php
date('G:i', strtotime($todo->todo_time))

todoの時間はH(時):i(分):s(秒)の形式でDBに登録されています(例 10:00:00)。今回のTODO一覧では秒の表記が不要な為、:sを外しています。又、Hの場合だと1~9時のような1桁の時に先頭へ0が表示されてしまう為G(先頭の0を外す)を指定したフォーマットに変更を行なっています。フォーマットの変更を行う場合は phpで予め用意されているstrtotime関数を使用して、時間を秒数に戻します。strtotime関数は協定世界時と言って1970/1月/1日からの経過秒数を出すことができ、その秒数を元にG(先頭の0を省略した時)、i(分)の構成にフォーマットの変更を行いました。これは手段の一つな為、このようなやり方も存在することを認識しておきましょう。

$todo->created_at->format("Y-m-d")

top.blade.php
$todo->created_at->format("Y-m-d")

日時を扱うデータ型にはformat関数を使用し出力をカスタマイズすることができます。今回取得したい部分は年月日だった為、「Y-m-d」と指定しましたが、その他にも「Y/m/d」や「Y年n月d日」など様々な表示を行うことも可能です。

TODO一覧取得の動作確認

実装の準備はこれにて完了しました。TODO一覧画面(http://127.0.0.1:8000/top)を表示し、作成したTODOが表示されているか確認してみましょう。

TODO一覧画面の実装

複数タスクを追加すると以下のような表示になるか確認もしておきます。

TODO一覧画面の実装

通常はID順(作成された順)で表示が行われますが、処理でsortByをして時間順に並び替えています。時間順に表示が行われているかも合わせて確認をしておきましょう。

実装時に注意すべきポイント

一覧表示の場合は表示するデータに注意しましょう。例えばパスワードやユーザーの個人情報など誤ってブラウザ上に出力してしまった場合は情報漏洩となってしまいます。他の処理に比べ、表示が容易に行える分扱うデータにおいては開発者側で配慮する必要があります。使用しないデータに関してはモデルの作成時に解説した$hiddenにデータを格納しておくことで、未然に防ぐことも可能です。

以上でTODO一覧画面の実装は完了になります。

Lesson 11 Chapter 3
TODO更新用クエリの実装

chapter3では作成したクエリの編集とタスクの完了・未完了を実装していきます。

TODO編集準備

1. top.blade.phpの編集

TODO編集画面に遷移する必要があるので、まず初めに「編集」ボタンへ編集画面に移動する為のリンクを設定していきます。top.blade.phpを開きましょう。

resources > views > top.blade.php

(編集前)
top.blade.php
<td class="text-center">
<span><button type="button" onclick="" class="btn btn-primary m-auto">編集</button></span>
...

Lesson8の「タスク追加」ボタンでも実装したようにbuttonタグへリンクの設置を行なっていきます。編集画面表示用のルーティングにはnameメソッドでtodo.editと命名しているのでtodo.editをリンクに指定してあげます。

(編集後)
top.blade.php
<td class="text-center">
<span><button type="button" onclick="location.href='{!! route('todo.edit') !!}'" class=...
...

リンクの設定が完了したら実際に編集ボタンを押下してみてください。編集画面に遷移できるようになっているはずです。

TODO編集画面

2. リンクにパラメータを付与する

続いては編集ボタンにパラメータを付与していきます。再度TODO一覧ページに戻りましょう。

TODO一覧画面

パラメータとは

ここでいうパラメータとはTODO一覧のそれぞれに振られているIDのことを指します。IDを使用することでデータの特定が可能となり編集を行うことができます。仮にパラメータを付与しない場合は全てのデータに編集内容が反映されるかエラーが発生するかのいずれかになります。このような不具合を防ぐためにもパラメータの付与は必須となります。

編集ボタンにパラメータを付与する

それでは編集ボタンにパラメータとしてIDを付与していきます。onclick属性を以下のように書き換えましょう。

top.blade.php
onclick="location.href='{!! route('todo.edit', ['id' => $todo->id]) !!}'"

これで一つ一つのボタンにTOODのIDが付与されました。

3. ルーティングの修正

最終的にはボタンに付与したパラメータをCotnrollerで受け取り、データの特定を行なっていきます。Controllerでパラメータを受け取るためにルーティングにもパラメータの記述を追加する必要があります。以下のように修正しましょう。

(修正前)
web.php
// TODO編集画面の表示
    Route::get("/edit", [App\Http\Controllers\TodosController::class, "TodoEdit"])->name("todo.edit");
(修正後)
web.php
// TODO編集画面の表示
    Route::get("/edit/{id}", [App\Http\Controllers\TodosController::class, "TodoEdit"])->name("todo.edit");

ルーティングには{id}とすることでボタンから送られたパラメータを受け取り、Controllerへ渡すことができます。

注意

ボタンでパラメータを渡す際に設定したキーはルーティングと一致させる必要があります。

top.blade.php
onclick="location.href='{!! route('todo.edit', ['id' => $todo->id]) !!}'"
web.php
// TODO編集画面の表示
    Route::get("/edit/{id}", [App\Http\Controllers\TodosController::class, "TodoEdit"])->name("todo.edit");

今回の場合だとidの部分が該当します。例えばidではなくuserとキーを指定していたとすると、ルーティングも{user}としてあげる必要があるので、覚えておきましょう。

4. TodoEditメソッドの修正

最後に、送られてきたパラメータを編集画面用のメソッド、TodoEditで受け取ります。

TodosController
public function TodoEdit($id)
    {
        return view("edit");
    }

受け取り方はシンプルで引数に$idを入れてあげることで、パラメータが$idへ格納されます。

5. パラメータの確認

それではパラメータを受け取ることができているか確認していきます。TODO一覧画面から適当な編集ボタンを押下してみましょう。

TODO一覧画面

こちらの画面ではTODOのIDが3に該当する部分のボタンを押下します。押下後URLを確認するとパラメータとしてIDが確認できます。(URL:http://127.0.0.1:8000/edit/3)

6. TodoEditメソッドでパラメータの確認

URLでの確認は出来ました。続いて該当のメソッドでパラメータを受けとれているかの確認も行なっておきます。確認の仕方はLesson4のchapter8で紹介したヘルパ関数ddを使用していきます。

TodosController
public function TodoEdit($id)
{
  dd($id);

  return view("edit");
}

記述が完了したら再度一覧画面より、適当な編集ボタンをクリックしてみましょう。以下のようにクリックしたボタンに付与されているIDが画面上に表示されていればControllerでの受け取りも問題なく行えています。

dd関数

7. IDに一致するTODOの取得

受け取ったIDをもとに一致するデータの取得を行い、それぞれのデータを編集画面に表示していきます。例えばタスクが「会議」の編集ボタンを押した場合は編集画面の入力欄には予め「会議」がセットされている状態を作っていきます。TodoEditメソッドを修正しましょう。

TodosController
public function TodoEdit($id)
{
  $todo = Todo::find($id);
  $selectTime = SelectTime::asSelectArray();

  return view("edit", compact("todo", "selectTime"));
}

IDに一致するTODOの取得はfindメソッドを使用し、findメソッドの引数に$idを渡してあげることで特定のデータの取得が行えます。又、TODO作成画面と同じようにTODO編集画面でも時間の選択が行えるようSelectTimeも定義を行い、同時にedit.bladeへ渡してあげます。

compactメソッド
compact("変数1", "変数2", ...)

複数の変数をbladeファイルへ渡したい場合、compactメソッドの引数へカンマ区切りで渡してあげましょう。

8. edit.blade.phpでtodoの表示

edit.blade.php
<form action="" method="post">
<div class="w-100 m-auto p-3 text-left">
    <select class="w-25 text-center priority_box">

        <option value="1" @if($todo->todo_priority == 1) selected @endif>低</option>
        <option value="2" @if($todo->todo_priority == 2) selected @endif>中</option>
        <option value="3" @if($todo->todo_priority == 3) selected @endif>高</option>

    </select>
    <select class="w-25 text-center date_box">

      @foreach($selectTime as $time)
        <option value={{ $time }} @if(date("G:i", strtotime($todo->todo_time)) == $time) selected @endif>
          {{ $time }}
        </option>
      @endforeach

    </select>
</div>
<div class="w-100 m-auto">
    <div class="w-100 p-3">

        <input
          type="text"
          class="w-75 task_input"
          placeholder="タスクを入力"
          value={{ $todo->todo }}
        >

        <button type="submit" class="btn btn-primary">編集
    </div>
</div>

それでは変更箇所を解説していきます。

優先度セレクトタグ

edit.blade.php
<select class="w-25 text-center priority_box">
    <option value="1" @if($todo->todo_priority == 1) selected @endif>低</option>
    <option value="2" @if($todo->todo_priority == 2) selected @endif>中</option>
    <option value="3" @if($todo->todo_priority == 3) selected @endif>高</option>
</select>

selected属性が付与されたoptionタグは、デフォルトで設定されます。ifディレクティブを使用し「todo_priorityが◯◯だったら」の条件を設けることで作成時に選択した優先順位がデフォルトで設定されるようになります。

時間セレクトタグ

edit.blade.php
<select class="w-25 text-center date_box">
@foreach($selectTime as $time)
    <option value={{ $time }} @if(date("G:i", strtotime($todo->todo_time)) == $time) selected @endif>
      {{ $time }}
    </option>
@endforeach
</select>

時間も同じように作成時に設定した時間をデフォルトで設定します。ただし、時間の場合DBに登録されているデータがH(時):i(分):s(秒)となっている為、TODO一覧画面でも行なったようにG:i形式に変換してあげる必要があります。

入力欄

edit.blade.php
<input
  type="text"
  class="w-75 task_input"
  placeholder="タスクを入力"
  value={{ $todo->todo }}
>

デフォルトで値をセットするデータをvalue属性にセットしています。

9. 動作確認

以上で準備が完了しました、適当なタスクの編集画面に移動してみるとそのデータが各入力欄に予めセットされている状況を作り出すことができているはずです。

TODO一覧画面 TODO編集画面

TODOを更新するクエリの作成

クエリ作成までの準備が整いました。ここからは更新用のクエリを作成していきます。初めにedit.blade.phpを開きましょう。

1. edit.blade.phpの修正

resources > views > edit.blade.php

edit.blade.php
<!-- action属性追加 -->
<form action={{ route("todo.update") }} method="post">
<div class="w-100 m-auto p-3 text-left">
    <!-- name属性追加 -->
    <select class="w-25 text-center priority_box" name="todo_priority">
        <option value="1" @if($todo->todo_priority == 1) selected @endif>低</option>
        <option value="2" @if($todo->todo_priority == 2) selected @endif>中</option>
        <option value="3" @if($todo->todo_priority == 3) selected @endif>高</option>
    </select>
    <!-- name属性追加 -->
    <select class="w-25 text-center date_box">
    @foreach($selectTime as $time)
        <option value={{ $time }} @if(date("G:i", strtotime($todo->todo_time)) == $time) selected @endif>
          {{ $time }}
        </option>
    @endforeach
    </select>
</div>
<div class="w-100 m-auto">
    <div class="w-100 p-3">
        <!-- name属性追加 -->
        <input
          type="text"
          class="w-75
          name="todo" task_input"
          placeholder="タスクを入力"
          value={{ $todo->todo }}
        >
        <!-- idを送る隠しデータの追加 -->
        <input
          type="hidden"
          name="id"
          value={{ $todo->id }}
        >
        <button type="submit" class="btn btn-primary">編集
    </div>
</div>
@csrf
<!-- メソッドディレクティブにput指定 -->
@method("put")
</form>

これまでと同様、formのaction属性や各項目にname属性を付与しています。そして今回は特定のデータの更新を行う為、IDも必要となります。利用するユーザにとってIDの重要性は高くないので隠しデータ(hidden)としてデータを送ってあげましょう。又、更新の場合はルーティングでputメソッドを使用して行う為、formタグの中にメソッドディレクティブを用意しputを指定する必要がありますので、覚えておきましょう。

2. TodoUpdateメソッドの作成

TODO更新用のクエリを作成していきます。

app > Http > Controllers > TodosController.php

TodosController.php
public function TodoUpdate(Request $request)
{
  $todo = Todo::find($request->id);

  $todo->todo_priority = $request->todo_priority;
  $todo->todo_time = $request->todo_time;
  $todo->todo = $request->todo;
  $todo->save();

  return redirect()->route("todo.lists");
}

以下が解説になります。

$todo = Todo::find($request->id)

TodosController.php
$todo = Todo::find($request->id)

findはTodoEditメソッドでも使用しました。引数にIDを渡すことで単一のデータを取得することができます。ここでは「更新したいデータ」を取得しています。

$todo->

TodosController.php
$todo->更新したいカラム = 更新後のデータ

2行目以降の部分では更新したいカラムに更新後のカラムをセットしているイメージになります。更新する一つ手前の準備を行なっています。

$todo->save();

TodosController.php
$todo->save();

Laravelではsaveメソッドと言ってデータを保存するメソッドが用意されています。モデルを扱ったデータ操作に対してsaveメソッドを使用することが可能です。

最後に全ての処理を終えた後、TODO一覧ページへリダイレクトする処理を行なっているので、更新後はTODO一覧画面へ遷移します。

3. ルーティングの設定

編集ボタンを押下したらTodoUpdateメソッドが実行されるようルーティングの設定を行います。TODO編集画面表示用のルーティングの下に追加してください。

routes > web.php

web.php
// TODO編集画面の表示
Route::put("/update", [App\Http\Controllers\TodosController::class, "TodoUpdate"])->name("todo.update");

データの更新処理を行う場合はputメソッドを使用します。それ以外はこれまでのルーティングと違いはありません。

4. バリデーションの実装

最後にバリデーションの実装を行います。TodoUpdateRequestを作成しましょう。尚、内容のほとんどがTodoStoreRequestと重複する為、解説は省略します。

ターミナル、コマンドプロンプト
php artisan make:request TodoUpdateRequest
TodoUpdateRequest.php
<?php

namespace App\Http\Requests;

use App\Enums\SelectTime;
use Illuminate\Foundation\Http\FormRequest;

class TodoUpdateRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, mixed>
     */
    public function rules()
    {
        return [
            "todo_time" => "required|string|enum_value:" . SelectTime::class . ",false",
            "todo_priority" => "required|integer|between:1,3",
            "todo" => "required|max:50",
        ];
    }

    public function messages()
    {
        return [
            "string" => "文字列で入力してください。",
            "integer" => "整数で入力してください。",
            "required" => "必須項目です。",
            "enum_value" => "存在しない値です。",
            "between" => "存在しない値です。",
            "max" => "50文字以下で入力してください。"
        ];
    }
}

TodoUpdateメソッドの修正

TodosController.php
<?php

// use宣言
use App\Http\Requests\TodoUpdateRequest;

class TodosController extends Controller

//省略

// TodoUpdateRequestクラスの設定
public function TodoUpdate(TodoUpdateRequest $request)
{
  $todo = Todo::find($request->id);

  $todo->todo_priority = $request->todo_priority;
  $todo->todo_time = $request->todo_time;
  $todo->todo = $request->todo;
  $todo->save();

  return redirect()->route("todo.lists");
}

edit.blade.phpの修正

edit.blade.php
<form action={{ route("todo.update") }} method="post">
<div class="w-100 m-auto p-3 text-left">
    <select class="w-25 text-center priority_box" name="todo_priority">
        <option value="1" @if($todo->todo_priority == 1) selected @endif>低</option>
        <option value="2" @if($todo->todo_priority == 2) selected @endif>中</option>
        <option value="3" @if($todo->todo_priority == 3) selected @endif>高</option>
    </select>
    <select class="w-25 text-center date_box" name="todo_time">
    @foreach($selectTime as $time)
        <option value={{ $time }} @if(date("G:i", strtotime($todo->todo_time)) == $time) selected @endif>
          {{ $time }}
        </option>
    @endforeach
    </select>
</div>

<!-- バリデーションメッセージ -->
@if($errors->first("todo_priority"))
    <span class="text-danger">{{ $errors->first("priority") }}</span>
@endif
@if($errors->first("todo_time"))
    <span class="text-danger">{{ $errors->first("time") }}</span>
@endif

<div class="w-100 m-auto">
    <div class="w-100 p-3">
        <input
          type="text"
          class="w-75 task_input"
          name="todo"
          placeholder="タスクを入力" value={{ $todo->todo }}
        >
        <input
          type="hidden"
          name="id"
          value={{ $todo->id }}
        >
        <button
          type="submit"
          class="btn btn-primary"
        >
          編集
        </button>
    </div>

    <!-- バリデーションメッセージ -->
    @if($errors->first("todo"))
        <span class="text-danger">{{ $errors->first("todo") }}</span>
    @endif

</div>
@csrf
@method("put")
</form>

5. TODO更新処理の動作確認

全ての準備が完了しました。それでは動作確認を行なっていきます。まずはTODO一覧画面に移動しましょう。

TODO一覧画面

任意の編集ボタンを押し、TODO編集画面に遷移後、各データがセットされているか確認しておきます。

TODO編集画面

変更したいデータを各項目へ入力し、編集ボタンを押してください。

TODO編集画面

編集ボタン押下後にTODO一覧画面へ遷移します。データが更新されているか確認しましょう。

TODO編集画面

以上でTODOを更新するクエリの作成は完了になります。

TODOを完了するクエリの作成

続いてはTODOを完了するクエリの作成を行なっていきます。完了ボタンを押下すると「完了したTODOリスト」にタスクが移動し、削除ボタンのみ有効になります。

完成イメージ TODO編集、完了タスク

流れはこれまで行なってきたTODO更新処理と同じでよりシンプルに実装が可能です。

1. top.blade.phpの編集

1箇所目

TODO編集画面のボタンにも付与したパラメータを完了ボタンのonclick属性にも設定します。今回使用するルーティングtodo.completedは後ほど作成します。

top.blade.php
<span>
  <button
    type="button"
    onclick="location.href='{!! route('todo.completed', ['id' => $todo->id]) !!}'"
    class=...
  >
    完了
  </button>
</span>
2箇所目

未完了のタスクは「Todoリスト」に、完了済みのタスクは下の「完了したTodoリスト」に配置されるようifディレクティブを使用して分けていきます。

Todoリスト部分
top.blade.php
<tbody>
@foreach($todos as $todo)
<!-- ifディレクティブの追加 -->
@if(!$todo->completed_flag)
<tr>
    <th>{{ $todo->id }}</th>
    <td class="text-center">{{ $todo->todo }}</td>

    @if($todo->todo_priority == "3")
        <td class="text-center">高</td>
    @elseif($todo->todo_priority == "2")
        <td class="text-center">中</td>
    @else
        <td class="text-center">低</td>
    @endif

    <!-- 省略 -->

</tr>
@endif

@endforeach
</tbody>
完了したTodoリスト部分
top.blade.php
<tbody>
@foreach($todos as $todo)

<!-- ifディレクティブの追加 -->
@if($todo->completed_flag)
<tr>
    <th>{{ $todo->id }}</th>
    <td class="text-center">{{ $todo->todo }}</td>

    @if($todo->todo_priority == "3")
        <td class="text-center">高</td>
    @elseif($todo->todo_priority == "2")
        <td class="text-center">中</td>
    @else
        <td class="text-center">低</td>
    @endif
    <td class="text-center">{{ date('G:i', strtotime($todo->todo_time)) }}</td>
    <td class="text-center">{{ $todo->created_at->format("Y-m-d") }}</td>
    <td class="text-center">
        <span>
          <button
            type="button"
            onclick=""
            class="btn
            btn-danger
            m-auto"
            >
              削除
          </button>
        </span>
    <td>

</tr>
@endif

@endforeach
</tbody>

completed_flagカラムはtrueかfalseの判定を行うため、条件式には「$todo->completed_flag」とします。「!$todo->completed_flag」のように先頭に(!)をつけることでfalseの場合をtrueと判定し、completed_flagがfalseであればTodoリスト、trueであれば完了したTodoリストへ表示を行なっています。

2. TodoCompletedメソッドの作成

TodoUpdateメソッドの下にTodoCompletedメソッドの作成を行いましょう。

TodosContoller.php
public function TodoCompleted($id)
{
  $todo = Todo::find($id);
  $todo->completed_flag = true;
  $todo->save();

  return back();
}

更新のクエリについては先ほどの同様な為、解説を省略します。TODO作成時completed_flagはfalseで作成しているので、完了ボタンを押した際にはtrueに変更します。又、処理後に前のページに戻す処理はreturnにbackメソッドを指定してあげることで実現させることが可能です。今回のようにページの遷移を行わず、同一ページ内で処理が完結する場合はreturn backとしておきましょう。

3. ルーティングの設定

完了ボタンを押した際にTodoCompletedメソッドを動作させるルーティングを設定します。

web.php
Route::get("/completed/{id}", [App\Http\Controllers\TodosController::class, "TodoCompleted"])->name("todo.completed");

URLにパラメータを載せてidを渡しているため、今回はget送信に分類されます。その場合はルーティングもgetメソッドを使用してあげましょう。

4. 動作確認

準備はこれにて完了です。適当なタスクの完了ボタンを押下してみてください。

TODO編集画面 TODO編集、完了タスク

以上で完了ボタンの実装が完了しました。この処理についてもcompleted_flagをfalseからtrueに変更しているため、更新処理に分類されます。このように同一ページ内で処理を完結させるパターンも覚えておきましょう。

Lesson 11 Chapter 4
TODO削除用クエリの実装

chapter4ではTODOアプリケーション最後のクエリ、削除の実践を行なっていきましょう。

TODO削除の概要

削除機能についてはchapter3の「TODO完了」の機能のようにページは存在しません。削除ボタンを押下したことでページの遷移は行わず、TODOの削除を行います。又、削除ボタンに関しては「完了したTODOリスト」にのみ表示させるボタンとして設置していきますので、ボタンの出しわけも行っていきます。

TODO削除の準備

1. top.blade.php

それではTODO削除の準備を行なっていきます。top.blade.phpに設置されている削除ボタンに修正を加えていきましょう。上記の概要にもある通り、削除ボタンは完了したTODOリストにのみ設置するボタンとしていきますので「TODOリスト」にある削除ボタンは消しておいてください。以下のような画面になっていれば問題ありません。

TODO一覧画面

続いて完了したTODOリストに設置されている削除ボタンのonclick属性にパラメータを付与します。削除においても複数あるタスクの中から特定のデータを削除する為、パラメータとしてIDを付与しておく必要があります。

top.blade.php
<span>
  <button
    type="button"
    onclick="location.href='{!! route('todo.delete', ['id' => $todo->id]) !!}'"
    class="btn btn-danger m-auto"
  >
    削除
  </button>
</span>
(編集前)
top.blade.php
onclick="" 
(編集後)
top.blade.php
onclick="location.href='{!! route('todo.delete', ['id' => $todo->id]) !!}'"

ルーティング(todo.delete)に対してIDを渡しています。ルーティングについてはこの後設定していきます。

以上でtop.blade.phpの修正は完了になります。

2. TodoDeleteメソッドの作成

削除用のクエリをTodoDeleteメソッドに作成していきます。TodosControllerを開き、メソッドを追加しましょう。

TodosController.php
public function TodoDelete($id)
{
    $todo = Todo::find($id);
    $todo->delete();

    return back();
}

ボタンから送られてきた$todo->idをTodoDeleteの引数で$idとして受け取り、findメソッドに$idを渡してデータの特定を行います。TODOの更新でも使用したsaveメソッドのようにLaravelではモデルを扱ったデータ操作を行う場合、deleteメソッドが用意されています。deleteメソッドを使用することでDBからデータを削除することが可能です。

削除用のクエリ作成はこれで以上になります。

3. ルーティングの設定

最後に削除ボタンを押した際にTodoDeleteメソッドを実行するため、ルーティングの設定を行いましょう。

web.php
Route::get("/delete/{id}", [App\Http\Controllers\TodosController::class, "TodoDelete"])->name("todo.delete");

削除も完了と同じようにURLにパラメータを付与して送るget送信に該当する為、ルーティングはgetメソッドで作成します。

以上でルーティングの設定は完了になります。

4. TODO削除の動作確認

実装した削除機能の動作確認を行なっていきます。2タスクほど完了をしておきましょう。

TODO一覧画面

削除ボタンを押下したタスクのみが削除されていれば、削除機能完成になります。

TODO一覧画面