Edited at

Laravel 6.0 基本のタスクリスト

Laravel公式で用意されているLaravel 5.2 基本のタスクリストはLaravel5.2とかなり古く、ディレクトリ構成も異なるし、ルーティングファイルの配置も変わってますし、HTMLタグのtrが抜けてたり、idの指定が異なっていたり、インデントが崩れていたり、old関数が抜けてたり、そのままチュートリアル通りに進めてもハマりどころが多く、初心者の挫折ポイントになってました。

そこで今回の記事では、Laravelに入門する方を対象に基本のタスクリストのチュートリアルをLaravel 6.0で行う手順に補足を追加しつつ紹介します。

特に問題なさそうな箇所はそのまま引用して紹介します。


アジェンダ


  • 1. 前準備

  • 2. Laravelをインストール

  • 3. データベースの準備

  • 4. Eloquentモデル

  • 5. Tinker(REPL)

  • 6. ルート定義


    • 6.1. 全タスクをリスト表示するルート

    • 6.2. 新しいタスクを追加するルート

    • 6.3. 既存のタスクを削除するルート



  • 7. レイアウトとビューの作成


    • 7.1. 親レイアウトビューの作成

    • 7.2. 共通エラービューの作成

    • 7.3. 子タスクビューの作成



  • 8. タスク追加機能


    • 8.1. バリデーション

    • 8.2. エラー表示

    • 8.3. タスク作成



  • 9. 既存タスクの表示機能

  • 10. タスク削除機能


    • 10.1 削除ボタンの追加

    • 10.2 タスク削除




作成するもの

Laravelの持つ機能の基本的な部分を試せるように、

完了したいタスク全てを管理できるシンプルなタスクリストを構築してみます。

言い換えれば、典型的なToDoリストのサンプルです。


ソースコード

このプロジェクトの最終の完全なソースコードはGitHubから取得可能です。

https://github.com/ucan-lab/laravel60-quickstart-basic

$ git clone git@github.com:ucan-lab/laravel60-quickstart-basic

$ cd laravel60-quickstart-basic
$ docker-compose up -d --build
$ docker-compose exec app composer install
$ docker-compose exec app cp .env.example .env
$ docker-compose exec app php artisan key:generate
$ docker-compose exec app php artisan migrate

http://127.0.0.1:10080


前準備

Laravelの環境を用意していない人はこちらの記事を元に開発環境を構築してください。

DockerでLaravel開発環境構築

Laravelさえ動作すれば問題ないですのでお好みで環境構築してください。


Laravelインストール

$ git clone git@github.com:ucan-lab/docker-laravel-handson.git laravel60-quickstart-basic

$ cd laravel60-quickstart-basic

記事の通り、環境構築した場合は既にLaravelインストール済みなので、一度Laravel削除してから進めます。

$ rm -rf src

.env を修正します。

COMPOSE_PROJECT_NAME=laravel60-quickstart-basic

DB_NAME=homestead
DB_USER=homestead
DB_PASS=secret
TZ=Asia/Tokyo

Docker環境のビルドとLaravel6.0のインストール

# Docker環境構築

$ docker-compose up -d --build


GitHubのリポジトリを作成する

https://github.com/new

laravel60-quickstart-basic リモートリポジトリを作成します。

リモートリポジトリを作成します。


Gitの初期化

laravel60-quickstart-basicディレクトリ内はDocker環境を構築した際のGit設定が残っているので今回はクリアしてから進めたいと思います。

$ rm -rf .git

$ git init
$ git add .
$ git commit -m "first commit"
$ git remote add origin git@github.com:ucan-lab/laravel60-quickstart-basic.git
$ git push -u origin master


Dockerコンテナ内の作業ディレクトリへ移動

$ docker-compose exec app ash

/work #

以降はDockerのappコンテナ内に入り、 /work ディレクトリで作業します。

Gitコマンドはコンテナ内ではなくホスト側のlaravel60-quickstart-basicディレクトリ内で実行してください。


Laravelインストール

# Laravelプロジェクトのインストール

$ composer create-project --prefer-dist "laravel/laravel=6.0.*" .

# マイグレーション実行
$ php artisan migrate
Migrating: 2014_10_12_000000_create_users_table
Migrated: 2014_10_12_000000_create_users_table (0.08 seconds)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated: 2014_10_12_100000_create_password_resets_table (0.03 seconds)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated: 2019_08_19_000000_create_failed_jobs_table (0.01 seconds)

