こんにちは!
PHPのLaravelやJavaScriptでWeb開発をしているフリラーンスエンジニアのファドと申します!
こちらの記事は応用Laravel教材の第4回目の記事になります。
その他の応用Laravel教材を学習したい方は下記リンクから直接教材へ飛ぶことができます。
-
応用Laravel
【応用Laravel教材①】タスク管理アプリを実装してLaravelをより深く知る!〜環境構築からログイン機能実装編〜
-
応用Laravel
【応用Laravel教材②】Laravelでタスク管理アプリ実装!〜テンプレートの作成からログイン画面作成編〜
-
応用Laravel
【応用Laravel教材③】Laravelでタスク管理アプリ実装!〜プロジェクト一覧機能実装からログイン後のリダイレクト先変更編〜
-
応用Laravel
【応用Laravel教材⑤】Laravelでタスク管理アプリ実装!〜プロジェクト追加機能実装からタスク追加機能実装編〜
-
応用Laravel
【応用Laravel教材⑥】Laravelでタスク管理アプリ実装!〜タスク編集機能実装編〜
タスク一覧
プロジェクト一覧が表示できるようになったので、次はタスク一覧画面を作成していきましょう。
ルーティング
まずはルーティングから行っていきます。task-app/routes/web.php
を下記の通り編集してください。
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ProjectController;
use App\Http\Controllers\TaskController; // ここを追加
Route::get('/', function () {
return view('welcome');
});
Route::get('/dashboard', function () {
return view('dashboard');
})->middleware(['auth'])->name('dashboard');
// ログイン必須ルート
Route::middleware('auth')->group(function () {
// プロジェクト一覧画面
Route::get('projects', [ProjectController::class, 'index'])->name('projects.index');
// タスク一覧画面
Route::get('projects/{id}/tasks', [TaskController::class, 'index'])->name('tasks.index'); // ここを追加
});
require __DIR__.'/auth.php';
もちろんタスク一覧画面もログインユーザーのみが閲覧できるようにしたいので、Route::middleware('auth')->group(function () {});
の中にルーティングしています。
テーブル作成
次は、タスク情報を保存するtasksテーブル
をマイグレーションで作成していきます。
ターミナルで下記コマンドをtask-app
ディレクトリ上で実行してください。
$ ./vendor/bin/sail php artisan make:migration create_tasks_table
作成したtask-app/database/migrations/2022_06_23_223330_create_tasks_table.php
を下記の通り編集してください。
※2022_06_23_223330は作成した日時なので、皆さんのファイルとは異なっています。
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('tasks', function (Blueprint $table) {
$table->id();
$table->bigInteger('project_id')->unsigned(); // ここを追加
$table->string('task_name', 100); // ここを追加
$table->date('due_date'); // ここを追加
$table->integer('task_status')->default(0); // ここを追加
$table->timestamps();
// 外部キーの設定
$table->foreign('project_id')->references('id')->on('projects'); // ここを追加
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('tasks');
}
};
今回も外部キーの設定をしています。
というのも、タスクはプロジェクト情報の下に存在するデータだからです。
そのためprojectsテーブル
とtasksテーブル
の整合性を保つために外部キー制約の設定をしています。
また、タスクにはタスク名(task_name)
や期限(due_date)
、進捗(task_status)
に関するデータを保持するので、それらの定義をマイグレーションファイル内で行っています。
進捗に関しては完了
や処理済み
、処理中
、未対応
などの文字列を入れるのではなく、数値(integer)
で管理していきます。
このようなある物事のステータスなどをデータベースで管理する場合は、数値で管理することが多いので覚えておきましょう。
それでは、定義したマイグレーションファイルを実行しましょう。
ターミナルで下記コマンドをtask-app
ディレクトリ上で実行してください。
$ ./vendor/bin/sail php artisan migrate
これでtasksテーブル
がデータベースに作成されました。
モデル作成
次に、tasksテーブル
のデータを操作するためのモデルを作成しましょう。
ターミナルで下記コマンドをtask-app
ディレクトリ上で実行してください。
$ ./vendor/bin/sail php artisan make:model Task
作成したtask-app/app/Models/Task.php
を下記の通り編集してください。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Task extends Model
{
use HasFactory;
protected $fillable = [
'project_id',
'task_name',
'due_date',
'task_status',
];
}
データ内容を変えたいカラムを$fillable
で指定しておきましょう。
今回はプロジェクトID
とタスク名
、期限
、進捗
の4つのカラムを指定すればOKです。
リレーション
今回もprojectsテーブル
とtasksテーブル
のリレーションの設定をしておきましょう。task-app/app/Models/Task.php
を下記の通り編集してください。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Task extends Model
{
use HasFactory;
protected $fillable = [
'project_id',
'task_name',
'due_date',
'task_status',
];
/**
* Projectsテーブルとのリレーション
*/
public function project()
{
return $this->belongsTo(Project::class);
}
}
今回は$this->belongsTo(Project::class);
と定義しています。
それは、1つのタスクに対して、ある1つのプロジェクトがそのタスクを所有しているからです。
projectsテーブル
から見た時のリレーションも設定していきましょう。task-app/app/Models/Project.php
を下記の通り編集してください。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Project extends Model
{
use HasFactory;
protected $fillable = [
'project_name',
'user_id',
];
/**
* Usersテーブルとのリレーション
*/
public function user()
{
return $this->belongsTo(User::class);
}
/**
* Tasksテーブルとのリレーション
*/
public function tasks()
{
return $this->hasMany(Task::class);
}
}
今回は$this->hasMany(Task::class);
と定義しています。
それは、1つのプロジェクトに対して複数のタスクを保持することができるからです。
これでリレーションの設定は終わりです。
テストデータ作成
次は、シーダーを使用してタスクのテストデータを作成していきましょう。
ターミナルで下記コマンドをtask-app
ディレクトリ上で実行してください。
$ ./vendor/bin/sail php artisan make:seeder TasksTableSeeder
作成したtask-app/database/seeders/TasksTableSeeder.php
を下記の通り編集してください。
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB; // ここを追加
class TasksTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$project = DB::table('projects')->first();
DB::table('tasks')->insert([
'project_id' => $project->id,
'task_name' => 'タスク名1',
'task_status' => 0,
'due_date' => date('Y-m-d H:i:s', strtotime("1 day")),
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
DB::table('tasks')->insert([
'project_id' => $project->id,
'task_name' => 'タスク名2',
'task_status' => 1,
'due_date' => date('Y-m-d H:i:s', strtotime("2 day")),
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
DB::table('tasks')->insert([
'project_id' => $project->id,
'task_name' => 'タスク名3',
'task_status' => 2,
'due_date' => date('Y-m-d H:i:s', strtotime("3 day")),
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
DB::table('tasks')->insert([
'project_id' => $project->id,
'task_name' => 'タスク名4',
'task_status' => 3,
'due_date' => date('Y-m-d H:i:s', strtotime("4 day")),
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
}
}
特に難しい処理はしていません。
今回はテストデータとして4つのデータを作成しました。
また、project_id
には、存在するproject_id
が保存されていてほしいので、$project = DB::table('projects')->first();
と記述して、シーダーファイル内でプロジェクト情報を取得するようにしています。
次にtask-app/database/seeders/DatabaseSeeder.php
を下記の通り編集してください。
<?php
namespace Database\Seeders;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*
* @return void
*/
public function run()
{
$this->call([
UsersTableSeeder::class,
ProjectsTableSeeder::class,
TasksTableSeeder::class,
]);
}
}
これでtasksテーブル
のテストデータを作成する準備が整いました。
それでは、ターミナルで下記コマンドをtask-app
ディレクトリ上で実行してください。
$ ./vendor/bin/sail php artisan migrate:refresh --seed
今回も新しくシーダーファイルを追加したので、migrate:refresh
コマンドを実行します。--seed
オプションも付与して、テーブルの作成とデータの作成を同時に行っています。
migrate:refresh
コマンド後にテーブルのデータを確認しておくといいですね!
コントローラー作成&編集
次に、ルーティングで指定したTaskコントローラー
を作成していきます。
ターミナルで下記コマンドをtask-app
ディレクトリ上で実行してください。
$ ./vendor/bin/sail php artisan make:controller TaskController
作成が完了したら、task-app/app/Http/Controllers/TaskController.php
を下記の通り編集してください。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Project;
class TaskController extends Controller
{
/**
* プロジェクトに紐づくタスク一覧
*/
public function index($id)
{
// URLで送られてきたプロジェクトID
$currentProjectId = $id;
// プロジェクト取得
$project = Project::find($currentProjectId);
// 取得したプロジェクトに紐づくタスクを取得
$tasks = $project->tasks->all();
return view('tasks.index', compact(
'currentProjectId',
'tasks',
));
}
}
これでTaskコントローラー
にindexメソッド
を作成することができました。
ビュー作成&編集
http://localhost/projects/1/tasks
にアクセスがあった場合、Taskコントローラー
でtasks/index.blade.php
を表示させるように処理を記述したので、index.blade.php
を作成していきましょう。
まずはtask-app/resources/views
ディレクトリ配下にtasksフォルダ
を作成してください。
作成が完了したらtasksフォルダ
内にindex.blade.php
を作成してください。
コマンドで作成する場合は、ターミナルで下記コマンドをtask-app
ディレクトリ上で実行してください。
$ mkdir resources/views/tasks && touch resources/views/tasks/index.blade.php
作成したtask-app/resources/views/tasks/index.blade.php
を下記の通り編集してください。
@extends('layouts.layout')
@section('title')
タスク一覧
@endsection
@section('content')
<div class="container mt-4">
<div class="row">
<div class="column col-md-8 offset-md-2 mt-md-0 mt-3">
<div class="card">
<div class="card-header bg-dark text-light d-flex justify-content-between align-items-center">
<p class="mb-0 h5">タスク</p>
<a href="#" class="btn btn-primary">追加</a>
</div>
<table class="table table-hover mb-0">
<thead class="text-light" style="background-color: rgb(106, 106, 106)">
<tr class="text-center">
<th scope="col"style="width: 65%">タスク名</th>
<th scope="col" style="width: 15%">進捗</th>
<th scope="col" style="width: 20%">期限</th>
</tr>
</thead>
<tbody class="text-center">
@foreach ($tasks as $task)
<tr>
<td><a href="#">{{ $task->task_name }}</a></td>
<td><span class="d-inline badge bg-secondary">{{ $task->task_status }}</span></td>
<td>{{ $task->due_date }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</div>
@endsection
http://localhost/projects/1/tasksにアクセスして、下記画像のように表示されればOKです。
また、プロジェクト一覧画面からプロジェクト名がクリックされた時に、そのクリックされたプロジェクトに紐づくタスク一覧画面が表示するようにリンクを変更しましょう。
task-app/resources/views/projects/index.blade.php
を下記の通り編集してください。
@extends('layouts.layout')
@section('title')
プロジェクト一覧
@endsection
@section('content')
<div class="container mt-4">
<div class="row">
<div class="col col-md-6 offset-md-3">
<div class="card">
<div class="card-header bg-dark text-light d-flex justify-content-between align-items-center">
<p class="mb-0 h5">プロジェクト</p>
<a href="#" class="btn btn-primary">追加</a>
</div>
<table class="table table-hover mb-0">
<tbody class="text-center">
@foreach ($projects as $project)
<tr>
<td><a href="{{ route('tasks.index', $project->id) }}">{{ $project->project_name }}</a></td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</div>
@endsection
http://localhost/projectsへアクセスして、プロジェクト1
をクリックしてみましょう。
下記画像のようにプロジェクト1に紐付いたタスクが表示されればOKです。
さらにヘッダーでいくつか変更するべき箇所があるので、そちらを変更していきましょう。task-app/resources/views/layouts/header.blade.php
を下記の通り編集してください。
<header>
<nav class="navbar navbar-expand-lg navbar-dark" style="background-color: #1f1f1f;">
<div class="container-fluid">
@auth
<a class="navbar-brand" href="{{ route('projects.index') }}">タスク管理</a>
@else
<a class="navbar-brand" href="/">タスク管理</a>
@endauth
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse justify-content-end" id="navbarNavAltMarkup">
<div class="navbar-nav">
@auth
<a class="mr-lg-3 my-lg-0 my-3 btn btn-sm btn-dark text-light nav-item nav-link disabled" href="#">{{ Auth::user()->name }}</a>
<form action="{{ route('logout') }}" method="POST">
@csrf
<button class="btn btn-sm btn-danger text-light nav-item nav-link w-100">ログアウト</button>
</form>
@else
<a class="mr-lg-3 my-lg-0 my-3 btn btn-sm btn-dark text-light nav-item nav-link" href="{{ route('login') }}">ログイン</a>
<a class="btn btn-sm btn-primary text-light nav-item nav-link" href="{{ route('register') }}">新規登録</a>
@endauth
</div>
</div>
</div>
</nav>
</header>
変更点は下記の2点です。
1つ目は左上のタスク管理ボタンを未ログイン・ログイン時に遷移先を変更しています。
未ログイン時はトップページへ遷移させるようにし、ログイン時はプロジェクト一覧画面を表示させるようにしています。
2つ目は右上のユーザー名をログインユーザーの名前を表示させるようにしました。
アクセサ
アクセサとは、テーブルが本来持つデータを加工した状態で参照することができるようにしたLaravel
にデフォルトで備わっている機能です。
今回は進捗を数値でテーブルに保存しているので、表示する際に数字を完了
や処理済み
、処理中
、未対応
などの文字列に変換する必要があります。
この変換をモデルで自動的に行うことができる機能です。
使い方はモデルでget〇〇Attribute
というメソッドを用意するだけです。
以下は例として性別(カラム名:gender)を取得するためのgetGenderTextAttributeメソッド
です。
public function getGenderTextAttribute()
{
// 性別を取得(数値)
$gender = $this->attributes['gender'];
if($gender === 1) {
return '男性';
} else if ($gender === 2) {
return '女性';
} else {
return 'その他';
}
}
まず、$this->attributes['gender']
と記述することで、genderカラム
の値を取得することができます。
そして、取得した値を条件分岐させることで取得した値によって男性
や女性
、その他
としてreturnしています。
このgetGenderTextAttributeメソッド
はコントローラーやテンプレートで下記のように使用することができます。
$user->gender_text
get○○○Attribute
の○○○
の部分を->
の後に記述するだけです。
ただしメソッドの定義ではキャメルケース(GenderText)
ですが、使用するときはスネークケース(gender_text)
になります。
アクセサの実装
それでは実際に実装してみましょう。
task-app/app/Models/Task.php
を下記の通り編集してください。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Task extends Model
{
use HasFactory;
const TASK_STATUS_STRING = [
'未対応',
'処理中',
'処理済み',
'完了',
];
const TASK_STATUS_CLASS = [
'bg-danger',
'bg-primary',
'bg-success',
'bg-secondary',
];
protected $fillable = [
'project_id',
'task_name',
'due_date',
'task_status',
];
/**
* Projectsテーブルとのリレーション
*/
public function project()
{
return $this->belongsTo(Project::class);
}
/**
* 進捗のテキスト用アクセサ
*/
public function getTaskStatusStringAttribute()
{
$taskStatus = $this->attributes['task_status'];
if (!isset(self::TASK_STATUS_STRING[$taskStatus])) {
return '';
}
return self::TASK_STATUS_STRING[$taskStatus];
}
/**
* 進捗のBootstrapクラス用アクセサ
*/
public function getTaskStatusClassAttribute()
{
$taskStatus = $this->attributes['task_status'];
if (!isset(self::TASK_STATUS_CLASS[$taskStatus])) {
return '';
}
return self::TASK_STATUS_CLASS[$taskStatus];
}
}
今回は進捗のテキスト完了
、処理済み
、処理中
、未対応
を出力してくれるgetTaskStatusStringAttributeメソッド
と、進捗によって色を変えたいのでBootstrapのクラスを出力してくれるgetTaskStatusClassAttributeメソッド
を作成しました。
どちらのメソッドでも$taskStatus = $this->attributes['task_status'];
で進捗を数値で取得した後、テキストとクラス名を返す処理にしてあります。
これでモデル側の処理は終了です。
ビュー編集
次に、ビュー側でアクセサのメソッドを使用してみましょう。
task-app/resources/views/tasks/index.blade.php
を下記の通り編集してください。
@extends('layouts.layout')
@section('title')
タスク一覧
@endsection
@section('content')
<div class="container mt-4">
<div class="row">
<div class="column col-md-8 offset-md-2 mt-md-0 mt-3">
<div class="card">
<div class="card-header bg-dark text-light d-flex justify-content-between align-items-center">
<p class="mb-0 h5">タスク</p>
<a href="#" class="btn btn-primary">追加</a>
</div>
<table class="table table-hover mb-0">
<thead class="text-light" style="background-color: rgb(106, 106, 106)">
<tr class="text-center">
<th scope="col"style="width: 65%">タスク名</th>
<th scope="col" style="width: 15%">進捗</th>
<th scope="col" style="width: 20%">期限</th>
</tr>
</thead>
<tbody class="text-center">
@foreach ($tasks as $task)
<tr>
<td><a href="#">{{ $task->task_name }}</a></td>
<td><span class="d-inline badge {{ $task->task_status_class }}">{{ $task->task_status_string }}</span></td>
<td>{{ $task->due_date }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</div>
@endsection
http://localhost/projects/1/tasksにアクセスして、下記画像のように進捗が表示されていればOKです。
まず、Bootstrapのクラス名を<span class="d-inline badge {{ $task->task_status_class }}">
で設定しています。
メソッド名はgetTaskStatusClassAttribute
だったので、取得時はtask_status_class
になるだけですね。
また、進捗のテキストを{{ $task->task_status_string }}
で設定しています。
こちらもメソッド名がgetTaskStatusStringAttribute
だったものを、取得時にtask_status_string
にするだけです。
次の教材へ
次の教材は下記から簡単に飛ぶことができます!
引き続きプログラミングを楽しんでいきましょう!
プログラミング学習サポートについて
「独学で挫折した。。。」
「一人でのプログラミング学習がしんどい。。。」
「未経験からエンジニア転職をしたいけど何をしたら良いかわからない。。。」
このような悩みをお持ちの方向けに、本教材作成者のファドがMENTAという学習サイトにてあなたのプログラミング学習とエンジニア転職を徹底サポートいたします!
サポート価格はなんと1日あたりたったの約300円!
教材で分からない箇所のサポートはもちろんのこと、本サイトで公開しているすべての課題の解答も公開しております。
また、MENTAで学習を終わらせていただいた方限定で懇意にしていただいている企業さんを紹介することもあります!
なお、サポート内容の詳細は下記の通りです。
- 目標設定
- マインドセット
- オリジナル教材見放題
- オリジナル課題見放題
- オリジナル課題の解答見放題
- 課題コードレビュー
- 教材への無制限質問
- 課題への無制限質問
- ポートフォリオ作成アドバイス
- 褒めのコーチング
いくつかのプランを用意させていただいておりますので、下記より一度ご覧ください!
コメント