ToDoアプリのタスク一覧表示機能を作る
第4章では、ToDoアプリのタスク一覧表示機能を作成していきます。
今回の目標物
ここで登場するのは、「右側のパネルのタスク一覧」です。
タスク一覧ページの右パネルがタスク一覧パネルをになっています。
今回はこちらを作成していきます。
①マイグレーションとモデルクラス
Migrationファイルの不整合の解消
先に「Migrationファイルの不整合」が起きた時の解消法をお伝えしておきます。
例えば、Gitなどを使用していると、Migrationテーブルに載っているMigrationファイル名(前の章で自分で作成したもの)の日付部分が、実物のMigrationファイル(GitからCloneしてきたもの)と異なってしまうことがあります。
不整合が発生する場合に、Migrateを実行すると実行済みの内容が同じものまで実行されてしまいます。当然、既にテーブルなどが存在するのでエラーが発生します。
この不整合を解消するためには Migration と Seeder の再実行を行えばよいのです。
「Migrationファイルの不整合」の場合には下記の手順を実施してみてください。
- DatabaseSeeder.phpを編集する
class DatabaseSeeder extends Seeder
{
public function run()
{
/* call seeder list */
$this->call(FoldersTableSeeder::class);
// 他のシーダーも必要に応じて追加
}
}
- Migrationをやり直して(全てのレコードが破棄される)全てのシーダーを挿入する
root~# php artisan migrate:fresh --seed
- 全てのシーダーのみ動かしたい場合は下記を実行してください。
root~# php artisan db:seed
- 個別のシーダーのみ動かしたい場合は下記を実行してください。
root~# php artisan db:seed --class=<seederのクラス名>
下記の記事が非常に参考になりますのでご案内しておきます。
タスクテーブルのテーブル定義を確認する
マイグレーションファイルを作成する前にテーブル定義を確認しておきましょう。
タスクテーブル
カラム論理名 | カラム物理名 | 型 | 型の意味 |
---|---|---|---|
ID | id | INTEGER | 連番(自動採番) |
フォルダID | folder_id | INTEGER | 整数 |
タイトル | title | VARCHAR(100) | 100文字までの文字列 |
状態 | status | INTEGER | 数値 |
期限日 | due_date | DATE | 日付 |
作成日 | created_at | TIMESTAMP | 日付と時刻 |
更新日 | updated_at | TIMESTAMP | 日付と時刻 |
マイグレーションファイルの作成と実行
それではマイグレーションファイルの作成と実行をしていきましょう。
まずは、マイグレーションファイルを作成します。
# tasksテーブルのマイグレーションファイルを作成する
root~# php artisan make:migration create_tasks_table --create=tasks
database/migrations
ディレクトリに新しくxxxx_xx_xx_xxxx_create_tasks_table.php
というような名前のマイグレーションファイルの雛形が作成されたはずです。
次に確認したフォルダーテーブルの定義に沿ってマイグレーションファイルを編集します。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateTasksTable extends Migration
{
// テーブル名を明示的に指定する
protected $table = 'tasks';
/**
* Run the migrations.
* tasksテーブルと各列を作成する
*
* @return void
*/
public function up()
{
Schema::create('tasks', function (Blueprint $table) {
$table->increments('id');
$table->integer('folder_id')->unsigned();
$table->string('title', 100);
$table->date('due_date');
$table->integer('status')->default(1);
$table->timestamps();
$table->foreign('folder_id')->references('id')->on('folders');
});
}
[中略]
}
Y_m_d_His_create_tasks_table.php の解説
タスクテーブルの定義に沿って下記のようにそれぞれ書き変えています。
コードの意味はコメントで説明しているので確認してみてください。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateTasksTable extends Migration
{
// テーブル名を明示的に指定する
protected $table = 'tasks';
/**
* Run the migrations.
* tasksテーブルと各列を作成する
*
* @return void
*/
public function up()
{
/**
* tasks Table create
* column1 -> カラム名:id, 型:INTEGER, オプション:AI
* column2 -> カラム名:folder_id, 型:INTEGER
* column3 -> カラム名:title, 型:VARCHAR(100)
* column4 -> カラム名:due_date, 型:DATE
* column5 -> カラム名:status, 型:INTEGER, デフォルト:1(未着手)
* column6 -> カラム名:created_at, 型:TIMESTAMP
* column7 -> カラム名:updated_at, 型:TIMESTAMP
*/
Schema::create('tasks', function (Blueprint $table) {
// UNSIGNED INTEGER(主キー)の同等の列を自動インクリメントする
$table->increments('id');
// INTEGER相当の列をUNSIGNED(MySQL)として追加する。
$table->integer('folder_id')->unsigned();
// オプションの長さのVARCHAR相当の列を追加する
$table->string('title', 100);
// DATEに相当する列を追加する
$table->date('due_date');
// INTEGER相当の列を追加してデフォルトの値に「1」をセットする
$table->integer('status')->default(1);
// created_atとupdated_atのTIMESTAMPに相当する列を追加する
$table->timestamps();
/* 外部キーを'folder_id'列に設定する(実在するIDの値以外はDBに入らないようにする) */
// foreign():外部キーを設定する列を指定する
// ->references():参照する列名を指定する
// ->on():参照するテーブル名を指定する
$table->foreign('folder_id')->references('id')->on('folders');
});
}
/**
* Reverse the migrations.
* tasksテーブルを削除する
*
* @return void
*/
public function down()
{
// tasksテーブルを削除する
Schema::dropIfExists('tasks');
}
}
それではマイグレーションを実行しましょう。
root~# php artisan migrate
ブラウザで http://localhost:8080 にアクセスしてテーブルを確認してください。
tasks
テーブルが作成されていればOKです。
モデルクラスの作成
次に、タスクテーブルに対応するモデルクラスを作成します。
# モデルを作成する
root~# php artisan make:model Task
下記のように タスクモデル が作成されたはずです。
前回と同様にデフォルトのままでOKです。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Task extends Model
{
use HasFactory;
}
②テストデータの作成と確認
シーダーを作成する
tasksテーブルのテストデータを挿入するためにシーダーを作成します。
# シーダーを作成する
root~# php artisan make:seeder TasksTableSeeder
database/seeds/TasksTableSeeder.php
を以下の通り記述してください。
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
class TasksTableSeeder extends Seeder
{
/**
* Run the database seeds.
* tasksTable用テストデータ
*
* @return void
*/
public function run()
{
foreach (range(1, 3) as $num) {
DB::table('tasks')->insert([
'folder_id' => 1,
'title' => "サンプルタスク {$num}",
'status' => $num,
'due_date' => Carbon::now()->addDay($num),
'created_at' => Carbon::now(),
'updated_at' => Carbon::now(),
]);
}
}
}
TasksTableSeeder.php の解説
run
メソッドの中にデータを挿入するコードを記述します。
それぞれのコードの解説はコメントに記述しているので確認してください。
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
class TasksTableSeeder extends Seeder
{
/**
* Run the database seeds.
* tasksTable用テストデータ
*
* @return void
*/
public function run()
{
// ID = 1 のフォルダに対して3つのタスクを登録(挿入)する
foreach (range(1, 3) as $num) {
// tasksテーブルにアクセスして行を挿入する
DB::table('tasks')->insert([
// folder_idに 1 を代入する
'folder_id' => 1,
// titleに "サンプルタスク {$num}" を代入する
'title' => "サンプルタスク {$num}",
// status に $num を代入する
'status' => $num,
// 期限日に 現在日時 + addDay($num) の期日を設定して代入する
// addDay():日単位での加算減算をする関数
'due_date' => Carbon::now()->addDay($num),
// 現在の日時を取得してcreated_atに作成日として代入する
'created_at' => Carbon::now(),
// 現在の日時を取得してupdated_atに更新日として代入する
'updated_at' => Carbon::now(),
]);
}
}
}
Seederを実行する前にすべてのシーダーを実行できるように DatabaseSeeder.php
に下記を追加しておきましょう。
// runメソッド内に追加する
$this->call(TasksTableSeeder::class);
それでは Seeder を実行しましょう。
# シーダーを実行する
root~# php artisan db:seed --class=TasksTableSeeder
上記のコマンドがうまくいかない場合は composer dump-autoload
コマンドを打ってから実行し直してください。
ブラウザで http://localhost:8080 にアクセスしてテーブルを確認してください。
上記のようにデーターが挿入されていればOKです。
tinker を使ってみよう
ここでも前回に引き続き tinker
を使ってみましょう。
ここではテストデータの確認を tinker で行っていきます。
下記の手順を実施してください。
- tinkerを起動する
root~# php artisan tinker
- フォルダークラスでの ID に合致するデータを取得してみる
> $folder = \App\Models\Folder::find(1);
= App\Models\Folder {#6565
id: 1,
title: "プライベート",
created_at: "2023-09-11 05:25:51",
updated_at: "2023-09-11 05:25:51",
}
ここでは、フォルダーテーブルの 1行目を find
関数 を使って取得しています。
1行目は id
が 1
で title
が プライベート
でしたので正しく取得できていることがわかります。なお、find
関数の引数を違う行数番号に変えると異なるレコードを取得できます。
- タスクの データ も確認してみましょう。
ここでは、取得条件を指定するメソッド where
を使って、フォルダIDカラムの値が先ほど取得したフォルダのID(=1)に合致するタスクを取得しています。
そのため、$folder = \App\Models\Folder::find(1);
を実施した直後に下記を実行しないと値を取得できない点に注意してください。
> \App\Models\Task::where('folder_id', $folder->id)->get();
= Illuminate\Database\Eloquent\Collection {#6971
all: [
App\Models\Task {#6234
id: 1,
folder_id: 1,
title: "サンプルタスク 1",
due_date: "2023-09-12",
status: 1,
created_at: "2023-09-11 06:35:43",
updated_at: "2023-09-11 06:35:43",
},
App\Models\Task {#7187
id: 2,
folder_id: 1,
title: "サンプルタスク 2",
due_date: "2023-09-13",
status: 2,
created_at: "2023-09-11 06:35:43",
updated_at: "2023-09-11 06:35:43",
},
App\Models\Task {#6225
id: 3,
folder_id: 1,
title: "サンプルタスク 3",
due_date: "2023-09-14",
status: 3,
created_at: "2023-09-11 06:35:43",
updated_at: "2023-09-11 06:35:43",
},
],
}
# tinkerを終了する
> quit
先ほどシーダーで挿入したtasksテーブルのデータが取得できていればOKです。
③コントローラーの修正
それではコントローラーをTaskモデルに合わせて修正していきましょう。
TaskController.php
を下記のように修正してください。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Folder;
use App\Models\Task;
class TaskController extends Controller
{
/**
* 【タスク一覧ページの表示機能】
*
* GET /folders/{id}/tasks
* @param int $id
* @return \Illuminate\View\View
*/
public function index(int $id)
{
$folders = Folder::all();
$folder = Folder::find($id);
$tasks = Task::where('folder_id', $folder->id)->get();
return view('tasks/index', [
'folders' => $folders,
'folder_id' => $folder->id,
'tasks' => $tasks
]);
}
}
TaskController.php の解説
それぞれのコードの解説はコメントに記述しているので確認してください。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Folder;
// タスククラスを名前空間でインポートする
use App\Models\Task;
class TaskController extends Controller
{
/**
* 【タスク一覧ページの表示機能】
*
* GET /folders/{id}/tasks
* @param int $id
* @return \Illuminate\View\View
*/
public function index(int $id)
{
/* Folderモデルの全てのデータをDBから取得する */
// all():全てのデータを取得する関数
$folders = Folder::all();
/* ユーザーによって選択されたフォルダを取得する */
// find():一行分のデータを取得する関数
$folder = Folder::find($id);
/* ユーザーによって選択されたフォルダに紐づくタスクを取得する */
// where(カラム名,カラムに対して比較する値):特定の条件を指定する関数 ※一致する場合の条件 `'='` を省略形で記述しています
// get():値を取得する関数(この場合はwhere関数で生成されたSQL文を発行して値を取得する)
$tasks = Task::where('folder_id', $folder->id)->get();
/* DBから取得した情報をViewに渡す */
// view('遷移先のbladeファイル名', [連想配列:渡したい変数についての情報]);
// 連想配列:['キー(テンプレート側で参照する際の変数名)' => '渡したい変数']
return view('tasks/index', [
'folders' => $folders,
'folder_id' => $folder->id,
'tasks' => $tasks
]);
}
}
それでは先ほどのコードを確認していきましょう。
findメソッド
findメソッド
まず find
は、プライマリキーのカラムを条件として一行分のデータを取得してきます。上の例でいうと、アクセス直後はフォルダテーブルから ID カラム(プライマリキー)が 1
である行のデータを検索して返します。
Folder::find($id);
クエリビルダ
whereメソッド
次に where
メソッドに注目してください。先ほど少し触れましたが、Laravel が提供するクエリビルダの機能を使っています。
クエリとは、SQL
クエリのことです。ビルダとは構築者という意味です。クエリビルダの機能によって SQL
を書かなくても PHP 風な記述でデータ操作を表現できます。
SQL
は裏側で生成されデータベースに発行されます。
Tasks::where('folder_id', $folder->id)->get();
where
メソッドはデータの取得条件を表し、SQL
の WHERE
句にあたります。第一引数がカラム名で第二引数が比較する値です。ただ厳密にいうと、上記の記述は以下の記述の省略形です。つまり、引数を三つ与えるとイコール以外の比較も可能だということです。省略しない場合は下記のようになります。
Tasks::where('folder_id', '=', $folder->id)->get();
注意していただきたいのは最後の get
メソッドです。この get
メソッドで構築された SQL
をデータベースに発行して結果を取得しています。
Tasks::where('folder_id', '=', $folder->id)->get();
そのため、get
メソッドを付けてない段階では SQL
文を作っているだけなので値は取れません。SQL
文を作っただけの状態です。
get
メソッドを忘れて「値が取れないぞ??」となりがちなので気をつけましょう。この辺は本家のPHPが execute
しないと SQL
文が実行されないのと似ていますね。
(少し突っ込んでいうと where
からは QueryBuilder
クラスのインスタンスが返ってきます。QueryBuilder
の get
メソッドを呼ぶことで結果の詰まった Collection
クラスのインスタンスが返ってきます。)
クエリビルダで SQL
を書かなくてよくなると言ってもこの機能は SQL
の代替ではありません。あくまで SQL
を組み立てる機能ですので、やはり SQL
の知識は必要になります。
また、クエリビルダの利点は SQL
を書かないことではなく(SQL
を書かないのが良いとか悪いという話ではない)、SQL
を PHP コードによって表現することによって条件の部品化や使い回しが効いてプログラムの生産性や可読性が向上することにあります。
tinker で SQL を確認する
tinker
を使って クエリビルダ でどんな SQL
が作られているのか確認してみましょう。
get
メソッドの代わりに toSql
メソッドを呼び出すと SQL
を発行するのではなく、SQL
文を生成して出力することができます。このtoSql
メソッドを使ってどんな SQL
文が作成されているか確認してみましょう。
root~# php artisan tinker
# フォルダ選択($id)がデフォルトの状態(1)に指定する
> $id = 1;
= 1
# 選択されたフォルダのレコードを検索するSQL文を発行する
> $folder = \App\Models\Folder::find($id);
= App\Models\Folder {#6571
id: 1,
title: "プライベート",
created_at: "2023-11-20 01:12:33",
updated_at: "2023-11-20 01:12:33",
}
# タスクを取得するクエリビルダでどのようなSQL文が記述されているか確認する
> $tasks = \App\Models\Task::where('folder_id', $folder->id)->toSql();
= "select * from `tasks` where `folder_id` = ?"
# tinkerを終了する
> quit
これで クエリビルダによって SQL
が作成されていることが確認できました。どのような SQL
文が発行されているか確認したい時にはこのような方法を用いることを覚えておいてください。
④テンプレートの修正
タスククラスに合わせて blade
ファイル を修正していきます。
タスク一覧ページのテンプレートファイル resources/views/tasks/index.blade.php
で <!-- ここにタスクが表示される -->
とコメントが書かれていた箇所に以下のコードを記述してください。
<div class="panel panel-default">
<div class="panel-heading">タスク</div>
<div class="panel-body">
<div class="text-right">
<a href="#" class="btn btn-default btn-block">
タスクを追加する
</a>
</div>
</div>
<table class="table">
<thead>
<tr>
<th>タイトル</th>
<th>状態</th>
<th>期限</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
@foreach($tasks as $task)
<tr>
<td>{{ $task->title }}</td>
<td>
<span class="label">{{ $task->status }}</span>
</td>
<td>{{ $task->due_date }}</td>
<td><a href="#">編集</a></td>
<td><a href="#">削除</a></td>
</tr>
@endforeach
</tbody>
</table>
</div>
index.blade.php の解説
それぞれのコードの解説はコメントに記述しているので確認してください。
<!-- ここにタスクが表示される -->
<div class="panel panel-default">
<div class="panel-heading">タスク</div>
<div class="panel-body">
<div class="text-right">
<a href="#" class="btn btn-default btn-block">
タスクを追加する
</a>
</div>
</div>
<table class="table">
<thead>
<tr>
<th>タイトル</th>
<th>状態</th>
<th>期限</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<!--
* 【task一覧セクション】
* foreach の中でTaskControllerから渡されたデータ $tasks を参照する
* $tasks をループして値を全て表示する
-->
@foreach($tasks as $task)
<tr>
<!-- タスクのタイトルを表示する -->
<td>{{ $task->title }}</td>
<!-- タスクの状態を表示する -->
<td>
<span class="label">{{ $task->status }}</span>
</td>
<!-- タスクの期限を表示する -->
<td>{{ $task->due_date }}</td>
<!-- 編集と削除のリンクを表示する -->
<td><a href="#">編集</a></td>
<td><a href="#">削除</a></td>
</tr>
@endforeach
</tbody>
</table>
</div>
それではここで表示を確認してみましょう。
ブラウザで http://localhost/folders/1/tasks にアクセスしてください。
タスクが3つ表示されていればOKです。
但し、いくつか課題があります。
- 状態が数値で表示されている。
1 →「未着手」、2 →「着手中」、3 →「完了」と表示したい。 - 状態ラベルを色分けできていない。
「未着手」は赤、「着手中」は緑、「完了」は灰色にしたい。 - 日付の形式はハイフン区切りではなくスラッシュ区切りにしたい。
次の項ではモデルクラスのアクセサという機能を使ってこれらの課題を改善します。
⑤Task モデルにアクセサを追加する
Task モデルにアクセサを追加する前にまずは アクセサ とは何かを確認していきましょう。
アクセサとは
アクセサとは、モデルクラスが本来持つデータを加工した値を、さもモデルクラスのプロパティであるかのように参照できる Laravel の機能です。この機能を使用すると少しコードがキレイになります。コードが読みやすいというのは「可読性」と呼ばれ重要なことでもあります。
まずモデルクラスに get○○○Attribute
という決まったフォーマットのメソッドを用意します。例えば、性別の文字列表現を取得するための getGenderTextAttribute
メソッドがあるとします。persons
テーブルに gender
(性別)カラムがあるイメージです。
class Person extends Model
{
public function getGenderTextAttribute()
{
switch($this->attributes['gender']) {
case 1:
return 'male';
case 2;
return 'female';
default:
return '??';
}
}
}
まず $this->attributes['gender']
に注目してください。これで gender
カラムの値を取得しています。実は Laravel ではモデルクラスの属性データ(テーブルで言うところの各カラムの値)はそれぞれがクラスのプロパティで管理されているのではなく、$attributes
という一つのプロパティで配列として管理されています。
前回の章でも $folder->title
とクラスのプロパティのようにタイトルを取得していましたが、これは PHP のマジックメソッド __get()
を利用しています。本当は Folder
クラスには title
というプロパティは存在しません(定義しなかったですよね)。モデルクラスは、存在しないプロパティを参照されると $attributes
からプロパティ名と一致するキーの値が返すように実装されているのです。
さて getGenderTextAttribute
メソッドでは gender
カラムの値に応じて文字列を返していますね。そしてこれをどう使うかというと..
// コントローラーやテンプレートなどで...
$person->gender_text; //=> 'male'
上記のように、ここでもクラスのプロパティかのように getGenderTextAttribute
メソッドの出力値を参照できています。get○○○Attribute
の ○○○ の部分が(擬似的な)プロパティになっていますね。ただしアクセサメソッドはキャメルケース(文字の区切りが大文字)ですがプロパティとして参照するときはスネークケース(文字の区切りがアンダースコア)になります。
ルールが分かってしまえば難しくはないですね。「アクセサを使わずに普通にモデルにメソッドを用意して普通にそれを呼び出しても同じでは?」と考える方もいらっしゃるかもしれません。まぁその通りです。ただアクセサを利用したほうがちょっとコードがキレイになります。公式サイトのトップページに "Love beautiful code? We do too." とあるように、コードをキレイに見せることは Laravel のコンセプトの一つであるようです。
アクセサの使いどころは、モデルが持つ属性データ(テーブルで言うところの各カラムの値)を加工した値を取得したいときです。実際に今回の ToDo アプリでも使ってみましょう。
状態の名前を表示する
まずは状態を数値ではなく文字列で表示する機能を実装します。
モデル
タスクモデルに定数 STATUS
と getStatusLabelAttribute
メソッドを追加してください。
class Task extends Model
{
use HasFactory;
/**
* ステータス(状態)定義
*
*/
const STATUS = [
1 => [ 'label' => '未着手' ],
2 => [ 'label' => '着手中' ],
3 => [ 'label' => '完了' ],
];
/**
* ステータス(状態)ラベルのアクセサメソッド
*
* @return string
*/
public function getStatusLabelAttribute()
{
$status = $this->attributes['status'];
if (!isset(self::STATUS[$status])) {
return '';
}
return self::STATUS[$status]['label'];
}
}
Task.php の解説
それぞれのコードの解説はコメントに記述しているので確認してください。
class Task extends Model
{
use HasFactory;
/**
* ステータス(状態)定義
* const:定数
*/
const STATUS = [
1 => [ 'label' => '未着手' ],
2 => [ 'label' => '着手中' ],
3 => [ 'label' => '完了' ],
];
/**
* ステータス(状態)ラベルのアクセサメソッド
*
* @return string
*/
public function getStatusLabelAttribute()
{
// ステータス(状態)カラムの値を取得する
$status = $this->attributes['status'];
// STATUSに定義されていない場合
if (!isset(self::STATUS[$status])) {
// 空文字を返す
return '';
}
// STATUSの値(['label'])を返す
return self::STATUS[$status]['label'];
}
}
$this->attributes['status']
で状態カラムの値を取得している以外は、getStatusLabelAttribute
メソッドの中身は普通の PHP です。STATUS
配列から状態値をキーに文字列表現を探して返しています。
テンプレート
テンプレートは、{{ $task->status }}
と記述していた箇所を書き換えてください。
+ <span class="label">{{ $task->status_label }}</span>
- <span class="label">{{ $task->status }}</span>
それではここで表示を確認してみましょう。
ブラウザで http://localhost/folders/1/tasks にアクセスしてください。
状態が文字列で表示されていればOKです。
状態を色分けする
次に状態のラベルを色分けする機能を追加します。
状態の文字列表現を表示したのとほとんど同じ要領です。
モデル
STATUS
配列に class
を追加して、getStatusClassAttribute
メソッドを追記します。Task.php
を下記のように書き直してください。
class Task extends Model
{
[中略]
/**
* ステータス(状態)定義
*
*/
const STATUS = [
1 => [ 'label' => '未着手', 'class' => 'label-danger' ],
2 => [ 'label' => '着手中', 'class' => 'label-info' ],
3 => [ 'label' => '完了', 'class' => '' ],
];
[中略]
/**
* 状態を表すHTMLクラスのアクセサメソッド
*
* @return string
*/
public function getStatusClassAttribute()
{
$status = $this->attributes['status'];
if (!isset(self::STATUS[$status])) {
return '';
}
return self::STATUS[$status]['class'];
}
}
Task.php の解説
それぞれのコードの解説はコメントに記述しているので確認してください。
class Task extends Model
{
[中略]
/**
* ステータス(状態)定義
*
*/
const STATUS = [
1 => [ 'label' => '未着手', 'class' => 'label-danger' ],
2 => [ 'label' => '着手中', 'class' => 'label-info' ],
3 => [ 'label' => '完了', 'class' => '' ],
];
[中略]
/**
* 状態を表すHTMLクラスのアクセサメソッド
*
* @return string
*/
public function getStatusClassAttribute()
{
// ステータス(状態)カラムの値を取得する
$status = $this->attributes['status'];
// STATUSに定義されていない場合
if (!isset(self::STATUS[$status])) {
// 空文字を返す
return '';
}
// STATUSの値(['class'])を返す
return self::STATUS[$status]['class'];
}
}
コードの中身は getStatusLabelAttribute
メソッドとほとんど一緒です。
label
の代わりに class
を返しています。
テンプレート
テンプレートでは、ラベルの部分のクラス属性に $task->status_class
を追加します。
+ <span class="label {{ $task->status_class }}">{{ $task->status_label }}</span>
- <span class="label">{{ $task->status_label }}</span>
それではここで表示を確認してみましょう。
ブラウザで http://localhost/folders/1/tasks にアクセスしてください。
状態が色分けされて表示されていればOKです。
日付の表示形式を変更する
最後に日付の表示形式を変更します。データベースに入ったままだと 2023-09-12
というハイフン区切りの形式なので、より一般的な 2023/09/12
というスラッシュ区切りの形式で表示していきます。
モデル
モデルクラスでは、getFormattedDueDateAttribute
メソッドを追加します。
Task.php
に下記を追加してください。
// Carbon ライブラリを名前空間でインポートする
use Carbon\Carbon;
[中略]
/**
* 整形した期限日のアクセサメソッド
*
* @return string
*/
public function getFormattedDueDateAttribute()
{
return Carbon::createFromFormat('Y-m-d', $this->attributes['due_date'])
->format('Y/m/d');
}
Task.php の解説
Carbon
ライブラリを使って期限日の値の形式を変更して返却しています。
それぞれのコードの解説はコメントに記述しているので確認してください。
// Carbon ライブラリを名前空間でインポートする
use Carbon\Carbon;
[中略]
/**
* 整形した期限日のアクセサメソッド
*
* @return string
*/
public function getFormattedDueDateAttribute()
{
/* Carbon ライブラリを使って期限日の値の形式を変更して返す */
// createFromFormat():datetime で指定した文字列を format で指定した書式に沿って解釈した時刻にする関数
// format():書式を指定する関数
return Carbon::createFromFormat('Y-m-d', $this->attributes['due_date'])
->format('Y/m/d');
}
テンプレート
テンプレートの $task->due_date
だった箇所を書き換えます。
+ <td>{{ $task->formatted_due_date }}</td>
- <td>{{ $task->due_date }}</td>
それではここで表示を確認してみましょう。
ブラウザで http://localhost/folders/1/tasks にアクセスしてください。
ブラウザで表示形式が変わっていればOKです。
ここまでの内容でタスク一覧ページは見た目の上ではこの章で作りたい段階は達成できました。
⑥モデルクラスにおけるリレーション(関連付け)
ここまで記述した内部のコードはもう少しキレイにすることができます。そこでもう一つだけ Laravel の強力な機能、「テーブル間の関連性をモデルクラスで表現する方法」を紹介してこの章を終わりたいと思います。
タスクコントローラーではタスクの一覧を以下のコードで取得しました。
$tasks = Task::where('folder_id', $folder->id)->get();
ここで紹介する機能を使うとこのように書き直すことができます。
$tasks = $folder->tasks()->get();
「特定のフォルダに紐づくタスクの一覧を取得する処理」が、ほとんど左から英語で読んだ通りの直感的な表現で記述されています。そのため、こちらの方が可読性は上がります。
それでは実際に実装をしてみましょう。
hasMany
フォルダクラスを編集します。以下の通り tasks
メソッドを追加してください。
class Folder extends Model
{
use HasFactory;
protected $table = 'folders';
/*
* フォルダクラスとタスククラスを関連付けするメソッド
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function tasks()
{
return $this->hasMany('App\Models\Task');
}
}
Folder.php の解説
それぞれのコードの解説はコメントに記述しているので確認してください。
class Folder extends Model
{
use HasFactory;
// テーブル名を明示的に指定する
protected $table = 'folders';
/*
* フォルダクラスとタスククラスを関連付けするメソッド
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function tasks()
{
/* フォルダクラスのタスククラスのリストを取得して返す */
// hasMany():テーブルの関係性を辿ってインスタンスから紐づく情報を取得する関数
// hasMany(モデル名, 関連するテーブルが持つ外部キーカラム名, hasManyが定義された外部キーに紐づけられたカラム)
return $this->hasMany('App\Models\Task');
}
}
index メソッドを編集する
次に、リレーションを使えるようにタスクコントローラーを書き換えてください。
+ $tasks = $folder->tasks()->get();
- $tasks = Task::where('folder_id', $folder->id)->get();
それではここで表示を確認してみましょう。
ブラウザで http://localhost/folders/1/tasks にアクセスしてください。
元の通りに見た目が変化してなければOKです。
まとめ
ToDoアプリのタスク一覧表示機能を作成はこれで一旦は完了です。
今回の内容は、前回の内容に プラスα したようなものです。
復習と同時に新しいこともいくつか覚えられたのではないでしょうか。
特にアクセサや hasMany
などのリレーション(関連付け)は頻繁に使用することになります。
今回はその一例でしかありませんが、理解の助けにはなると思います。
この調子でLaravelを習得してきましょう。
ここまでのソースコードを下記に掲載しておきます。
確認などにご活用ください。