# Laravelバージョン確認
$ php artisan -V
Laravel Framework 6.0.3

http://127.0.0.1:10080

Laravelインストール、マイグレーションの実行ができ、welcome画面が表示されればokです。

GitコマンドはホストOS側で実行します。


データベースの準備


データベースマイグレーション


最初に全タスクを保持しておくためのデータベーステーブルを定義する、マイグレーション(migration:移行)を使ってみましょう。Laravelのデータベースマイグレーションはスラスラ書ける記述的なPHPコードを用いて、データベーステーブルの構造を定義し修正するための簡単な方法を提供しています。チームメンバーへ個別で用意しているデータベースのコピーへカラムを各自自分で追加するように伝える代わりに、あなたがソース管理にPushしたマイグレーションを実行してもらえます。

では、全タスクを保持するデータベーステーブルを構築しましょう。Artisan CLIは様々なクラスの生成に利用でき、Laravelプロジェクトを構築するためにたくさんタイプする手間を省いてくれます。今回はtasksテーブルのために、新しいデータベースマイグレーションをmake:migrationコマンドを使って生成します。


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


マイグレーションはプロジェクトのdatabase/migrationsディレクトリの中に設置されます。お分かりでしょうが、make:migrationコマンドは、マイグレーションファイルへ自動増分IDとタイムスタンプの追加を始めに定義しています。このファイルを編集し、タスクの名前を保存するstringカラムを追加しましょう。


<?php

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

class CreateTasksTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/

public function up()
{
Schema::create('tasks', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->timestamps();
});
}

/**
* Reverse the migrations.
*
* @return void
*/

public function down()
{
Schema::dropIfExists('tasks');
}
}


マイグレーションを実行するには、migrate Artisanコマンドを使います。


$ php artisan migrate


このコマンドは全データベーステーブルを生成します。お好きなクライアントを使用し、データベーステーブルを調べてもらえば、マイグレーションで定義したカラムを含んだ新しいtasksテーブルを見つけることができるでしょう。次に、タスクを表すEloquent ORMモデルを定義しましょう!



Eloquentモデル


EloquentはLaravelのデフォルトORM(object-relational mapper)です。Eloquentは明確に定義された「モデル」を用いることで、苦労せずにデータベースへのデータ保存/取得を行わせてくれます。通常各Eloquentモデルは、一つのデータベーステーブルに対応します。

では、作ったばかりのtasksデータベーステーブルに対応するTaskモデルを定義してみましょう。このモデルを生成するために、再度Artisanコマンドを使用します。この場合はmake:modelコマンドを使用します。


$ php artisan make:model Task


このモデルはアプリケーションのappディレクトリに設置されます。デフォルトではこのクラスは空です。データベーステーブルはモデルの複数形の名前だと想定されているため、Eloquentモデルがどのテーブルに対応するかを明確に宣言する必要はありません。ですから、この場合Taskモデルはtasksデータベーステーブルと対応していると想定しています。



  • Eloquentモデルクラス


    • 各単語の最初の文字を大文字にした単数形

    • パスカルケース

    • 例: Book



  • データベーステーブル


    • 各単語をアンダースコアで区切った複数形

    • スネークケース

    • 例: books



モデルクラス
テーブルスキーマ

Article
articles

LineItem
line_items

Deer
deers

Mouse
mice

Person
people


空のモデルは次のようになっています。


<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Task extends Model
{
//
}


