Laravelで超シンプルなToDoアプリを作成する

# はじめに
Laravelで超シンプルなToDoアプリを作成する手順を紹介します。
ToDoアプリは、CRUD（Create, Read, Update, Delete）の操作を兼ね揃えた最もシンプルなアプリの一つと言えます。
そのため、学習したい言語やフレームワークを用いてToDoアプリを作成することで、その言語やフレームワークを用いたデータの作成・読み取り・更新・削除といった基本操作を一通り学習することができます。

Laravelを用いてToDoアプリを作成するチュートリアルは公式のものも含めて既にいくつかあると思いますが、この記事では超シンプル・必要最低限の機能だけに絞ったToDoアプリを作成する手順を紹介します。
まずは最低限のToDoアプリを作成してみてLaravelのCRUDアプリ作成の勘所を押さえ、そこから自分が欲しいと思う機能を少しずつ加えながら学習を更に進めるのが良いと思います。

# 作成するアプリの概要
## 画面構成
超シンプルにしてLaravelによるCRUD操作に内容をフォーカスしたいので、CSSはほぼ設定していません。
見た目はイケてませんが、そこは機能を実現してから各自の好みで装飾してください。

### タスク一覧ページ
タスクの一覧を表示するためのページです。
このページからタスク作成ページ・タスク編集ページ・タスク削除ページへ遷移することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329353/8a165450-b127-b580-4d74-44e57dba980a.png)

このページのURLは、アプリのルート（`/`）にします。

### タスク作成ページ
新しいタスクを作成するためのページです。
タスクの情報をフォームに入力し、"追加"ボタンを押すことで新しいタスクを作成することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329353/ab1c2d17-370c-f2b6-176c-3d69aba7a8d1.png)

このページのURLは`/create-page`にします。
また、追加ボタンが押されて実際にタスクを追加するAPIのURLは`/create`とします。

### タスク編集ページ
選択したタスクの内容を編集するためのページです。
選択したタスクの内容が予めフォームに入力されているので、編集したい箇所を書き換えて"修正"ボタンを押すことで内容を書き換えることができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329353/d322403d-d1a0-8b2b-d5a0-941f0e8748c5.png)

このページのURLは`/edit-page`にします。
また、修正ボタンが押されて実際にタスクを修正するAPIのURLは`/edit`とします。

### タスク削除ページ
選択したタスクを削除するためのページです。
選択したタスクの内容が表示されるので、確認の上で"削除"ボタンを推すことでタスクを削除することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329353/14939485-568d-7723-7a74-cd9b5910aff4.png)

このページのURLは`/delete-page`にします。
また、削除ボタンが押されて実際にタスクを修正するAPIのURLは`/delete`とします。

## URL設計
このアプリで使用するURLは以下のとおりです。

| URL | HTTPリクエストメソッド | 用途 |
|:--|:--|:--|
| / | GET | アプリのトップページをレスポンスで返す |
| /create-page | GET | タスクの作成ページをレスポンスで返す |
| /create | POST | タスクの作成を実行する |
| /edit-page | GET | タスクの編集ページをレスポンスで返す |
| /edit | POST | タスクの編集を実行する |
| /delete-page | GET | タスクの削除ページをレスポンスで返す |
| /delete | POST | タスクの削除を実行する |

## テーブル構成
このアプリは、作成したタスクをデータベース(SQLite)に保存します。
使用するテーブルは"todos"テーブルひとつだけです。

本格的なアプリのデータベースとしてはSQLiteは力不足ですが、LaravelによるCRUDアプリの作成方法にフォーカスしたいので、まずは使うまでの準備が超簡単なSQLiteで慣れましょう。
作成したアプリをサーバで動かしたいといったステップまで進んだら、mysqlやpostgresなどのもう少し本格的なデータベースに切り替えることを検討すればよいと思います。

### todosテーブルの構成
todosテーブルは以下のようなカラムを持ちます。

| カラムの論理名 | カラムの物理名 |
|:--|:--|
| id | id |
| タスクの名前 | task_name |
| タスクの説明 | task_description |
| 担当者の名前 | assign_person_name |
| 見積時間(h) | estimate_hour |

