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から取得可能です。
$ 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
前準備
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のリポジトリを作成する
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
Laravelインストール、マイグレーションの実行ができ、welcome画面が表示されればokです。
SQLSTATE[HY000] [2054] The server requested authentication method unknownto the client
MySQL8系のデータベースを使っててこのエラーが発生する場合は、MySQL8.0 認証方式を変更する(Laravel5)の記事を参照ください。
データベースの準備
データベースマイグレーション
最初に全タスクを保持しておくためのデータベーステーブルを定義する、マイグレーション(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) {
//
});
-
http://127.0.0.1:10080
- この状態だと真っ白な画面が表示されます。
ビューの表示
次に
/
ルートを完成させましょう。このルートでは、新しいタスクを追加するフォームを含み、同時に現在の全タスクをリストする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
簡単な説明
先に進む前に、このテンプレートについて多少説明しましょう。最初に
@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> </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
タスクアプリケーションは、もうほとんど完成です。しかし、終了した既存タスクを削除する手段がありません。次に実装しましょう。
タスク削除
削除ボタンの追加
コード中、削除ボタンを設置する場所に"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>
見せかけのメソッドの説明
削除ボタンフォームの
method
がPOST
を使用しているのにかかわらず、定義しているルートはRoute::delete
である点に注目です。HTMLフォームはGET
とPOST
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の基本のタスクリストチュートリアルは完了です。
もしうまくいかなかった場合は下記のコメント欄より気軽にご質問ください。
またうまくいった場合も完成報告もらえると嬉しいです☺️