［Laravel］超基礎☆簡単タスク管理アプリのチュートリアル

（※ この記事は、Laravel8が出るより結構前に書いて、他のブログに載せていました。それを転載したものなので、Laravel7で解説しています。ご了承くださいませ。。(m´・ω・｀)m ｺﾞﾒﾝ…）

環境：XAMPP

初めてLaravelで何かアプリを作ってみようと思って、公式ドキュメントにも載っている簡単なタスク管理アプリを作ることにしました！
自分への備忘録も兼ねて、チュートリアル的な感じで解説していこうと思います。同じく初Laravel製アプリを作ろうとしている方や、Laravel勉強中の初学者の方に参考になれば幸いです！

完成品は以下のような感じです。↓↓

<img width="500" alt="pic_1" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235741/8ce62713-a63a-41f5-573e-266834bad99e.png">

新規追加でタスクを入力すると、タスク一覧に表示されて、削除ボタンを押すと削除されます。↓↓

<img width="500" alt="pic_2" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235741/6187c7e7-73eb-8c36-f62c-16f9d23e509d.png">

それでは早速スタート！

#1： プロジェクトの作成

XAMPPのインストール後、C:\xampp\htdocs に移動して、composerでプロジェクトを作成します。プロジェクト名は『basic-task』にします。

```
composer create-project --prefer-dist laravel/laravel basic-task
```
※ 因みに、現在は上記のコマンドだと最新の8系がインストールされるので、7系にする場合は、以下コマンドのようにバージョン指定が必要です。

```
composer create-project --prefer-dist laravel/laravel basic-task "7.*"
```

プロジェクトを作成したら、cd コマンドでプロジェクト内（C:\xampp\htdocs\basic-task）に移動します。因みに今回のLaravelのバージョンを確認したら下記のようになりました。

```
php artisan --version　//Laravelバージョン確認用コマンド
Laravel Framework 7.11.0
```

#2： データベースの作成

XAMPPでデータベースを作成します。(XAMPPでのデータベースの作り方がわからない人は、検索してみてください。) データベース名は『basic_task』にします。
データベースを作ったら、C:\xampp\htdocs\basic-task\\.env ファイルに移動して、 DB_DATABASE を『basic_task』に変更します。DB_USERNAME と DB_PASSWORD はそのままにします。(XAMPP側でデータベース ユーザーを設定した人は、こちらも変更してください。)

```basic-task\.env
DB_DATABASE=basic_task // ←ここ変更。
DB_USERNAME=root
DB_PASSWORD=
```

#3： ロケールの設定

C:\xampp\htdocs\basic-task\config\app.php ファイルへ移動します。
'timezone' と 'locale' を以下のように変更します。

```basic-task\config\app.php
'timezone' => 'Asia/Tokyo',
'locale' => 'ja',
```

#4： マイグレーションファイルの作成

マイグレーションファイル (データベーステーブル)を作成します。テーブル名は『tasks』にします。 --create=tasks オプションをつけると、作成したマイグレーションファイルに対応したテーブルを作ってくれます。(XAMPP側でいちいちテーブルを作らなくてもOK！)

```
php artisan make:migration create_tasks_table --create=tasks
```

#5： マイグレーションファイルの更新

作成したマイグレーションファイルを更新します。
C:\xampp\htdocs\basic-task\database\migrations\2020_05_19_021913_create_tasks_table.php ファイルへ移動します。（2020_05_19_021913 の部分は、作成した日付ですので、作成日によって変わります。）今回は極シンプルなアプリということもあり、nameカラムの追加のみです。18行目あたりに $table->string('name'); だけを追記します。

```basic-task\database\migrations\2020_05_19_021913_create_tasks_table.php
    public function up()
   {
       Schema::create('tasks', function (Blueprint $table) {
           $table->id();
           $table->string('name'); //ここを追記。
           $table->timestamps();
       });
   }
```

追記したらマイグレーションします。

```
php artisan migrate
```

tasksテーブルに、nameカラムが入っていることを確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235741/09d96db4-9d33-4e65-a25c-25872b01a64d.png)

#6： モデルの作成
マイグレーションファイルに対応するモデルを作ります。
モデル名は『Task』にします。

```
php artisan make:model Task
```

C:\xampp\htdocs\basic-task\app\Task.php ファイルにモデルが作成されます。

#7： ルートの設定

お次はルートの設定をします。C:\xampp\htdocs\basic-task\routes\web.php ファイルへと移動します。以下3つのルートを設定します。

