未経験からエンジニア転職をするための最強ロードマップロードマップ

【応用Laravel教材④】Laravelでタスク管理アプリ実装!〜タスク一覧機能実装編〜

ファド

こんにちは!
PHPのLaravelやJavaScriptでWeb開発をしているフリラーンスエンジニアのファドと申します!

こちらの記事は応用Laravel教材の第4回目の記事になります。

その他の応用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にするだけです。

次の教材へ

次の教材は下記から簡単に飛ぶことができます!
引き続きプログラミングを楽しんでいきましょう!

あわせて読みたい
【応用Laravel教材⑤】Laravelでタスク管理アプリ実装!〜プロジェクト追加機能実装からタスク追加機能実... こちらの記事は応用Laravel教材の第5回目の記事になります。 その他の応用Laravel教材を学習したい方は下記リンクから直接教材へ飛ぶことができます。 【プロジェクト作...

プログラミング学習サポートについて

「独学で挫折した。。。」

「一人でのプログラミング学習がしんどい。。。」

「未経験からエンジニア転職をしたいけど何をしたら良いかわからない。。。」

このような悩みをお持ちの方向けに、本教材作成者のファドがMENTAという学習サイトにてあなたのプログラミング学習とエンジニア転職を徹底サポートいたします!

サポート価格はなんと1日あたりたったの約300円!

教材で分からない箇所のサポートはもちろんのこと、本サイトで公開しているすべての課題の解答も公開しております。
また、MENTAで学習を終わらせていただいた方限定で懇意にしていただいている企業さんを紹介することもあります!

なお、サポート内容の詳細は下記の通りです。

  • 目標設定
  • マインドセット
  • オリジナル教材見放題
  • オリジナル課題見放題
  • オリジナル課題の解答見放題
  • 課題コードレビュー
  • 教材への無制限質問
  • 課題への無制限質問
  • ポートフォリオ作成アドバイス
  • 褒めのコーチング

いくつかのプランを用意させていただいておりますので、下記より一度ご覧ください!

あわせて読みたい
【プログラミング学習】1日あたりたったの「300円」のみでプログラミングを学び、最短で転職or稼ぐ!|【ME... プログラミングを学びたいすべての方へこれからの時代は格安でプログラミングを学び、最短で稼げ自己紹介はじめまして!この度はプランをご覧いただき、誠にありがとうござ...

コメント

    この記事が気に入ったら
    フォローしてね!

    よかったらシェアしてね!
    • URLをコピーしました!

    コメント

    コメントする

    目次