Tinker(REPL


全てのLaravelアプリケーションには、PsySHパッケージによるREPLである、Tinkerが含まれています。Tinkerにより、Laravel全体のEloquent ORM、ジョブ、イベントなどをコマンドラインから操作できます。Tinker環境に入るには、tinker Artisanコマンドを実行します。


$ php artisan tinker

Psy Shell v0.9.9 (PHP 7.3.8 — cli) by Justin Hileman
>>>


Eloquentで遊ぶ

use App\Task;

// タスク1 登録
$task1 = new Task();
$task1->name = 'task1';
$task1->save();

// タスク2 登録
$task2 = new Task();
$task2->name = 'task2';
$task2->save();

// タスク3 登録
$task3 = new Task();
$task3->name = 'task3';
$task3->save();

// 全件取得
Task::all();

// IDを指定して1件取得
Task::find(3);
// 戻り値がCollection型で返る
Task::where('name', 'task2')->get();
// 戻り値がTask型で返る
Task::where('name', 'task2')->first();

// 更新
$task = Task::find(1);
$task->name = 'update task1';
$task->save();
Task::find(1);

// 削除
$task = Task::find(1);
$task->delete();
Task::all();

Task::destroy(2);
Task::all();

Task::where('name', 'task3')->delete();
Task::all();


ルート定義

Laravelの全ルートは、routesディレクトリ下に設置されている、ルートファイルで定義されます。

これらのファイルはフレームワークにより、自動的に読み込まれます。


ルートのスタブを定義

定義されたルートはwebミドルウェアグループにアサインされ、セッション状態やCSRF保護などの機能が提供されます。

それでは、アプリケーションにいくつかルートを追加します。

ルートはユーザがページにアクセスするために指定するURLを実行するコントローラーか無名関数を指定するために使用します。

Webインターフェイスのルートを定義します。

このアプリケーションでは、最低3つのルートが必要になることがわかっています。


  • 全タスクをリスト表示するルート

  • 新しいタスクを追加するルート

  • 既存のタスクを削除するルート

では、routes/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!
|
*/

use App\Task;
use Illuminate\Http\Request;

/**
* タスクダッシュボード表示
*/

Route::get('/', function () {
//
});

/**
* 新タスク追加
*/

Route::post('/task', function (Request $request) {
//
});

/**
* タスク削除
*/

Route::delete('/task/{task}', function (Task $task) {
//
});


ビューの表示


次に/ルートを完成させましょう。このルートでは、新しいタスクを追加するフォームを含み、同時に現在の全タスクをリストするHTMLテンプレートを表示しましょう。

Laravelの全HTMLテンプレートはresources/viewsディレクトリに設置されます。ルートからこれらのテンプレートの一つを返すためにviewヘルパが使えます。


Route::get('/', function () {

return view('tasks');
});


view関数に渡したtasksは、resources/views/tasks.blade.phpテンプレートに対応するビューオブジェクトインスタンを生成します。もちろん、このビューは実際に定義する必要がありますので、早速とりかかりましょう!



レイアウトとビューの作成


このアプリケーションは新しいタスクを追加するためのフォームを含み、同時に現在のタスクをリストするビューを一つだけ持ちます。ビューを想像するために役立つように、基本的なBootstrapのCSSスタイルを適用した、最終段階のアプリケーションのスナップショットをご覧ください。



レイアウト定義


ほとんど全てのアプリケーションでは同じレイアウトをページに渡り共有します。たとえばこのアプリケーションは全ページ(一つ以上のページが存在する場合)で表示する、典型的なトップナビバーがあります。LaravelはBladeテンプレートを使い、こうしたページ間共通のフューチャーを簡単に共有できるようになっています。

前に説明したように、Laravelの全ビューはresources/viewsに設置されます。ですから新しいレイアウトビューもresources/views/layouts/app.blade.phpとして定義します。.blade.php拡張子はビューを表示するときにBladeテンプレートエンジンを使用することをフレームワークへ指示します。もちろん、Laravelでも普通のPHPテンプレートが使用できます。しかし、Bladeなら簡潔できれいなテンプレートを書くための便利なショートカットが利用できます。

app.blade.phpビューは以下のような構成になるでしょう。


$ mkdir resources/views/layouts

$ touch resources/views/layouts/app.blade.php


resources/views/layouts/app.blade.php

<!DOCTYPE html>

<html lang="ja">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Laravel Quickstart - Basic</title>
<!-- Fonts -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.4.0/css/font-awesome.min.css" rel='stylesheet' type='text/css'>
<link href="https://fonts.googleapis.com/css?family=Lato:100,300,400,700" rel='stylesheet' type='text/css'>
<!-- Styles -->
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">
<style>
body {
font-family: 'Lato';
}

.fa-btn {
margin-right: 6px;
}
</style>
</head>
<body id="app-layout">

<nav class="navbar navbar-default">
<div class="container">
<div class="navbar-header">
<!-- Branding Image -->
<a class="navbar-brand" href="{{ url('/') }}">
タスクリスト
</a>
</div>
</div>
</nav>

@yield('content')

<!-- JavaScripts -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
</body>
</html>


レイアウトの@yield('content')の部分に注目です。これはレイアウトを拡張する全部の子ページが、自身のコンテンツを親へ注入できる場所を指定するための特別なBladeディレクティブ(指定子)です。



共通エラーメッセージビューの定義

$ mkdir resources/views/common

$ touch resources/views/common/errors.blade.php


resources/views/common/errors.blade.php

@if (count($errors) > 0)

<!-- Form Error List -->
<div class="alert alert-danger">
<strong>おや? 何かがおかしいようです!</strong>

<br><br>

<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif


子ビューの定義


次に、新しいタスクを作成するためのフォームと、存在する全タスクを同時に表示するビューを定義する必要があります。resources/views/tasks.blade.phpを定義しましょう。


$ touch resources/views/tasks.blade.php


resources/views/tasks.blade.php

@extends('layouts.app')

@section('content')
<div class="container">
<div class="col-sm-offset-2 col-sm-8">
<div class="panel panel-default">
<div class="panel-heading">
新しいタスク
</div>

<div class="panel-body">
<!-- バリデーションエラーの表示 -->
@include('common.errors')

<!-- 新タスクフォーム -->
<form action="{{ url('task')}}" method="POST" class="form-horizontal">
@csrf

<!-- タスク名 -->
<div class="form-group">
<label for="task-name" class="col-sm-3 control-label">タスク</label>

<div class="col-sm-6">
<input type="text" name="name" id="task-name" class="form-control" value="{{ old('task') }}">
</div>
</div>

<!-- タスク追加ボタン -->
<div class="form-group">
<div class="col-sm-offset-3 col-sm-6">
<button type="submit" class="btn btn-default">
<i class="fa fa-btn fa-plus"></i> タスク追加
</button>
</div>
</div>
</form>
</div>
</div>

<!-- TODO: 現在のタスク -->
</div>
</div>
@endsection

http://127.0.0.1:10080


簡単な説明


先に進む前に、このテンプレートについて多少説明しましょう。最初に@extendsディレクティブにより、resources/views/layouts/app.blade.phpに定義したレイアウトを使用することをBladeに指示しています。@section('content')から@endsectionの間のコンテンツが、app.blade.phpレイアウトの中の@yield('content')ディレクティブの場所に挿入されます。

@include('common.errors')ディレクティブはresources/views/common/errors.blade.phpに設置されているテンプレートをロードします。このテンプレートはまだ定義していませんが、すぐに行います。



タスク追加


バリデーション


これでビューにフォームが用意できましたので、フォームの入力の正当性を確認し(バリデーション)、新しいタスクを作成するPOST /taskルートのコードをroutes/web.phpへ追加しましょう。最初に、入力のバリデーションです。

このフォームでは、nameフィールドの入力が必須で、内容が255文字以下であることを確認しましょう。バリデーションに失敗したら、ユーザーを/のURLへリダイレクトし、同時に以前の入力とエラーをセッションへフラッシュデータとして保存します。フラッシュデータとしてセッションに入力を保存することで、バリデーションエラー時にユーザの入力を再表示できるようになります。


Route::post('/task', function (Request $request) {

$validator = Validator::make($request->all(), [
'name' => 'required|max:255',
]);

if ($validator->fails()) {
return redirect('/')
->withInput()
->withErrors($validator);
}

// タスク作成処理…
});


$errors変数


一休みして、この例の->withErrors($validator)の部分について解説しましょう。->withErrors($validator)の呼び出しは、指定されたバリデター(validator)インスタンスのエラーをフラッシュデータとしてセッションへ保存し、ビューの中で$errors変数としてアクセスできるようにしてくれます。

フォームのバリデーションエラーを表示するために@include('common.errors')ディレクティブをビューで使用したことを思い出してください。common.errorsによりバリデーションエラーを同じ形式で、全ページに渡り簡単に表示できるようにしています。このビューの内容を定義しましょう。

注意: $errors変数は全てのLaravelビューの中で参照できます。バリデーションエラーが存在しない場合は、ViewErrorBagの空のインスタンスです。



タスク作成


これで入力のバリデーションは処理できました。新しいタスクを実際に作成するためにルート処理の定義を続けましょう。新しくタスクを生成したら、ユーザを/のURLへリダイレクトします。タスクを作成するには、新しいEloquentモデルに対しプロパティーを生成し、値を設定した後に、saveメソッドを使用します。


Route::post('/task', function (Request $request) {

$validator = Validator::make($request->all(), [
'name' => 'required|max:255',
]);

if ($validator->fails()) {
return redirect('/')
->withInput()
->withErrors($validator);
}

$task = new Task();
$task->name = $request->name;
$task->save();

return redirect('/');
});

http://127.0.0.1:10080

タスク追加してみましょう。

タスク追加してもタスクの一覧が表示されないのは現時点では正しい動作です。

データベースに値が登録されているかは、tinkerを使って確認します。

$ php artisan tinker


App\Task::all();
=> Illuminate\Database\Eloquent\Collection {#3012
all: [
App\Task {#3013
id: 4,
name: "ゆうきゃん",
created_at: "2019-09-10 15:16:03",
updated_at: "2019-09-10 15:16:03",
},
],
}


いいですね!これでタスクを作成できるようになりました。次に存在する全タスクをリストするビューを追加していきましょう。



既存タスクの表示


最初に、/ルートを編集し、既存の全タスクをビューに渡しましょう。view関数は第2引数に、ビューで使用するデータを配列で受け付けます。配列のキーはビューの中で変数となります。


Route::get('/', function () {

$tasks = Task::orderBy('created_at', 'asc')->get();

return view('tasks', [
'tasks' => $tasks
]);
});


データを渡したら、tasks.blade.phpビューの中でタスクを反復処理し、テーブルとして表示します。とても早く通常のPHPコードにコンパイルできる@foreach Blade構造文で簡単にループが記述できます。



resources/views/tasks.blade.php

<!-- TODO: 現在のタスク -->

ここのコードを下記のコードに差し替えます。

(インデントは揃えて貼り付けましょう)

<!-- 現在のタスク -->

@if (count($tasks) > 0)
<div class="panel panel-default">
<div class="panel-heading">
現在のタスク
</div>
<div class="panel-body">
<table class="table table-striped task-table">
<!-- テーブルヘッダ -->
<thead>
<tr>
<th>タスク</th>
<th>&nbsp;</th>
</tr>
</thead>
<!-- テーブル本体 -->
<tbody>
@foreach ($tasks as $task)
<tr>
<td class="table-text">
<div>{{ $task->name }}</div>
</td>
<!-- TODO: 削除ボタン -->
<td>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@endif

http://127.0.0.1:10080


タスクアプリケーションは、もうほとんど完成です。しかし、終了した既存タスクを削除する手段がありません。次に実装しましょう。



タスク削除


削除ボタンの追加


コード中、削除ボタンを設置する場所に"TODO"を残してあります。では、tasks.blade.phpビューのタスクリストの各行に、削除ボタンを追加しましょう。小さなボタンひとつのフォームをリストの各タスクごとに作成します。ボタンがクリックされると、DELETE /taskリクエストがアプリケーションに送信されます。



resources/views/tasks.blade.php

<!-- TODO: 削除ボタン -->

ここのコードを下記のコードに差し替えます。

(インデントは揃えて貼り付けましょう)

<!-- 削除ボタン -->

<td>
<form action="{{ url('task/' . $task->id) }}" method="POST">
@csrf
@method('DELETE')

<button type="submit" class="btn btn-danger">
<i class="fa fa-btn fa-trash"></i> 削除
</button>
</form>
</td>


見せかけのメソッドの説明


削除ボタンフォームのmethodPOSTを使用しているのにかかわらず、定義しているルートはRoute::deleteである点に注目です。HTMLフォームはGETPOST HTTP動詞のみを許しています。そのため、フォームのDELETEリクエストを見せかける手段が必要になります。


フォームの中で@method('DELETE')関数の結果を出力することにより、DELETEリクエストへ見せかけることができます。Laravelはこれを認識し、実際のHTTPリクエストメソッドをオーバーライドします。生成されるフィールドは次の内容です。

<input type="hidden" name="_method" value="DELETE">


タスク削除


最後に、指定したタスクを実際に削除するロジックをルートへ追加しましょう。{task}ルートパラメータに対応するTaskモデルを自動的に取得するため、暗黙のモデル結合が使えます。

ルートのコールバックの中で、レコードを削除するためにdeleteメソッドを使いましょう。レコードが削除された後は、ユーザを/ URLへリダイレクトします。



routes/web.php

Route::delete('/task/{task}', function (Task $task) {

$task->delete();

return redirect('/');
});


さいごに

以上でLaravel6.0の基本のタスクリストチュートリアルは完了です。

もしうまくいかなかった場合は下記のコメント欄より気軽にご質問ください。

またうまくいった場合も完成報告もらえると嬉しいです☺️