①トップページへアクセスしたときに表示されるページ。（getリクエスト）
②新規追加フォームで追加ボタンを押した（＝submitした）時にデータを飛ばすページ（postリクエスト）
※ フォームから飛んでくるデータはRequest型で渡ってきます。その為、コールバック関数の引数には、Implicit Bindingを利用して、(Request $request) とします。この辺りがよくわからない方は、『Laravel フォーム Request Implicit Binding』等で検索してみてください。
③削除ボタンを押した時にデータを飛ばすページ（deleteリクエスト）
以上の3つです。

```basic-task\routes\web.php
Route::get('/', function () {
   //
});
Route::post('/task', function (Request $request) {
   //
});
Route::delete('/task/{task}', function () {
   //
});
```

#8： BootstrapとFont Awesomeのインストール

viewページ作成の前に、BootstrapとFont Awesomeのインストールをしておきます。まずはBootstrapのインストールから。

composerを使って、laravel/ui パッケージをインストールします。

```
composer require laravel/ui
```

Artisanコマンドを利用して、Bootstrapのスカフォールドをインストールします。

```
php artisan ui bootstrap
```

package.json の "devDependencies"箇所に、以下のようにBootstrapが追加されます。

```package.json
    "devDependencies": {
       "axios": "^0.19",
       "bootstrap": "^4.0.0",　//Bootstrapが追加されています。
       "cross-env": "^7.0",
       "jquery": "^3.2",
       "laravel-mix": "^5.0.1",
       "lodash": "^4.17.13",
       "popper.js": "^1.12",
       "resolve-url-loader": "^3.1.0",
       "sass": "^1.15.2",
       "sass-loader": "^8.0.0",
       "vue-template-compiler": "^2.6.11"
   },
```

npmでインストールします。

```
npm install && npm run dev
```

これで、C:\xampp\htdocs\basic-task\public\css\app.css ファイルにコンパイルされてBootstrapが使えるようになります！

次はFont Awesomeをインストールします。まずはnpmでインストールします。

```
npm install --save @fortawesome/fontawesome-free
```

package.json の "dependencies" の箇所に、以下のようにFont Awesomeが追加されます。

```package.json
    "dependencies": {
       "@fortawesome/fontawesome-free": "^5.13.0"
   }
```

C:\xampp\htdocs\basic-task\resources\sass\app.scss ファイルに移動して、以下の通りに追記してインポートします。

```basic-task\resources\sass\app.scss
// Font Awesome
@import '~@fortawesome/fontawesome-free/scss/fontawesome';
@import '~@fortawesome/fontawesome-free/scss/regular';
@import '~@fortawesome/fontawesome-free/scss/solid';
@import '~@fortawesome/fontawesome-free/scss/brands';
```

npmで実行します。

```
npm run production
```

C:\xampp\htdocs\basic-task\public\css\app.css ファイルにコンパイルされてFont Awesomeが使えるようになります！

#9：viewの作成

C:\xampp\htdocs\basic-task\resources\views フォルダ内に、
『tasks.blade.php』という名前で、手動で新規ファイルを作ります。今回、極シンプルなアプリなので、viewはトップページの1ページのみです。
ひとまず、新規追加フォーム箇所を作成して表示させてみます。
コードは以下の通りです。

```basic-task\resources\views\tasks.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>Basic Tasks</title>
 <link type="text/css" rel="stylesheet" href="{{ mix('css/app.css') }}">
</head>
<body>
 <div class="container">
   <h3 class="my-3">タスク管理ツール</h3>
   <div class="card mb-3">
     <div class="card-header">タスク新規追加</div>
     <div class="card-body">
       <form method="POST" action="{{ url('/task') }}">
         @csrf
         <div class="form-group">
           <input type="text" name="name" class="form-control">
           @if ($errors->has('name'))
           <p class="text-danger">{{ $errors->first('name') }}</p>
           @endif
           <button type="submit" class="btn btn-outline-info mt-2"><i class="fas fa-plus fa-lg mr-2"></i>追加</button>
         </div>
       </form>
     </div>
   </div>
 </div>
</body>
</html>
```

ルートファイル（C:\xampp\htdocs\basic-task\routes\web.php）のgetリクエスト部分を、tasks.blade.php ファイルが表示されるように、以下の通り更新します。

```basic-task\routes\web.php
Route::get('/', function () {
   return view('tasks');
});
```

ブラウザを更新すると、以下のように表示されるはずです。