# 開発環境を整える
## PHPとcomposerのインストール
こちらのページを参考にさせていただきました。
Macの場合でもほぼ変わりありませんので、composerのインストールまで済ませておきましょう。
[Laravelチュートリアル1 – 環境構築編(Windows)](https://awesome-linus.com/2019/05/28/laravel-tutorial-environment-windows/)

上記のチュートリアルですが、2以降も大変参考になるので、Laravel初学者は一通り手を動かしてみることをお勧めします。
[Laravelチュートリアル2 – ルーティングを理解しよう](https://awesome-linus.com/2019/06/03/laravel-tutorial-routing/)
[Laravelチュートリアル3 -コントローラーで簡単な処理を書いてみよう](https://awesome-linus.com/2019/06/04/laravel-tutorial-controller/)
[Laravelチュートリアル4 -データベースを使用して処理を書いてみよう](https://awesome-linus.com/2019/06/05/laravel-tutorial-database/)
[Laravelチュートリアル5 – フォームを扱った処理を書いてみよう](https://awesome-linus.com/2019/06/05/laravel-tutorial-form/)

## SQLite3のインストール
初期データを作成するためにSQLite3も使用します。
以下のページを参考にSQLite3もインストールしておきましょう。
[Laravelでsqliteをデータベースとして使用する方法](https://awesome-linus.com/2019/05/24/laravel-sqlite-database/)

# 開発の流れ
超シンプルなToDoアプリを作る流れを順を追って解説します。

## Laravelプロジェクトの作成
Laravelのプロジェクトを作成したいディレクトリをターミナルで開き、以下のコマンドを実行します。

```bash
composer create-project laravel/laravel=5.7 laravel_todolist
```

Laravelプロジェクトの作成が成功したら、以下のコマンドで"laravel_todolist"ディレクトリに入り、開発サーバを起動します。

```bash
cd laravel_todolist
php artisan serve
```

`http://127.0.0.1:8000`へアクセスして、Laravelと表示さてれいれば、開発サーバの起動は成功しています。


## データベースを用意
### sqliteファイルの作成
作成した"laravel_todolist"ディレクトリで以下のコマンドを実行してsqliteファイルを作成します。

```bash
touch database/database.sqlite
```

### LaravelプロジェクトのDB接続情報を書き換え
"laravel_todolist"ディレクトリにある`.env`ファイルをvi等で開いて内容を書き換えます。

```bash
vi .env
```

書き換え対象は以下の行です。

```
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret
```

SQLiteを使用するために以下のような内容に書き換えて保存します。
（`#`の行はSQLiteでは使わないのでコメントアウトしています）

```
DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=homestead
# DB_USERNAME=homestead
# DB_PASSWORD=secret
```

### マイグレーションの実行
"laravel_todolist"ディレクトリで以下のコマンドを実行し、マイグレーションを実行してテーブルを作成します。

```bash
php artisan migrate
```

Laravelはデフォルトでusersテーブルとpassword_resetsテーブルのマイグレーションファイルを持っているので、`migrate`コマンドを実行することでusersテーブルとpassword_resetsテーブルがデータベースに作成されます。

### SQLite3による接続確認
"laravel_todolist"ディレクトリで以下のコマンドを実行してデータベースに接続します。

```bash
sqlite3 database/database.sqlite
```

接続ができると、ターミナルが以下のような表示になります。
この画面でSQLを入力・実行することができます。

```bash
SQLite version 3.28.0 2019-04-15 14:49:49
Enter ".help" for usage hints.
sqlite>
```

`.table`というコマンドを入力することでテーブルの一覧を見ることができます。
migrateコマンドで作成したusersテーブルとpassword_resetsテーブルが存在することを確認してください。
（migrateを実行した際に、マイグレーションの履歴を保持するためのmigrationsというテーブルも自動的に作成されています）

```bash
sqlite> .table
migrations       password_resets  users
```

以上を確認できたらSQLite3を終了させます。
SQLite3を終了させるには`Ctrl+D`を入力します。

## ToDoテーブルの作成
機能を開発する前に、データを格納するtodosテーブルをデータベースに作成しておきます。

### マイグレーションファイルの作成
"laravel_todolist"ディレクトリで以下のコマンドを実行します。

```bash
php artisan make:migration create_todos_table --create=todos
```

### マイグレーションを実行してtodosテーブルを作成
"laravel_todolist/database/migrations"ディレクトリに```YYYY_MM_DD_hhmmss_create_todo_table.php```といったフォーマットの名前でファイルが作成されます。これがマイグレーションファイルです。
このファイルをviで開きます。

```php:YYYY_MM_DD_hhmmss_create_todo_table.php
<?php                                                                                                              

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateTodoTable extends Migration

   /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('todos', function (Blueprint $table) {
            $table->increments('id');
            $table->timestamps();
        });
    }
 
    /**
     * Reverse the migrations.
     *
     * @return void
    */
    public function down()
    {
        Schema::dropIfExists('todos');
    }                                                                                                              
 }
```

upファンクションに定義されているのが、マイグレーションが実行された際にデータベースに作成されるtodosテーブルの情報です。todosテーブルには以下の情報を加えたいと思います。

| カラムの論理名 | カラムの物理名 |
|:--|:--|
| id | id |
| タスクの名前 | task_name |
| タスクの説明 | task_description |
| 担当者の名前 | assign_person_name |
| 見積時間(h) | estimate_hour |

upファンクションを以下のように書き換えます。

```php:YYYY_MM_DD_hhmmss_create_todo_table.php
<?php                                                                                                              

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateTodoTable extends Migration

   /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('todos', function (Blueprint $table) {
            $table->increments('id');
            $table->string('task_name');  // 追加
            $table->text('task_description');  // 追加
            $table->string('assign_person_name');  // 追加
            $table->string('estimate_hour');  // 追加
            $table->timestamps();
        });
    }
 
    /**
     * Reverse the migrations.
     *
     * @return void
    */
    public function down()
    {
        Schema::dropIfExists('todos');
    }                                                                                                              
 }
```

修正が終わったら、ファイルを保存します。
"laravel_todolist"ディレクトリに戻り、以下のコマンドでマイグレーションを実行してください。

```bash
php artisan migrate
```

SQLite3でdatabase.sqliteを開き、todosテーブルが作成されていることを確認します。

```bash
sqlite3 database/database.sqlite
SQLite version 3.28.0 2019-04-15 14:49:49
Enter ".help" for usage hints.
sqlite> .table
migrations       password_resets  todos            users          
sqlite> 
```

### SQLite３で初期データを登録
引き続き、SQLite3で初期データを登録します。
以下のSQLを実行します。

```sql

sqlite> INSERT INTO todos(task_name, task_description, assign_person_name, estimate_hour) VALUES("テスト", "テストです", "テストさん", "1");
```

以下のSQLを実行して、初期データが登録されたことを確認してください。

```sql
sqlite> SELECT * FROM todos;
1|テスト|テストです|テストさん|1||
```

## Todoモデルクラスを実装
todosテーブルをLaravelから操作するために、Todoモデルクラスを実装します。

"laravel_todolist"ディレクトリで以下のコマンドを実行して、Todoモデルクラスを生成します。

```
php artisan make:model Todo
```

"laravel_todolist/app/Todo.php"というファイルが生成されるので、vi等で開き以下の行を追加して保存します。
ID列を$guardedで指定したので、ID列にはデータの作成時や更新時に値が入らないようになります。（ID列には、データ作成時に自動で連番を入れたいのでこのような指定を行っておきます）

```php:Todo.php
<?php                                                                                                              

namespace App;

use Illuminate\Database\Eloquent\Model;

class Todo extends Model
{
    protected $guarded = ['id'];  // 追加
}
```

## タスク一覧ページを実装
CRUDのR（Read）に該当する機能を実装します。

### ルーティングの設定
"laravel_todolist/routes/web.php"をvi等で開きます。

```php:web.php
<?php                                                                                                              
 
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});
```

`/`へのルーティングが予め書かれていますが、ここを以下のように書き換えます。

```php:web.php
<?php                                                                                                              
 
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', 'TodolistFormController@index');
```

これで、`/`を呼び出されたときに、TodolistFormControllerクラスのindexファンクションが実行される指定になりました。

### コントローラの作成
web.phpを書き換えましたが、まだTodolistFormControllerクラスが無いので実装しましょう。

"laravel_todolist"ディレクトリで以下のコマンドを実行してTodolistFormControllerクラスを作成します。

```bash
php artisan make:controller TodolistFormController
```

"laravel_todolist/app/Http/Controllers/TodolistFormController.php"というファイルが作成されるので、このファイルをvi等で開きます。

```php:TodolistFormController.php
<?php                                                                                                              
 
namespace App\Http\Controllers;
 
use Illuminate\Http\Request;
 
class TodolistFormController extends Controller
{
    //
}
```

まだ何もファンクションが無い状態なので、ここにタスク一覧を表示するindexファンクションを追加します。

```php:TodolistFormController.php
<?php                                                                                                              
 
namespace App\Http\Controllers;
 
use Illuminate\Http\Request;
use App\ToDo;  // 追加

class TodolistFormController extends Controller
{
     // タスクを一覧で表示
     public function index()
     {
         $todos = Todo::orderBy('id', 'asc')->get();
         return view('todo_list', [
             "todos" => $todos
         ]);
     }
}
```

`use App\ToDo;`でTodoモデルクラスを読み込んでいることに注意してください。
ToDoモデルクラスの`get()`ファンクションを呼び出すことで、todosテーブルの値を取り出すことが出来ます。
また、`orderBy()`ファンクションを使用することでデータのソートも行っています。

returnでは"todo_list"ビューを使用し、`$todos`データを渡すように定義しています。
次は"todo_list"ビューを作成しましょう。

### ビューの作成
ビューファイルはartisanコマンドで作成できないので、touchコマンド等で直接ファイルを作成します。
"laravel_todolist/resources/views/"ディレクトリに"todo_list.blade.php"というファイルを作成し、vi等で開いて以下のように実装します。

```php:todo_list.blade.php
<h1>ToDo List</h1>                                                                                                 
<div>
    <h2>タスクの一覧</h2>
    <a href="/create-page">タスクを追加</a>
    <table border="1">
        <tr>
            <th>タスクの名前</th>
            <th>タスクの説明</th>
            <th>担当者の名前</th>
            <th>見積時間(h)</th>
            <th colspan="2">操作</th>
        </tr>
        @foreach($todos as $todo)
        <tr>
            <td>{{$todo->task_name}}</td>
            <td>{{$todo->task_description}}</td>
            <td>{{$todo->assign_person_name}}</td>
            <td>{{$todo->estimate_hour}}</td>
            <td><a href="/edit-page/{{$todo->id}}">編集</a></td>
            <td><a href="/delete-page/{{$todo->id}}">削除</a></td>
        </tr>
        @endforeach
    </table>
</div>
```

見るべきポイントとしては`@foreach($todos as $todo)`でタスクの数だけ繰り返し表示している箇所です。コントローラからタスクの一覧が`$todos`という名前で渡されてくるので、これをforeachでループさせながら全件表示しています。

また、まだタスクの追加・編集・削除機能は実装していませんが、`<a>`タグによるリンクは先に実装してしまいます。
編集と削除のURLはどのタスクを対象とするのかをIDで判別するため、URLに`{{$todo->id}}`を渡しています。

### 動作確認
"laravel_todolist"ディレクトリで以下のコマンドを実行し、Laravelの開発サーバを起動します。

```bash
php artisan serve
```

ブラウザで`http://localhost:8000/`を開き、SQLiteで登録しておいた初期データが表示されることを確認してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329353/ee130770-97ef-b530-f551-6b2b704554a8.png)

これで、タスク一覧ページの実装が出来ました。
試しに、SQLite3でINSERTのSQLを実行して初期データの件数を増やすと、タスク一覧ページに表示されるタスクの件数も増えていくはずです。
次は、タスクを追加するページを実装します。

## タスク追加ページを実装
CRUDのC（Create）に該当する機能を実装します。

### ルーティングの設定
"laravel_todolist/routes/web.php"を編集します。
タスク追加ページを表示する`/create-page`と、追加ボタンが押されたら実際にタスクを追加する`/create`へのルーティングを追加します。
`/create-page`はGETメソッドの指定で、`/create`はPOSTメソッドの指定になる点に注意してください。

```php:web.php
<?php                                                                                                              
 
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', 'TodolistFormController@index');
Route::get('/create-page', 'TodolistFormController@createPage');
Route::post('/create', 'TodolistFormController@create');
```

### コントローラの作成
"laravel_todolist/app/Http/Controllers/TodolistFormController.php"を開き、TodolistFormControllerの実装に処理を追加します。

#### createPageファンクションの追加
"createPage"ファンクションはタスク作成ページを返すだけでよいので、"todo_create"ビューを返しているだけです。

```php:TodolistFormController.php
    // ここより上は省略

    // タスク作成画面を表示
    public function createPage()
    {
        return view('todo_create');
    }

    // 以下省略
```

#### createファンクションの追加
"create"ファンクションはタスク作成ページで「作成」ボタンを押されたら実行されます。タスク作成ページで入力された情報がPOSTリクエストのパラメータとして渡ってくるので、その情報をもとに新しいタスクを作成しています。

Todoモデルクラスをnewしてインスタンスを生成し、タスクの情報をパラメータに詰めて`save()`ファンクションを呼び出すことでtodosテーブルにレコードが保存されます。

処理の最後で`return redirect('/')`することで、タスク登録後に`/`ページへリダイレクトされます。

```php:TodolistFormController.php
    // ここより上は省略

    // タスクを登録
    public function create(Request $request)
    {
        $todo = new Todo();
        $todo->task_name = $request->task_name;
        $todo->task_description = $request->task_description;
        $todo->assign_person_name = $request->assign_person_name;
        $todo->estimate_hour = $request->estimate_hour;
        $todo->save();
 
        return redirect('/');
    }

    // 以下省略
```

### ビューの作成
タスク追加画面が必要なので作成します。
"laravel_todolist/resources/views/"ディレクトリに"todo_create.blade.php"というファイルを作成し、vi等で開いて以下のように実装します。

```php:todo_create.blade.php
<h1>ToDo List</h1>                                                                                                 
<div>
    <h2>タスクを追加</h2>
    <form method="POST" action="/create">
        @csrf
        <p>
            タスクの名前：<input type="text" name="task_name">
        </p>
        <p>
            タスクの説明：<input type="text" name="task_description">
        </p>
        <p>
            担当者の名前：<input type="text" name="assign_person_name">
        </p>
        <p>
            見積時間(h)：<input type="number" name="estimate_hour">
        </p>
        <input type="submit" name="create" value="追加">
    </form>
    <a href="/">戻る</a>
</div>
```

`<form method="POST" action="/create">`としてあるので、`<input type="submit" name="create" value="追加">`ボタンが押されたら`/create`のPOSTメソッドへリクエストが飛ぶことになります。

### 動作確認
"laravel_todolist"ディレクトリで`php artisan serve`コマンドでテストサーバを起動し、以下の手順で新しいタスクを追加できることを確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329353/6c5d12a6-d2f7-118c-e3a9-12b81db0888e.png)

## タスク編集ページを実装
CRUDのU（Update）に該当する機能を実装します。

### ルーティングの設定
"laravel_todolist/routes/web.php"を編集します。
タスク編集ページを表示する`/edit-page`と、修正ボタンが押されたら実際にタスクを更新する`/edit`へのルーティングを追加します。

`/edit-page`はGETメソッドの指定で、`/edit`はPOSTメソッドの指定になる点はタスク作成ページと同様です。
ただし`/edit-page`へのルーティングは、「どのタスクを編集するのか」という情報をURLパラメータに加えたいため、`'/edit-page/{id}'`という指定になっている点に注意です。

```php:web.php
<?php                                                                                                              
 
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', 'TodolistFormController@index');
Route::get('/create-page', 'TodolistFormController@createPage');
Route::post('/create', 'TodolistFormController@create');
Route::get('/edit-page/{id}', 'TodolistFormController@editPage');
Route::post('/edit', 'TodolistFormController@edit');
```

### コントローラの作成
"laravel_todolist/app/Http/Controllers/TodolistFormController.php"を開き、TodolistFormControllerの実装に処理を追加します。

#### editPageファンクションの追加
"editPage"ファンクションは、編集対象のタスクのIDがURLパラメータの`$id`で渡ってくるので、`$id`を引数として受け付けます。
Todoモデルクラスのfindファンクションを用いるとテーブルのキーを用いた検索ができるので、引数で受け取った`$id`を使って対象のタスクを取得し、"todo_edit"ビューと合わせて返しています。

```php:TodolistFormController.php
    // ここより上は省略

    // タスク編集画面を表示
    public function editPage($id)
    {
        $todo = Todo::find($id);
        return view('todo_edit', [
            "todo" => $todo
        ]);
    }

    // 以下省略
```

#### editファンクションの追加
"edit"ファンクションはタスク編集ページで「修正」ボタンを押されたら実行されます。タスク修正ページで入力された情報がPOSTリクエストのパラメータとして渡ってくるので、その情報をもとにタスクの情報を更新しています。

まず、Todoモデルクラスのfindファンクションを使って、リクエストパラメータに入っている`$id`のタスクをデータベースのtodosテーブルから取得します。
そして、updateファンクションを使って、取得したタスクの内容を書き換えます。

処理の最後で`return redirect('/')`することで、タスク編集後に`/`ページへリダイレクトされます。

```php:TodolistFormController.php
    // ここより上は省略

    // タスクを更新
    public function edit(Request $request)
    {
        Todo::find($request->id)->update([
            'task_name' => $request->task_name,
            'task_description' => $request->task_description,
            'assign_person_name' => $request->assign_person_name,
            'estimate_hour' => $request->estimate_hour
        ]);
        return redirect('/');
    }

    // 以下省略
```

### ビューの作成
タスク編集画面が必要なので作成します。
"laravel_todolist/resources/views/"ディレクトリに"todo_edit.blade.php"というファイルを作成し、vi等で開いて以下のように実装します。

```php:todo_edit.blade.php
<h1>ToDo List</h1>                                                                                                 
<div>
    <h2>タスクの修正</h2>
    <form method="POST" action="/edit">
        @csrf
        <input type="hidden" name="id" value="{{$todo->id}}">
        <p>
            タスクの名前：<input type="text" name="task_name" value="{{$todo->task_name}}">
        </p>
         <p>
             タスクの説明：<input type="text" name="task_description" value="{{$todo->task_description}}">
         </p>
         <p>
             担当者の名前：<input type="text" name="assign_person_name" value="{{$todo->assign_person_name}}">
         </p>
         <p>
             見積時間(h) ：<input type="number" name="estimate_hour" value="{{$todo->estimate_hour}}">
         </p>
         <input type="submit" name="edit" value="修正">
     </form>
     <a href="/">戻る</a>
 </div>
```

`<form method="POST" action="/edit">`としてあるので、`<input type="submit" name="edit" value="修正">`ボタンが押されたら`/edit`のPOSTメソッドへリクエストが飛ぶことになります。
また、各inputのvalueには、コントローラから渡された`$todo`データに格納されているタスクの情報が入ります。これによって、タスク編集画面を開いたときに、タスクの情報が予めフォームに入力された状態になります。

### 動作確認
"laravel_todolist"ディレクトリで`php artisan serve`コマンドでテストサーバを起動し、以下の手順でタスクの編集ができることを確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329353/04ea3917-407f-5d90-2bf3-46da1e0b0f3e.png)

## タスク削除ページを実装
CRUDのD(Delete)に該当する機能を実装します。

### ルーティングの設定
"laravel_todolist/routes/web.php"を編集します。
タスク削除ページを表示する`/delete-page`と、修正ボタンが押されたら実際にタスクを更新する`/delete`へのルーティングを追加します。

`/delete-page`はGETメソッドの指定で、`/delete`はPOSTメソッドの指定になる点はタスク編集ページと同様です。`/delete-page`へのルーティングについても、「どのタスクを削除するのか」という情報をURLパラメータに加えたいため、`'/delete-page/{id}'`という指定になっています。
ただし、`/delete`はPOSTメソッドの指定で`'/delete/{id}'`となっている点は注意です。これは、削除ページではタスクの内容を確認するだけでフォームへの入力は行わないため、リクエストパラメータを使うよりもURLパラメータにIDを仕込んだほうがシンプルだと考えたためです。

```php:web.php
<?php                                                                                                              
 
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', 'TodolistFormController@index');
Route::get('/create-page', 'TodolistFormController@createPage');
Route::post('/create', 'TodolistFormController@create');
Route::get('/edit-page/{id}', 'TodolistFormController@editPage');
Route::post('/edit', 'TodolistFormController@edit');
Route::get('/delete-page/{id}', 'TodolistFormController@deletePage');
Route::post('/delete/{id}', 'TodolistFormController@delete');
```

### コントローラの作成
"laravel_todolist/app/Http/Controllers/TodolistFormController.php"を開き、TodolistFormControllerの実装に処理を追加します。

#### deletePageファンクションの追加
"deletePage"ファンクションは、削除対象のタスクのIDがURLパラメータの`$id`で渡ってくるので、`$id`を引数として受け付けます。
Todoモデルクラスのfindファンクションで対象のタスクを取得し、"todo_delete"ビューと合わせて返しています。

```php:TodolistFormController.php
    // ここより上は省略

    // タスク削除画面を表示
    public function deletePage($id)
    {
        $todo = Todo::find($id);
        return view('todo_delete', [
            "todo" => $todo
        ]);
    }

    // 以下省略
```

#### deleteファンクションの追加
"delete"ファンクションはタスク削除ページで「削除」ボタンを押されたら実行されます。選択されたタスクのIDがURLパラメータとして渡ってくるので、その情報をもとにタスクの情報を削除しています。

まず、Todoモデルクラスのfindファンクションを使って、URLパラメータに入っている`$id`のタスクをデータベースのtodosテーブルから取得します。
そして、deleteファンクションを使って、取得したタスクをtodosテーブルから削除しています。

処理の最後で`return redirect('/')`することで、タスク編集後に`/`ページへリダイレクトされます。

```php:TodolistFormController.php
    // ここより上は省略

    // タスクを削除
    public function delete(Request $id)
    {
        Todo::find($id)->delete();
        return redirect('/');
    }

    // 以下省略
```

### ビューの作成
"laravel_todolist/resources/views/"ディレクトリに"todo_delete.blade.php"というファイルを作成し、vi等で開いて以下のように実装します。

```php:todo_delete.blade.php
<h1>ToDo List</h1>                                                                                                 
<div>
    <h2>タスクを削除</h2>
    <form method="POST" action="/delete/{{$todo->id}}">
        @csrf
        <p>
            タスクの名前：{{$todo->task_name}}
        </p>
        <p>
            タスクの説明：{{$todo->task_description}}
        </p>
        <p>
            担当者の名前：{{$todo->assign_person_name}}
        </p>
        <p>
            見積時間(h) ：{{$todo->estimate_hour}}
        <p>
        <input type="submit" name="delete" value="削除">
    </form>
    <a href="/">戻る</a>
</div>
```

`<form method="POST" action="/delete">`としてあるので、`<input type="submit" name="delete" value="削除">`ボタンが押されたら`/delete`のPOSTメソッドへリクエストが飛ぶことになります。
削除対象のタスクの情報はコントローラから`$todo`という名前のデータで渡されてきているので、`$todo`から値を取り出してHTML上に直接（inputタグを使わずに）表示しています。

### 動作確認
"laravel_todolist"ディレクトリで`php artisan serve`コマンドでテストサーバを起動し、以下の手順でタスクの削除ができることを確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329353/59b6e379-d5d1-be0e-2b6c-8eda80c6111e.png)

## その他の機能を実装
以上で、超シンプルなToDoアプリのCRUD（Create, Read, Update, Delete）の機能が実現できました。
このままでもまぁまぁ使えるのですが、もう少し完成度を上げるために以下の２点の改善を行います。

1. タスクに設定した見積時間の合計を表示する
2. Validationの実装（タスク作成時と編集時に入力内容をチェックする）

### 見積時間の合計を表示
個々のタスクの見積時間はタスク一覧ページで表示されますが、タスクの数が増えてくるといちいち合計を手計算しなければいけないのは面倒です。
そこで、全てのタスクの見積時間を合計し、タスク一覧ページに表示しましょう。

#### コントローラの修正
"laravel_todolist/app/Http/Controllers/TodolistFormController.php"を開き、TodolistFormControllerのindexファンクションに処理を追加します。

```php:TodolistFormController.php
    // ここより上は省略

    // タスクを一覧で表示
    public function index()
    {
        $todos = Todo::orderBy('id', 'asc')->get();
 
        // 見積時間の合計を求める
        $estimate_hour_sum = 0;
        foreach ($todos as $todo){
            $estimate_hour_sum += $todo->estimate_hour;
        }
 
        return view('todo_list', [                                                                                 
            "todos" => $todos,
            "estimate_hour_sum" => $estimate_hour_sum
        ]);
    }

    // 以下省略
```

新たに`$estimate_hour_sum`という変数を定義し、foreachで全タスクを回して`estimate_hour`を合算します。そして最後に、ビューを返す際のデータに`estimate_hour_sum`も加えてあげます。

#### ビューの修正
"laravel_todolist/resources/views/todo_list.blade.php"を開いて以下のように修正します。

```php:todo_list.blade.php
 <h1>ToDo List</h1>
 <div>
     <h2>タスクの一覧</h2>
     <a href="/create-page">タスクを追加</a>
     <table border="1">
        <tr>
            <th>タスクの名前</th>
            <th>タスクの説明</th>
            <th>担当者の名前</th>
            <th>見積時間(h)</th>
            <th colspan="2">操作</th>
        </tr>
        @foreach($todos as $todo)
        <tr>
            <td>{{$todo->task_name}}</td>
            <td>{{$todo->task_description}}</td>
            <td>{{$todo->assign_person_name}}</td>
            <td>{{$todo->estimate_hour}}</td>
            <td><a href="/edit-page/{{$todo->id}}">編集</a></td>
            <td><a href="/delete-page/{{$todo->id}}">削除</a></td>
        </tr>                                                                                                      
        @endforeach
    </table>
    <p>
        見積時間の合計(h)：{{$estimate_hour_sum}}
    </p>
</div>
```

一番下に見積時間の合計を追加しています。

### Validationの実装
現状では、タスク作成やタスク編集で、フォームの値が空の状態だった場合、QueryExceptionが発生してしまいます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329353/7dff2bd3-50a1-0546-f5b9-4eb5951a4ed6.png)

これは、todosテーブルの各カラムについてNULLを許容していないためですが、ユーザがタスク作成ページやタスク編集ページではフォームの値を埋めた状態でボタンを押してもらえればこのExceptionは回避できます。
そのような機能を実現させたい場合、Validationを実装することでフォームに入力されたデータを先にチェックし、異常なデータが有る場合は処理を中断させることができます。

#### コントローラの修正
"laravel_todolist/app/Http/Controllers/TodolistFormController.php"を開き、TodolistFormControllerのcreateファンクションに処理を追加します。

```php:TodolistFormController.php
    // ここより上は省略

    // タスクを登録
    public function create(Request $request)
    {
        $validator = $request->validate([
            'task_name' => 'required',
            'task_description' => 'required',
            'assign_person_name' => 'required',
            'estimate_hour' => 'required'
        ]);
 
        $todo = new Todo();
        $todo->task_name = $request->task_name;
        $todo->task_description = $request->task_description;
        $todo->assign_person_name = $request->assign_person_name;
        $todo->estimate_hour = $request->estimate_hour;
        $todo->save();
 
        return redirect('/');
    }

    // 以下省略
```

Todoモデルクラスをnewする前に、HTTPリクエストで渡されてきたパラメータを`$validator`を使ってチェックしています。
チェックのルールは各パラメータで個別に設定することができ、今回の場合は全てのパラメータについて`required`を指定して必須としています。

Validationルールは他にも色々あるので、以下の公式ガイドの「使用可能なバリデーションルール」を参照してみてください。
[Laravel 5.7 バリデーション](https://readouble.com/laravel/5.7/ja/validation.html)

#### ビューの修正
"laravel_todolist/resources/views/todo_create.blade.php"を開いて以下のように修正します。

```php:todo_create.blade.php
<h1>ToDo List</h1>                                                                                                 
<div>
    <h2>タスクを追加</h2>
    <form method="POST" action="/create">
        @csrf
        @if ($errors->any())
            <ul>
            @foreach ($errors->all() as $error)
               <li style="color:red">{{$error}}</li>
            @endforeach
            </ul>
        @endif
        <p>
            タスクの名前：<input type="text" name="task_name">
        </p>
        <p>
            タスクの説明：<input type="text" name="task_description">
        </p>
        <p>
            担当者の名前：<input type="text" name="assign_person_name">
        </p>
        <p>
            見積時間(h)：<input type="number" name="estimate_hour">
        </p>
        <input type="submit" name="create" value="追加">
    </form>
    <a href="/">戻る</a>
</div>
```

`@if ($errors->any())`で、Validationエラーがあった場合のエラーメッセージ表示領域を定義しています。
コントローラ側で`$validator`のチェックルールに引っかかった場合、自動的にビューが再表示されます。その際に`$errors`にエラー内容が配列で格納されているので、foreachループで回して全てのエラーメッセージを`<li>`タグでリスト表示しています。

#### 日本語メッセージの表示
##### 日本語メッセージファイルのインストール
ここまででもValidationチェックとエラーメッセージの表示はできるのですが、エラーメッセージが英文表記となっていてユーザフレンドリーとは言い難い状態です。
そのため、まずは公式の手順に沿って日本語メッセージのインストールを行います。

[Laravel 5.7 validation.php言語ファイル](https://readouble.com/laravel/5.7/ja/validation-php.html)

"laravel_todolist"ディレクトリで以下のコマンドを実行します。

```bash
php -r "copy('https://readouble.com/laravel/5.7/ja/install-ja-lang-files.php', 'install-ja-lang.php');"
php -f install-ja-lang.php
php -r "unlink('install-ja-lang.php');"
```

"laravel_todolist/resources/lang/ja"というディレクトリが新たに作られ、その配下にあるvalidation.phpがValidationの日本語メッセージです。

##### Laravelプロジェクトの言語設定を変更
"laravel_todolist/config/app.php"を開き、`locale`というキーの値を`en`から`ja`に変更して保存します。

```php:app.php
    // ここより上は省略

    /*
    |--------------------------------------------------------------------------
    | Application Locale Configuration
    |--------------------------------------------------------------------------
    |
    | The application locale determines the default locale that will be used
    | by the translation service provider. You are free to set this value
    | to any of the locales which will be supported by the application.
    |
    */
 
    'locale' => 'ja',

    // 以下省略
```

##### カスタムバリデーション属性名の設定
ここまででメッセージのフォーム名以外は日本語になりますが、まだフォーム名はリクエストパラメータのキー名がそのまま表示されてしまいます。ここを日本語名に変換するには、カスタムバリデーション属性名を設定する必要があります。
"laravel_todolist/resources/lang/ja/validation.php"を開いて以下のように修正します。

```php:validation.php
    // ここより上は省略

    /*
    |--------------------------------------------------------------------------
    | カスタムバリデーション属性名
    |--------------------------------------------------------------------------
    |
    | 以下の言語行は、例えば"email"の代わりに「メールアドレス」のように、
    | 読み手にフレンドリーな表現でプレースホルダーを置き換えるために指定する
    | 言語行です。これはメッセージをよりきれいに表示するために役に立ちます。
    |
    */
 
    'attributes' => [
        'task_name' => 'タスクの名前',
        'task_description' => 'タスクの説明',
        'assign_person_name' => '担当者の名前',
        'estimate_hour' => '見積時間',
    ],
];
```

##### 動作確認
タスク追加画面でフォームの値を入力せずに追加ボタンを押すと、以下のようにValidationメッセージが表示されるようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/329353/5adfae4a-676b-7fbc-460e-41f2ac51a9fa.png)

同様のExceptionはタスク編集画面でも起こりうるので、タスク編集画面とコントローラのeditファンクションにもValidationを導入しておくと良いでしょう。

# ToDoアプリの機能追加例
超シンプルなToDoアプリの作成紹介は以上となります。
ここからは、各々がToDoアプリをベースにしながら付帯機能の開発を進めて、Laravelによるアプリ開発の理解を深めるのがよいと思います。
いくつか機能追加の例を示しておきます。

## 見積時間(h)のデータ型を文字型から数値型に変える
現状では見積時間(h)はマイグレーションファイルでStringとして定義されているため、todosテーブル上でも文字型として定義されています。
しかし、時間は数字であったほうが集計等で扱いやすいので、文字型ではなく数字型の方が良いはずです。

マイグレーションファイルを追加し、estimate_hourの定義を数値型に変更する対応をしてみると、開発途中でデータ型を変更する練習になると思います。

## 担当者の名前をセレクトボックスから選択できるようにする
現状では担当者の名前はテキストボックスに直接入力していますが、ここは予め決められた名前の中から選択できる方が使い勝手が良いはずです。

担当者の名前をセレクトボックスから選択できるようにするには、ざっと考えただけでも以下のような対応が必要になってきそうです。

* 担当者の名前をデータベースに登録しておきたい
  * 担当者の名前のデータベースが必要
  * 担当者の名前のCRUD機能が必要
* タスク登録時に担当者の名前をデータベースから取得したい
  * タスクをデータベースに保存する際の担当者の名前は、Valueではなくidを保存する
* タスク編集時に担当者の名前をデータベースから取得したい
  * タスク登録時と似ているが、セレクトボックスには予めidに対応するvalueを選択した状態にしておきたい
* その他考慮すべきこと
  * 担当者の名前を削除するときに、その担当者のタスクが残っている場合にどうするか？

これは、ToDoモデルに加えて、新たに担当者のモデル（AssignPersonモデル）が必要となり、モデル間の関係も考慮する練習になると思います。

## ログイン機能の実装
現状ではこのToDoアプリはログイン機能を備えていないため、URLを知っていれば誰でもアクセスしてデータを操作できてしまいます。これではセキュリティ面で実用的では無いため、ログイン機能を備えるべきでしょう。

Laravelは標準でAuthの機能を備えているので、公式ガイドを参考にしながらログイン機能を追加してみるとより実用的なアプリになると思います。
[Laravel 5.7 認証](https://readouble.com/laravel/5.7/ja/authentication.html)

また、ユーザに対して「管理者」「一般」といった権限レベルを追加し、タスクの削除ができるのは管理者のみといった権限設定を行えるようにするのも学習になると思います。

## 排他制御の実装
ログイン機能を追加したら、実用性を上げるためにデータ更新時の排他制御も実現させたいところです。
現状では、複数のアカウントから同時にタスクの編集を行われた場合、一方の編集内容は破棄されてしまうという問題があります。
処理の流れを示すと以下のようになります。

**排他制御が行われていない場合の処理の流れ**

1. Aさんがタスク1のタスク編集画面を開く
2. Bさんがタスク1のタスク編集画面を開く
3. Aさんがタスク1の内容を書き換えて保存する
4. Bさんがタスク1の内容を書き換えて保存する　※Aさんの編集内容はここには反映されていない
5. タスク1はBさんの編集内容で保存されるため、Aさんの編集内容は破棄されてしまう

このような問題を回避するためには、排他制御としてデータ更新時の楽観ロック（更新前に読み込んだデータが、他のユーザによって先に更新されていないかチェックする仕組み）を実装する必要があります。
排他制御を実装している場合、処理の流れは以下のようになります。

**排他制御が行われる場合の処理の流れ**

1. Aさんがタスク1のタスク編集画面を開く
2. Bさんがタスク1のタスク編集画面を開く
3. Aさんがタスク1の内容を書き換えて保存しようとしたときに、他の人が先に書き換え・保存していないかチェックする
4. 他の人によって書き換え・保存されていないので、Aさんの書き換え内容は保存される 
5. Bさんがタスク1の内容を書き換えて保存しようとしたときに、他の人が先に書き換え・保存していないかチェックする
6. Aさんによって書き換え・保存されているので、Bさんの書き換えは失敗する

複数アカウントでデータが操作されるアプリであれば排他制御は避けて通れないので、良い練習になると思います