【Laravel】Web開発初心者が1週間で入門チュートリアルを実施した記録(7)


以下の続き。

https://qiita.com/yotty19911207/items/1053c3153e9c3c252b9d

前回までで、フォルダ作成＆タスク作成機能をつくった。


<h1>入門Laravelチュートリアル（7）ToDoアプリのタスクの編集機能を作る</h1>

続いて、タスクの編集機能。
流れとしてはフォルダ作成＆タスク作成機能と同じ。

①ルーティング設定(web.php)

     Route::get('/folders/{id}/tasks/{task_id}/edit', 'TaskController@showEditForm')->name('tasks.edit');
     Route::post('/folders/{id}/tasks/{task_id}/edit', 'TaskController@edit');


※
getは一覧画面の「編集」ボタンを押して、タスク編集画面に遷移してきた時。
postはタスク編集画面にてタイトル・状態・期限を入力して「送信」ボタンを押した時。

まずはget時のshowEditFormメソッドに関して実装していく。

②コントローラーの作成・記載(showEditForm)

    /**
     * GET /folders/{id}/tasks/{task_id}/edit
     */
     public function showEditForm(int $id, int $task_id)
     {
         $task = Task::find($task_id);
     
         return view('tasks/edit', [
             'task' => $task,
         ]);
     }

これまでは新規でフォルダやタスクを作っていたので、特に引数で受け取っていなかったが、編集については、既にあるタスクの情報を編集画面のinput属性の初期値として表示する必要があるため、該当タスクの情報を受け取ってテンプレートに渡している。

⑧テンプレートの作成(showEditForm)

「/resources/views/folders/edit.blade.php」を作成して中身を書いていく。
フォルダ名を入れる入力フォームと「送信」ボタンがあるだけのシンプルな画面なので、基本的なHTML。

ポイントは以下。
・タイトル：

     <input type="text" class="form-control" name="title" id="title"
    value="{{ old('title', $task->title) }}" />

タイトルを入力する箇所のinputタグでvalue値を上記のように指定している。
old 関数はセッションにフラッシュデーターとして保存されている入力値を取得するが、第二引数としてデフォルト値を指定できる。ここでは、最初に編集画面に遷移した際は、tasksテーブルのtitleカラムを初期値として設定し、それ以降は入力値を編集して送信した際にバリデーションエラーになった際などは、編集していた入力値が表示される。

・状態：


     <select name="status" id="status" class="form-control">
       @foreach(\App\Task::STATUS as $key => $val)
         <option
             value="{{ $key }}"
             {{ $key == old('status', $task->status) ? 'selected' : '' }}
         >
           {{ $val['label'] }}
         </option>
       @endforeach
     </select>

STATUSは以下でした。

    const STATUS = [
        1 => [ 'label' => '未着手', 'class' => 'label-danger' ],
        2 => [ 'label' => '着手中', 'class' => 'label-info' ],
        3 => [ 'label' => '完了', 'class' => '' ],
    ];

Task モデルで定義した配列定数 STATUS を @foreach でループして option 要素を出力。option 要素の value に配列のキー（1, 2, 3）を、タグで囲んだ表示文字列には 'label' の値を出力します。

      {{ $key == old('status', $task->status) ? 'selected' : '' }}

配列のKey、がold関数で表現している、"デフォルトでtasksテーブルのstatusカラム、入力値を編集していた場合はその編集値"と一致する場合、そのKeyにselected属性が適用される。
→編集ページを開いたときはタスクを作成したときのタイトルが入力欄に入っていて、値を変更して送信したが入力エラーになって戻ってきたときは変更後の値が入っているという挙動を実現。

・バリデーション
まずはバリデーションのための FormRequest クラスから作成。

      php artisan make:request EditTask

→app/Http/Requests/EditTask.php が作成されるので以下の内容を記載。


    <?php

    namespace App\Http\Requests;

    use App\Models\Task;
    use Illuminate\Validation\Rule;

    class EditTask extends CreateTask
      {
    public function rules()
    {
        $rule = parent::rules();

        $status_rule = Rule::in(array_keys(Task::STATUS));

        return $rule + [
            'status' => 'required|' . $status_rule,
        ];
    }

    public function attributes()
    {
        $attributes = parent::attributes();

        return $attributes + [
            'status' => '状態',
        ];
    }

    public function messages()
    {
        $messages = parent::messages();

        $status_labels = array_map(function($item) {
            return $item['label'];
        }, Task::STATUS);

        $status_labels = implode('、', $status_labels);

        return $messages + [
            'status.in' => ':attribute には ' . $status_labels. ' のいずれかを指定してください。',
        ];
    }
}

ここのバリデーションの記載は初見で？？？となったので詳しく調べた。
まずはrules関数。

    public function rules()
    {
        $rule = parent::rules();

        $status_rule = Rule::in(array_keys(Task::STATUS));

        return $rule + [
            'status' => 'required|' . $status_rule,
        ];
    }

・EditTaskはCreateTaskを継承している。
・CreateTaskにもrulesメソッドを保持している（↓）。

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'title' => 'required|max:100',
            'due_date' => 'required|date|after_or_equal:today',
        ];
    }

これより、EditTaskのrulesメソッドでは、CreateTaskのタイトルと期限日のバリデーションは保持しつつ、状態に関するバリデーションを加えて返却している。

・状態に関する記載について

        $status_rule = Rule::in(array_keys(Task::STATUS));

Rule::inではフィールドが指定したリストの中の値に含まれていることをバリデートしている。具体的には、Taskモデルに定義したSTATUSのキー値である1, 2, 3のいずれかの値になっているかを確認するため、status_ruleは"in(1,2,3)"となる。

    return $rule + [
        'status' => 'required|' . $status_rule,
    ];

最後に、EditTaskのrule関数として、CreateTaskのタイトルと期限日のバリデーション　＋　状態に関するバリデーションとして、'status' => 'required|in(1, 2, 3)',を返却する。
※PHPで.(ドット)は結合演算子。

https://blog-and-destroy.com/6084


attributeとmessagesについてもruleと基本的には同様で、CreateTaskの内容に状態の分を追加する書き方をしているがmessagesはコードだけ見ると少しややこしい。
やりたいことは、「状態 には 未着手、着手中、完了 のいずれかを指定してください。」というメッセージを作りたい。

array_map(コールバック関数,配列)

今回の場合のコールバック関数は以下になる。

    //配列の'label'要素を返す関数
    function($item) {
                 return $item['label'];
    }

つまり、$status_labelsにはTASK::STATUSの'label'要素が格納される。

implode(’セパレータ',配列)なので、'label'要素が'、'で連結される　→ (未着手、着手中、完了)

ここまででshowEditTaskメソッドについては完了。

引き続き、editメソッドについて。

②コントローラーの作成・記載(edit)

    public function edit(int $id, int $task_id, EditTask $request)
    {
        // 1
        $task = Task::find($task_id);

        // 2
        $task->title = $request->title;
        $task->status = $request->status;
        $task->due_date = $request->due_date;
        $task->save();

        // 3
        return redirect()->route('tasks.index', [
            'id' => $task->folder_id,
        ]);
    }

タスクIDからタスクの情報を取得し、対応するカラムに画面のインプット情報を詰めて保存。ルーティング設定で宣言した名前('tasks.index')のURLに{id}はtasksテーブルのフォルダIDでリダイレクトする。

⑧テンプレートの作成(edit)

最後に一覧画面の編集リンクのhref設定を記載する。

    <a href="{{ route('tasks.edit', ['id' => $task->folder_id, 'task_id' => $task->id]) }}">
      編集
    </a>

以上で(7)は完了。