<img width="500" alt="pic_4" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235741/a43c60e5-1710-e7f9-1701-c4df6629528a.png">

tasks.blade.php ファイルのコードに関して何点か説明します。（HTMLの書き方や、Bootstrapの書き方等は説明致しませんので、分からない方はご自身で調べてみてください。）
10行目辺りの以下コードは、コンパイルしたBootstrapとFont Awesomeを読み込みます。

```
<link type="text/css" rel="stylesheet" href="{{ mix('css/app.css') }}">
```

上記はmix関数を使っていますが、以下のような書き方で、asset関数を使った書き方もできます。

```
<link type="text/css" rel="stylesheet" href="{{ asset('css/app.css') }}">
```

フォーム部分について説明します。Laravelでフォームを作成する場合は、必ずformタグの下に『@csrf』をつけてください。CSRF対策の為です。これを書かないと、419｜Page Expired エラー等が起きる可能性があります。

```
<form method="POST" action="{{ url('/task') }}">
@csrf
```

以下の部分はエラー変数を使っています。Laravelでは、エラーが出ると直前のページへとリダイレクトされます。その為、その時に（今回の場合だと、submitしたら '/task' に飛ばすので、その直前のページ、つまりトップページです。）定義済みエラー変数を設定することができます。

```
@if ($errors->has('name'))
<p class="text-danger">{{ $errors->first('name') }}</p>
@endif
```

\<p\>タグ部分を、@if ($errors->has('name')) ～ @endif で囲わずに、『\<p class="text-danger">{{ $errors->first('name') }}\</p\>』の部分だけでも動作します。しかし、このエラー変数はどこでも使用可能な為、ifで囲わないとブラウザからソースを表示したときに、\<p\>タグが表示されます。（以下画像の赤枠部分です。）そこで、エラーが出た時だけ表示させるようにするには、ifで囲みます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235741/f33a69e5-fe27-ca13-0c28-ea907823d423.png)

#10：ルートの更新

まずはpostリクエストのルート設定を更新します。コードは以下の通りです。

```
Route::post('/task', function (Request $request) {
   request()->validate(
       [
           'name' => 'required|unique:tasks|min:3|max:255'
       ],
       [
           'name.required' => 'タスク内容を入力してください。',
           'name.unique' => 'そのタスクは既に追加されています。',
           'name.min' => '3文字以上で入力してください。',
           'name.max' => '255文字以内で入力してください。'
       ]
   );
   $task = new Task();
   $task->name = request('name');
   $task->save();
   return redirect('/');
});
```

request()->validate( ) の箇所は、バリデートをかけている（入力項目をチェックする）部分です。今回は、『入力必須で、ユニークであること、3文字以上であること、255文字以内であること』を設定しています。また、続けて上記のように配列を追加すると、エラーメッセージを好きなようにカスタマイズすることができます。デフォルトのメッセージは、C:\xampp\htdocs\basic-task\resources\lang\ja\validation.php ファイル内に書かれている英語表記のものです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/235741/c21cd207-c1b0-64e8-f435-9b04c98229d7.png)

バリデーションの後は、コードの通りですが、以下の通りになります。
① Taskモデルのインスタンス化
\$task = new Task();
② request()ヘルパー関数を利用して、viewファイルのinputタグの名前がnameというデータを格納
\$task->name = request('name');
③ 最後に保存して、トップページにリダイレクト
\$task->save();
return redirect('/');

次は、getリクエストのルート設定も更新します。コードは以下の通りです。

```
Route::get('/', function () {
   return view('tasks', [
       'tasks' => App\Task::latest()->get()
   ]);
});
```

『トップページにアクセスしたら、'tasks'ページを表示して、'tasks'という変数を最新のものから降順にして、viewに渡しますよ』という意味です。因みに、ここでは'tasks'という変数は、viewファイルの方では『\$tasks』という表記で＄マーク付きになりますので、混同しないようにしましょう。私も最初は混乱しました！ｗｗ

#11： viewを完成させる
getリクエストとpostリクエストのルート設定が終わったら、viewファイルを完成させてしまいます。タスク一覧と、削除ボタンを表示する箇所です。コードは以下の通りです。（全ソースコードはこの後に載せていますので、全体像はそちらでご確認ください。）

```
    <div class="card">
     <div class="card-header">タスク一覧</div>
     <div class="card-body">
       @if (count($tasks) > 0)
       <table class="table table-striped">
         <tbody>
           @foreach ($tasks as $task)
           <tr>
             <td>{{ $task->name }}</td>
             <td>
               <form method="POST" action="{{ url('/task/' . $task->id) }}">
                 @csrf
                 @method('DELETE')
                 <button type="submit" class="btn btn-outline-danger" style="width: 100px;"><i class="far fa-trash-alt"></i> 削除</button>
               </form>
             </td>
           </tr>
           @endforeach
         </tbody>
       </table>
       @endif
     </div>
   </div>
```

上記のコードについて何点か説明します。まず@if (count(\$tasks) > 0) の箇所は、このように書くことで、タスクがあれば表示するというようにしております。@foreach (\$tasks as \$task) 箇所では、ルートで設定したように、最新のものを降順で、且つ@foreachを使うことによって、一つずつ表示するように設定しています。

#12：ルートを完成させる
最後に、削除ボタンを押したときのルート設定をします。コードは以下の通りです。

```
Route::delete('/task/{task}', function (Task $task) {
   $task->delete();
   return redirect('/');
});
```

delete('/task/{task}' の、{task}にはTaskインスタンスのidが入ってきます。また、Taskインスタンスが渡ってくるので、Implicit Bindingでコールバック関数の引数には (Task \$task) を渡してあげます。この際、delete('/task/{task}' の{task} と、(Task \$task) の \$task は同じ名前にしないといけません。

以上で全行程が終了です！お疲れさまでした！
最後に、viewファイル、ルートファイル、マイグレーションファイルのソースコードを以下に載せておりますので、全体像のチェック等、参考にしてください！

```basic-task\resources\views\tasks.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>Basic Tasks</title>
 <!-- <link type="text/css" rel="stylesheet" href="{{ asset('css/app.css') }}"> -->
 <link type="text/css" rel="stylesheet" href="{{ mix('css/app.css') }}">
</head>
<body>
 <div class="container">
   <h3 class="my-3">タスク管理ツール</h3>
   <div class="card mb-3">
     <div class="card-header">タスク新規追加</div>
     <div class="card-body">
       <form method="POST" action="{{ url('/task') }}">
         @csrf
         <div class="form-group">
           <input type="text" name="name" class="form-control">
           @if ($errors->has('name'))
           <p class="text-danger">{{ $errors->first('name') }}</p>
           @endif
           <button type="submit" class="btn btn-outline-info mt-2"><i class="fas fa-plus fa-lg mr-2"></i>追加</button>
         </div>
       </form>
     </div>
   </div>
   <div class="card">
     <div class="card-header">タスク一覧</div>
     <div class="card-body">
       @if (count($tasks) > 0)
       <table class="table table-striped">
         <tbody>
           @foreach ($tasks as $task)
           <tr>
             <td>{{ $task->name }}</td>
             <td>
               <form method="POST" action="{{ url('/task/' . $task->id) }}">
                 @csrf
                 @method('DELETE')
                 <button type="submit" class="btn btn-outline-danger" style="width: 100px;"><i class="far fa-trash-alt"></i> 削除</button>
               </form>
             </td>
           </tr>
           @endforeach
         </tbody>
       </table>
       @endif
     </div>
   </div>
 </div>
</body>
</html>
```

```basic-task\routes\web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Task;

/*
|--------------------------------------------------------------------------
| 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('tasks', [
       'tasks' => App\Task::latest()->get()
   ]);
});

Route::post('/task', function (Request $request) {
   request()->validate(
       [
           'name' => 'required|unique:tasks|min:3|max:255'
       ],
       [
           'name.required' => 'タスク内容を入力してください。',
           'name.unique' => 'そのタスクは既に追加されています。',
           'name.min' => '3文字以上で入力してください。',
           'name.max' => '255文字以内で入力してください。'
       ]
   );
   $task = new Task();
   $task->name = request('name');
   $task->save();
   return redirect('/');
});

Route::delete('/task/{task}', function (Task $task) {
   $task->delete();
   return redirect('/');
});
```

```basic-task\database\migrations\2020_05_19_021913_create_tasks_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateTasksTable extends Migration
{
   /**
    * Run the migrations.
    *
    * @return void
    */
   public function up()
   {
       Schema::create('tasks', function (Blueprint $table) {
           $table->id();
           $table->string('name');
           $table->timestamps();
       });
   }
   /**
    * Reverse the migrations.
    *
    * @return void
    */
   public function down()
   {
       Schema::dropIfExists('tasks');
   }
}
```