Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

More than 1 year has passed since last update.

Laravel応用教材①

Last updated at Posted at 2022-06-22

作成するアプリ

Laravelで基本のCRUD操作ができるようになったので、いよいよ本格的なアプリを作成していきましょう。

今回作成するアプリは、「タスク管理アプリ」です。

アプリ作成準備

さっそくLaravel教材で作成したfirst-appと同じLaravelディレクトリにsailコマンドでプロジェクトを作成していきましょう。
ターミナルで下記コマンドをLaravelディレクトリ上で実行してください。

Laravelプロジェクト作成

プロジェクト作成
$ curl -s "https://laravel.build/task-app" | bash

実行が終わったらsail upコマンドで起動させましょう。
ターミナルで下記コマンドをLaravelディレクトリ上で実行してください。

sail up

sail up
$ cd task-app && ./vendor/bin/sail up -d

これでタスク管理アプリのプロジェクト作成が完了しました。

phpMyAdminインストール

次にphpMyAdminのインストールをしていきましょう。
task-app/docker-compose.ymlを下記の通り編集してください。

docker-compose.yml
# For more information: https://laravel.com/docs/sail
version: '3'
services:
    # --- ここから追加 ---
    phpmyadmin:
        image: phpmyadmin/phpmyadmin
        links:
            - mysql:mysql
        ports:
            - 8888:80
        environment:
            PMA_HOST: mysql
        networks:
            - sail
    # --- ここまで追加 ---
    laravel.test:
    # --- 以下省略 ---

編集が完了したら、一旦開発環境をオフにします。
ターミナルで下記コマンドをtask-appディレクトリ上で実行してください。

コマンド
$ ./vendor/bin/sail stop

もう一度起動させるので、ターミナルで下記コマンドをtask-appディレクトリ上で実行してください。

コマンド
$ ./vendor/bin/sail up -d

これでphpMyAdminがインストールできたので、さっそくhttp://localhost:8888へアクセスして下記画像のような画面が表示されればOKです。
スクリーンショット 2022-06-29 23.34.04.png
データベースにアクセスするためのユーザー名とパスワードはtask-app/.envに記述されているので確認してみましょう。

.env
# --- 以上省略 ---

DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=task_app
DB_USERNAME=sail
DB_PASSWORD=password

# --- 以下省略 ---

DB_〇〇となっているところがデータベースに関する情報です。
データベース名がtask_appでユーザー名がsail、パスワードがpasswordとなります。

phpMyAdminで上記のユーザー名とパスワードを入力してログインしてください。
スクリーンショット 2022-06-29 23.38.38.png
task_appというデータベースが作成されていればOKです。

初期設定

Laravelをインストールした直後にしておく初期設定をしておきましょう。

初期設定で行うことは下記の4つです。

  1. アプリケーション名変更
  2. タイムゾーン変更
  3. 言語設定
  4. 日本語ファイルダウンロード

アプリケーション名変更

それでは、アプリケーション名を変更するためにtask-app/.envを下記の通り編集してください。

.env
# APP_NAME=Laravelから下記に変更
APP_NAME="タスク管理アプリ"
APP_ENV=local
# --- 以下省略 ---

タイムゾーン・言語設定・日本語ファイルダウンロード

次はタイムゾーン・言語設定・日本語ファイルダウンロードを一気に行っていきます。

task-app/config/app.phpを下記の通り編集してください。

app.php
// 'timezone' => 'UTC'から下記に変更(72行目付近)
'timezone' => 'Asia/Tokyo',

// 'locale' => 'en'から下記に変更(85行目付近)
'locale' => 'ja',

// 'faker_locale' => 'en_US'から下記に変更(111行目付近)
'faker_locale' => 'ja_JP',

編集箇所はLaravel教材の時と全く同じです。
最後にアプリケーション名と、タイムゾーン、言語設定の変更をアプリケーションに反映させるためにターミナルにて、下記コマンドをtask-appディレクトリ上で実行してください

コマンド
$ ./vendor/bin/sail php artisan cache:clear && ./vendor/bin/sail php artisan config:clear

これでアプリケーション名とタイムゾーン、言語設定を変更することができました。

最後に日本語ファイルのダウンロードを行っておきましょう。
こちらはドキュメントにも載っていますが、下記3コマンドで完了します。
さっそくターミナルにて、下記コマンドをtask-appディレクトリ上で実行してください

コマンド
$ ./vendor/bin/sail php -r "copy('https://readouble.com/laravel/8.x/ja/install-ja-lang-files.php', 'install-ja-lang.php');"
$ ./vendor/bin/sail php -f install-ja-lang.php
$ ./vendor/bin/sail php -r "unlink('install-ja-lang.php');"

コマンド実行後、task-app/resources/lang/jaディレクトリが作成されていればOKです。

これで開発の準備は整ったので、さっそく開発していきましょう。

ログイン機能実装

まずはログイン機能を実装していきます。
Laravelにはログイン機能を実装するためにLaravel Breezeが提供されています。

このLaravel Breezeを使用すると、たった数コマンドで認証機能が実装できてしまいます。
新規登録機能やログイン機能、ログアウト機能は認証機能と言われるので、覚えておきましょう。

それではさっそくLaravel Breezeを導入していきましょう。
ターミナルで下記コマンドをtask-appディレクトリ上で実行してください。

コマンド
$ ./vendor/bin/sail composer require laravel/breeze

このコマンドでLaravel Breezeに関するデータがインストールされたので、次はそれらのデータをLaravelに反映させていきましょう。

ターミナルで下記コマンドをtask-appディレクトリ上で実行してください。

コマンド
$ ./vendor/bin/sail php artisan breeze:install

コマンドの実行が終わると様々なファイルが追加されています。

追加されているファイルを覚える必要はありませんが、下記のようなファイルが追加されています。

app/Http/Controllers/Auth/AuthenticatedSessionController.php
app/Http/Controllers/Auth/ConfirmablePasswordController.php
app/Http/Controllers/Auth/EmailVerificationNotificationController.php
app/Http/Controllers/Auth/EmailVerificationPromptController.php
app/Http/Controllers/Auth/NewPasswordController.php
app/Http/Controllers/Auth/PasswordResetLinkController.php
app/Http/Controllers/Auth/RegisteredUserController.php
app/Http/Controllers/Auth/VerifyEmailController.php
app/Http/Requests/Auth/LoginRequest.php
app/View/Components/AppLayout.php
app/View/Components/GuestLayout.php
resources/views/auth/confirm-password.blade.php
resources/views/auth/forgot-password.blade.php
resources/views/auth/login.blade.php
resources/views/auth/register.blade.php
resources/views/auth/reset-password.blade.php
resources/views/auth/verify-email.blade.php
resources/views/components/application-logo.blade.php
resources/views/components/auth-card.blade.php
resources/views/components/auth-session-status.blade.php
resources/views/components/auth-validation-errors.blade.php
resources/views/components/button.blade.php
resources/views/components/dropdown-link.blade.php
resources/views/components/dropdown.blade.php
resources/views/components/input.blade.php
resources/views/components/label.blade.php
resources/views/components/nav-link.blade.php
resources/views/components/responsive-nav-link.blade.php
resources/views/dashboard.blade.php
resources/views/layouts/app.blade.php
resources/views/layouts/guest.blade.php
resources/views/layouts/navigation.blade.php
routes/auth.php
tailwind.config.js
tests/Feature/AuthenticationTest.php
tests/Feature/EmailVerificationTest.php
tests/Feature/PasswordConfirmationTest.php
tests/Feature/PasswordResetTest.php
tests/Feature/RegistrationTest.php

ファイル名や拡張子を確認すると、コントローラーやビューなどのファイルがインストールされていることがわかるかと思います。

次にターミナルで下記コマンドをtask-appディレクトリ上で実行してください。

コマンド
$ ./vendor/bin/sail npm install && ./vendor/bin/sail npm run dev

実行に時間がかかることもありますが、このコマンドを実行することで認証に関する画面が無事に表示できるようになります。

試しにhttp://localhostへアクセスしてみると、画面右上にLoginボタンRegisterボタンが表示されます。
スクリーンショット 2022-06-30 0.59.51.png
Loginボタンをクリックすると、下記のようにログイン画面が表示されます。
スクリーンショット 2022-06-30 1.02.07.png
Registerボタンをクリックすると、下記のように新規登録画面が表示されます。
スクリーンショット 2022-06-30 1.02.50.png
今のままだと画面は表示できるのですが、ユーザー情報を登録するテーブルが作成されていないので、新規登録やログインをすることはできません。

そのため、最後に認証に関するusersテーブルなどを作成するためにマイグレーションを行いましょう。

ターミナルで下記コマンドをtask-appディレクトリ上で実行してください。

コマンド
$ ./vendor/bin/sail php artisan migrate

Laravel教材では削除したデフォルトのマイグレーションファイルが実行され、認証用のテーブルが作成されます。

実際に作成されるテーブルはfailed_jobsテーブル migrationsテーブルpassword_resetsテーブルpersonal_access_tokensテーブルusersテーブルの5つが作成されます。

今回使用するのはusersテーブルです。

ユーザー作成

以上でLaravel Breezeの導入は終了ですが、新規登録やログインができることを確認しておきましょう。
http://localhost/registerへアクセスして、新規登録を行いましょう。

登録するデータは下記の通りです。

ラベル データ
Name fdsa
Email fdsa@fdsa.fdsa
Password fdsafdsa
Confirm Password fdsafdsa

無事に登録が完了すれば、下記画面へリダイレクトされます。
スクリーンショット 2022-06-30 1.10.11.png
右上のユーザー名fdsaをクリックするとLog Outボタンが出てくるのでログアウトしましょう。

http://localhost/loginへアクセスして、ログインを行いましょう。
ログイン情報は下記の通りです。

ラベル データ
Email fdsa@fdsa.fdsa
Password fdsafdsa

ログインが無事に完了すれば、下記画面へリダイレクトされます。
スクリーンショット 2022-06-30 1.10.11.png
これで新規登録とログイン、ログアウトの動作確認が完了しました。

テストデータ作成

次にユーザーのテストデータを作成しておきましょう。
テストデータの作成はLaravel教材の時と全く同じです。

まずはシーダーファイルを作成しましょう。
ターミナルで下記コマンドをtask-appディレクトリ上で実行してください。

コマンド
$ ./vendor/bin/sail php artisan make:seeder UsersTableSeeder

作成したtask-app/database/seeders/UsersTableSeeder.phpを下記の通り編集してください。

UsersTableSeeder.php
<?php

namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB; // ここを追加

class UsersTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        DB::table('users')->insert([
            'name' => 'test',
            'email' => 'test@test.test',
            'password' => bcrypt('testtest'),
            'created_at' => date('Y-m-d H:i:s'),
            'updated_at' => date('Y-m-d H:i:s'),
        ]);
    }
}

Laravelでは、ユーザーのパスワード登録時にセキュリティの観点から必ず暗号化して登録します。
暗号化のためのヘルパーメソッドであるbcryptメソッドも用意されており、簡単に暗号化することができます。

Laravelでユーザーのテストデータを作る際には、パスワードを暗号化することを覚えておきましょう。

次にtask-app/database/seeders/DatabaseSeeder.phpを下記の通り編集してください。

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,
        ]);
    }
}

これでusersテーブルのテストデータを作成する準備が整いました。
それでは、ターミナルで下記コマンドをtask-appディレクトリ上で実行してください。

コマンド
$ ./vendor/bin/sail php artisan db:seed

コマンドの実行が完了すると、ユーザー名testというテストデータが作成されます。
念の為、皆さんはphpMyAdminなどで確認しておきましょう。

現在usersテーブルには新規登録画面から登録したユーザー名fdsaのデータとシーダーで登録したユーザー名testの2つのユーザーデータが存在してることになります。

どちらのユーザーでもログインができるかなどは確かめておきましょう。

ID ユーザー名 メールアドレス パスワード
1 fdsa fdsa@fdsa.fdsa fdsafdsa
2 test test@test.test testtest

・新規登録
http://localhost/registerアクセス

・ログイン
ログアウト後、http://localhost/loginへアクセス

各種テンプレート作成

次はビューの元になる見た目のテンプレートを作成してきましょう。

テンプレート作成

ターミナルで下記コマンドをtask-appディレクトリ上で実行してください。

コマンド
$ touch resources/views/layouts/layout.blade.php

task-app/resources/views/layoutsディレクトリにlayout.blade.phpが作成されるので、下記の通り編集してください。

layout.blade.php
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>@yield('title', 'タスク管理')</title>

    <!-- Bootstrap -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/css/bootstrap.min.css" integrity="sha384-r4NyP46KrjDleawBgD5tp8Y7UzmLA05oM1iAEQ17CSuDqnUK2+k9luXQOfXJCJ4I" crossorigin="anonymous">

    <!-- CSS -->
    <link rel="stylesheet" href="{{ asset('css/style.css') }}">
</head>

<body>
    <!-- ヘッダー -->
    @include('layouts.header')

    <!-- コンテンツ -->
    <main>
        @yield('content')
    </main>

    <!-- フッター -->
    @include('layouts.footer')

    <!-- Bootstrap -->
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/js/bootstrap.min.js" integrity="sha384-oesi62hOLfzrys4LxRF63OJCXdXDipiYWBnvTl9Y9/TRlw5xlKIEHpNyvvDShgf/" crossorigin="anonymous"></script>
</body>

</html>

Laravel教材の時と同じように、簡単あに見た目を整えるためにBootstrapをCDNで使用しています。
また、後ほど作成するCSSを読み込むための<link rel="stylesheet" href="{{ asset('css/style.css') }}">という記述もしています。
こちらについては、CSS導入時に説明します。

タイトルやヘッダー、コンテンツ、フッターなどは@yield@includeを使用しているので、この後作成していきます。

ヘッダー作成

次に共通ヘッダーを作成します。

ターミナルで下記コマンドをtask-appディレクトリ上で実行してください。

コマンド
$ touch resources/views/layouts/header.blade.php

task-app/resources/views/layoutsディレクトリにheader.blade.phpが作成されるので、下記の通り編集してください。

header.blade.php
<header>
    <nav class="navbar navbar-expand-lg navbar-dark" style="background-color: #1f1f1f;">
        <div class="container-fluid">
            <a class="navbar-brand" href="#">タスク管理</a>
            <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" href="#">ユーザーA</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>

ヘッダーには必ず左上にアプリ名であるタスク管理というリンクを設定しておきます。
また、右上には未ログインユーザーかログインユーザーかで出し分けるボタンを設置します。

未ログインの場合はログインボタン新規登録ボタンを右上に表示します。
一方でログイン済みの場合はユーザー名ログアウトボタンを表示します。

Bladeテンプレートでは@authと記述することでログイン済みの時の処理を記述することができます。
@authで囲まれている部分は下記のようにユーザー名ログアウトボタンを記述しています。

ログイン済み
@auth
    <a class="mr-lg-3 my-lg-0 my-3 btn btn-sm btn-dark text-light nav-item nav-link" href="#">ユーザーA</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

ログアウトボタンはPOST送信のためform要素として処理しています。
formを使用する際は@csrfを忘れないようにしましょう。
また、action属性の属性値に{{ route('logout') }}と記述されていますが、これはLaravel Breezeで導入した場合のログアウト用のルート名がlogoutだからです。

ちなみにLaravelでは現在設定されているルートをコマンドで確認することができます。
ターミナルで下記コマンドをtask-appディレクトリ上で実行してください。

コマンド
$ ./vendor/bin/sail php artisan route:list

コマンドを実行すると下記のような実行結果が表示されます。

route:list
  GET|HEAD   / ............................................................................................................ 
  POST       _ignition/execute-solution ..... ignition.executeSolution › Spatie\LaravelIgnition › ExecuteSolutionController
  GET|HEAD   _ignition/health-check ................. ignition.healthCheck › Spatie\LaravelIgnition › HealthCheckController
  POST       _ignition/update-config .............. ignition.updateConfig › Spatie\LaravelIgnition › UpdateConfigController
  GET|HEAD   api/user ..................................................................................................... 
  GET|HEAD   confirm-password .................................. password.confirm › Auth\ConfirmablePasswordController@show
  POST       confirm-password .................................................... Auth\ConfirmablePasswordController@store
  GET|HEAD   dashboard .......................................................................................... dashboard
  POST       email/verification-notification ....... verification.send › Auth\EmailVerificationNotificationController@store
  GET|HEAD   forgot-password ................................... password.request › Auth\PasswordResetLinkController@create
  POST       forgot-password ...................................... password.email › Auth\PasswordResetLinkController@store
  GET|HEAD   login ..................................................... login › Auth\AuthenticatedSessionController@create
  POST       login .............................................................. Auth\AuthenticatedSessionController@store
  POST       logout .................................................. logout › Auth\AuthenticatedSessionController@destroy
  GET|HEAD   register ..................................................... register › Auth\RegisteredUserController@create
  POST       register ................................................................. Auth\RegisteredUserController@store
  POST       reset-password ............................................ password.update › Auth\NewPasswordController@store
  GET|HEAD   reset-password/{token} .................................... password.reset › Auth\NewPasswordController@create
  GET|HEAD   sanctum/csrf-cookie .............................................. Laravel\Sanctum › CsrfCookieController@show
  GET|HEAD   verify-email ........................... verification.notice › Auth\EmailVerificationPromptController@__invoke
  GET|HEAD   verify-email/{id}/{hash} ........................... verification.verify › Auth\VerifyEmailController@__invoke

                                                                                                        Showing [21] routes

これが現在ルーティングされているルート一覧です。
ログアウトに関するルーティングは下記の通りです。

ログアウトのルーティング
POST       logout .................................................. logout › Auth\AuthenticatedSessionController@destroy

この一文を読み解くと、POST通信でhttp://localhost//logoutというURLにアクセスでき、ルート名はlogoutで、アクセスがあるとAuth\AuthenticatedSessionControllerというコントローラーのdestroyメソッドが実行されるということです。

また、実行結果をよく見るとregisterloginといった新規登録とログインのルーティングもされていることがわかります。

それらのルート名を使用して、未ログイン時の2つのボタンも作成することができます。
未ログイン時の記述は@else以下の処理です。

未ログイン
@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

ログイン用のルート名はloginなので、{{ route('login') }}となります。
また、新規登録用のルート名はregisterなので{{ route('register') }}となります。

フッター作成

次に共通フッターを作成します。

ターミナルで下記コマンドをtask-appディレクトリ上で実行してください。

コマンド
$ touch resources/views/layouts/footer.blade.php

task-app/resources/views/layoutsディレクトリにfooter.blade.phpが作成されるので、下記の通り編集してください。

footer.blade.php
<footer class="footer fixed-bottom" style="background-color: #1f1f1f;">
    <div class="container text-center">
        <span class="text-light">©︎Laravel応用教材</span>
    </div>
</footer>

フッターは特に難しいことはしていません。

トップページ作成

次にテンプレートが完成したのでトップページを作成していきましょう。
http://localhost/へアクセスされた場合に表示されるwelcome.blade.phpを変更していきましょう。

task-app/resources/views/welcome.blade.phpを下記の通り編集してください。

welcome.blade.php
@extends('layouts.layout')

@section('title')
    タスク管理
@endsection

@section('content')
    <div class="top-page d-flex flex-column justify-content-center align-items-center ">
        <div class="h1 text-center">
            <h1 class="text-light">タスク管理</h1>
            <h2 class="text-light">いつでもどこでも簡単に♪</h2>
        </div>
        <div class="d-flex">
            @auth
                <a href="#" class="btn btn-success">プロジェクト一覧</a>
            @else
                <a href="{{ route('register') }}" class="btn btn-success">まずは無料で登録する</a>
            @endauth
        </div>
    </div>
@endsection

ヘッダーと同じように@authを使用して、ログイン時と未ログインの時のボタン出し分けを行っています。
ログイン時はプロジェクト一覧へ遷移できるボタンを表示し、未ログインの時は新規登録画面へ遷移するためのボタンを表示するようにしています。

次に、独自のCSSで背景などを指定したいので、CSSファイルを作成します。
LaravelでCSSを使用するにはいくつか方法がありますが、まずは一番簡単に実装できる方法で実装します。

task-app/publicディレクトリにcssフォルダを作成し、その中にstyle.cssを作成してください。
コマンドでもVSCode上で作成してもどちらでもOKです。

コマンドで作成する場合は、ターミナルで下記コマンドをtask-appディレクトリ上で実行してください。

コマンド
$ mkdir public/css && touch public/css/style.css

作成したtask-app/public/css/style.cssを下記の通り編集してください。

style.css
.top-page {
    background-image: url('../images/test.jpg');
    background-color:rgba(0, 0, 0, .6);
    background-blend-mode:darken;
    background-position: center center;
    background-repeat: no-repeat;
    background-size: cover;
    background-attachment: fixed;
    min-height: calc(100vh - 80px);
}

CSSで背景に画像を指定していますが、画像がまだないので写真を用意して設定していきます。
皆さんが持っている画像でも大丈夫ですが、https://unsplash.com/photos/cckf4TsHAuwからダウンロードしていただいてもOKです。
※画像URLが開けない場合はこちらから好きな写真を選んでダウンロードしてください。

Laravelで画像を表示させるためにtask-app/publicディレクトリにimagesフォルダを作成します。
さっそくpublicディレクトリにimagesフォルダを作成しましょう。
コマンドでもVSCode上で作成してもどちらでもOKです。

コマンドで作成する場合は、ターミナルで下記コマンドをtask-appディレクトリ上で実行してください。

コマンド
$ public/images

作成したtask-app/public/imagesディレクトリに画像を入れてください。
画像の名前はCSSで指定した通り、test.jpgとしています。

これでトップページを表示することができるようになりました。
http://localhost/にアクセスして、下記画像のようになっていればOKです!
スクリーンショット 2022-07-01 1.54.51.png

認証画面編集

次に、新規登録画面やログイン画面の見た目を変更していきましょう。
Laravel Breezeで認証機能を実装した場合、自動的にtask-app/resources/viewsディレクトリにauthディレクトリが作成され、その中に認証用のBladeファイルがいくつか作成されています。

新規登録画面

まず、新規登録画面はtask-app/resources/views/auth/register.blade.phpを変更することで、オリジナルの新規登録画面を作成することができます。

register.blade.phpを下記の通り編集してください。

resources/views/auth/register.blade.php
@extends('layouts.layout')

@section('title')
    新規登録
@endsection

@section('content')
    <div class="container mt-4">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header text-center">新規登録</div>
     
                    <div class="card-body">
                        <form method="POST" action="{{ route('register') }}">
                            @csrf
     
                            <div class="form-group d-flex flex-column flex-md-row">
                                <label for="name" class="col-md-4 col-form-label text-md-right">ユーザー名:</label>
                                <div class="col-md-6">
                                    <input id="name" type="text" class="form-control @error('name') is-invalid @enderror" name="name" value="{{ old('name') }}" required autocomplete="name" autofocus>
                                    @error('name')
                                        <span class="invalid-feedback" role="alert">
                                            <strong>{{ $message }}</strong>
                                        </span>
                                    @enderror
                                </div>
                            </div>

                            <div class="form-group d-flex flex-column flex-md-row mt-3">
                                <label for="email" class="col-md-4 col-form-label text-md-right">メールアドレス:</label>
                                <div class="col-md-6">
                                    <input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email" autofocus>
                                    @error('email')
                                        <span class="invalid-feedback" role="alert">
                                            <strong>{{ $message }}</strong>
                                        </span>
                                    @enderror
                                </div>
                            </div>

                            <div class="form-group d-flex flex-column flex-md-row mt-3">
                                <label for="password" class="col-md-4 col-form-label text-md-right">パスワード:</label>
                                <div class="col-md-6">
                                    <input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="new-password">
                                    @error('password')
                                        <span class="invalid-feedback" role="alert">
                                            <strong>{{ $message }}</strong>
                                        </span>
                                    @enderror
                                </div>
                            </div>

                            <div class="form-group d-flex flex-column flex-md-row mt-3">
                                <label for="password_confirmation" class="col-md-4 col-form-label text-md-right">パスワード確認:</label>
                                <div class="col-md-6">
                                    <input id="password_confirmation" type="password" class="form-control @error('password_confirmation') is-invalid @enderror" name="password_confirmation" required autocomplete="new-password">
                                    @error('password_confirmation')
                                        <span class="invalid-feedback" role="alert">
                                            <strong>{{ $message }}</strong>
                                        </span>
                                    @enderror
                                </div>
                            </div>
     
                            <div class="form-group d-flex mt-3 mb-0">
                                <div class="col-md-10 col-12 d-flex justify-content-end">
                                    <button type="submit" class="btn btn-primary">登録</button>
                                </div>
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

編集が完了したら、http://localhost/registerへアクセスし、下記の通り画面が表示されればOKです。
スクリーンショット 2022-07-01 1.59.14.png

それでは実装内容について2つのポイントを説明します。
まず1つ目は、formでPOST送信をしているということです。
action属性にはroute('register')を記述していますが、これは以前ルーティング一覧をコマンドで確認した時に新規登録用のルート名がregisterであったためです。

念の為、もう一度ルーティング一覧を確認できるコマンドを復習しておきましょう。

コマンド
$ ./vendor/bin/sail php artisan route:list

心配な方はもう一度新規登録のルート名がregisterなのか確認しておくと良いでしょう。

2つ目にパスワード一致のバリデーション機能についてです。
Laravelでは、confirmedルールというバリデーションルールが実装されています。
このルールはname属性がある属性値(仮にpasswordとします)の入力値とある属性値+confirmationという属性値(今回の場合はpassword_confirmation)の入力値が一致することを検証します。

よく使う場面として、パスワードを入力させた後に確認としてもう一度パスワードを入力させる時のバリデーションとして使用します。

今回もパスワードの確認用inputにはpassword_confirmationというname属性にしています。

ログイン画面

次にログイン画面の編集を行っていきます。
ログイン画面はtask-app/resources/views/auth/login.blade.phpを変更することで、オリジナルのログイン画面を作成することができます。

login.blade.phpを下記の通り編集してください。

resources/views/auth/login.blade.php
@extends('layouts.layout')

@section('title')
    ログイン
@endsection

@section('content')
    <div class="container mt-4">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header text-center">ログイン</div>
     
                    <div class="card-body">
                        <form method="POST" action="{{ route('login') }}">
                            @csrf
     
                            <div class="form-group d-flex flex-column flex-md-row">
                                <label for="email" class="col-md-4 col-form-label text-md-right">メールアドレス:</label>
                                <div class="col-md-6">
                                    <input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email" autofocus>
                                    @error('email')
                                        <span class="invalid-feedback" role="alert">
                                            <strong>{{ $message }}</strong>
                                        </span>
                                    @enderror
                                </div>
                            </div>

                            <div class="form-group d-flex flex-column flex-md-row mt-3">
                                <label for="password" class="col-md-4 col-form-label text-md-right">パスワード:</label>
                                <div class="col-md-6">
                                    <input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="current-password">
                                    @error('password')
                                        <span class="invalid-feedback" role="alert">
                                            <strong>{{ $message }}</strong>
                                        </span>
                                    @enderror
                                </div>
                            </div>
     
                            <div class="form-group d-flex mt-3 mb-0">
                                <div class="col-md-10 col-12 d-flex justify-content-end">
                                    <button type="submit" class="btn btn-primary">ログイン</button>
                                </div>
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

編集が完了したら、http://localhost/loginへアクセスし、下記の通り画面が表示されればOKです。
スクリーンショット 2022-07-01 2.13.40.png
ログイン画面は特に難しいことはありません。
新規登録画面と同じようにformでPOST送信をしています。

action属性にはroute('login')を記述していますが、Laravel Breezeで認証機能を実装した場合のログインのルート名がloginだからです。

もちろん$ ./vendor/bin/sail php artisan route:listで確認できます。

バリデーションメッセージの日本語化

これで新規登録とログイン機能は完成しましたが、新規登録時のバリデーションメッセージの一部が下記画像の通り、日本語化されていないので設定していきます。
スクリーンショット 2022-07-01 17.04.36.png
ちなみに、新規登録機能のコントローラーはtask-app/app/Http/Controllers/Auth/RegisterdUserController.phpです。

ユーザー情報登録用のメソッドstoreは下記のようになっています。

storeメソッド
/**
 * Handle an incoming registration request.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Http\RedirectResponse
 *
 * @throws \Illuminate\Validation\ValidationException
 */
public function store(Request $request)
{
    $request->validate([
        'name' => ['required', 'string', 'max:255'],
        'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
        'password' => ['required', 'confirmed', Rules\Password::defaults()],
    ]);

    $user = User::create([
        'name' => $request->name,
        'email' => $request->email,
        'password' => Hash::make($request->password),
    ]);

    event(new Registered($user));

    Auth::login($user);

    return redirect(RouteServiceProvider::HOME);
}

バリデーションに関するコードは$request->validate()で囲まれた部分です。
記述方法はフォームリクエストで指定した方法と同じです。
このようにバリデーションはコントローラーで指定することもできます。

では、さっそく日本語化していきましょう。

task-app/resources/lang/ja/validation.phpを下記の通り編集してください。

validation.php
// --- 以上省略 ---

/*
|--------------------------------------------------------------------------
| カスタムバリデーション属性名
|--------------------------------------------------------------------------
|
| 以下の言語行は、例えば"email"の代わりに「メールアドレス」のように、
| 読み手にフレンドリーな表現でプレースホルダーを置き換えるために指定する
| 言語行です。これはメッセージをよりきれいに表示するために役に立ちます。
|
*/

'attributes' => [
    'name' => 'ユーザー名',
    'email' => 'メールアドレス',
    'password' => 'パスワード',
],

validation.phpは、以前日本語ファイルをダウンロードした際にダウンロードされたファイルです。
そのファイルの一番下にattributes => [];となっているので、その中に翻訳した値を入力すればOKです。

もう一度新規登録画面でバリデーションに引っかかるように入力してみると、下記画像のようになります。
スクリーンショット 2022-07-01 17.22.35.png
これで日本語化はOKです。

プロジェクト一覧

次はプロジェクト一覧画面を作成していきましょう。

ルーティング

まずはルーティングから行っていきます。
task-app/routes/web.phpを下記の通り編集してください。

web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ProjectController;

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');
});

require __DIR__.'/auth.php';

まず、タスク管理アプリには、未ログインユーザーがアクセスできないページがいくつか存在しています。
その内の一つが今回実装するプロジェクト一覧画面です。
web.phpでは、アプリケーションの利用に認証を求める機能を実装することができます。

ページに認証を求める処理はミドルウェアを用いて実装します。
ミドルウェアとは、ルート毎の処理に移る前に実行される処理のことです。
つまり、画面を表示する前にログインかどうかをミドルウェアを使用して確かめるということです。

認証を求めるミドルウェアは最初から用意されているので、ルートにミドルウェアを適用するだけで実装することができます。

具体的な記述方法は下記の通りです。

ミドルウェア
Route::middleware('auth')->group(function () {
    // ここにログイン後しか表示できないルートを記述する
});

とても簡単ですね。

以降ログインが必要な画面・処理のルーティングをする場合は、この中に記述していきます。
今はこれから実装するプロジェクト一覧画面のルーティングを記述しています。

コントローラー作成&編集

次に、ルーティングで指定したProjectコントローラーを作成していきます。
ターミナルで下記コマンドをtask-appディレクトリ上で実行してください。

コマンド
$ ./vendor/bin/sail php artisan make:controller ProjectController

作成が完了したら、task-app/app/Http/Controllers/ProjectController.phpを下記の通り編集してください。

ProjectController
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class ProjectController extends Controller
{
    /**
     * プロジェクト一覧画面表示
     */
    public function index()
    {
        return view('projects.index');
    }
}

これでProjectコントローラーindexメソッドを作成することができました。
http://localhost/projectsにアクセスがあった場合、projects/index.blade.phpを表示させるように処理を記述したので、index.blade.phpを作成していきましょう。

まずはtask-app/resources/viewsディレクトリ配下にprojectsフォルダを作成してください。
作成が完了したらprojectsフォルダ内にindex.blade.phpを作成してください。

コマンドで作成する場合は、ターミナルで下記コマンドをtask-appディレクトリ上で実行してください。

コマンド
$ mkdir resources/views/projects && touch resources/views/projects/index.blade.php

作成したtask-app/resources/views/projects/index.blade.phpを下記の通り編集してください。

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">
                            <tr>
                                <td class="table-active"><a href="#">プロジェクト1</a></td>
                            </tr>
                            <tr>
                                <td><a href="#">プロジェクト2</a></td>
                            </tr>
                            <tr>
                                <td><a href="#">プロジェクト3</a></td>
                            </tr>
                            <tr>
                                <td><a href="#">プロジェクト4</a></td>
                            </tr>
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </div>
@endsection

今回は特に難しい記述はありません。
まだプロジェクトのデータが存在していないので、HTMLでデータ部分を記述しています。

テーブル作成

プロジェクトのデータを保存するためのprojectsテーブルを作成していきます。
projectsテーブルの内容は下記の通りです。

カラム名 日本語
id ID
user_id ユーザーID
project_name プロジェクト名
created_at 作成日
updated_at 更新日

user_idというカラムが存在していますが、このカラムの値とusersテーブルidを紐付けることで、どのユーザーが作成したプロジェクトなのかをデータとして紐付けることができます。

それでは、さっそくマイグレーションでprojectsテーブルを作成していきましょう。
ターミナルで下記コマンドをtask-appディレクトリ上で実行してください。

コマンド
$ ./vendor/bin/sail php artisan make:migration create_projects_table

作成したtask-app/database/migrations/2022_06_23_223330_create_projects_table.phpを下記の通り編集してください。
※2022_06_23_223330は作成した日時なので、皆さんのファイルとは異なっています。

2022_06_23_223330_create_projects_table.php
<?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('projects', function (Blueprint $table) {
            $table->id();
            $table->bigInteger('user_id')->unsigned(); // ここを追加
            $table->string('project_name', 30); // ここを追加
            $table->timestamps();

            // 外部キーの設定
            $table->foreign('user_id')->references('id')->on('users'); // ここを追加
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('projects');
    }
};

追加した内容を確認すると、外部キーの設定という処理が追加されています。
これは2つのテーブル間でデータの整合性を保つために設定される制約です。
必須の設定ではありませんが、usersテーブルprojectsテーブルのデータの整合性を保つために記述しておきます。

※データの整合性を保つ例としては、ユーザー情報が削除されると、削除されたユーザーに紐付いたプロジェクト情報も削除されます。

では、ターミナルで下記コマンドをtask-appディレクトリ上で実行してください。

コマンド
$ ./vendor/bin/sail php artisan migrate

これでprojectsテーブルが作成されました。

モデル作成

次に、projectsテーブルのデータを操作するためのモデルを作成しましょう。
ターミナルで下記コマンドをtask-appディレクトリ上で実行してください。

コマンド
$ ./vendor/bin/sail php artisan make:model Project

作成したtask-app/app/Models/Project.phpを下記の通り編集してください。

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 = [
        'user_id',
        'project_name',
    ];
}

データ内容を変えたいカラムを$fillableで指定しておきましょう。
今回はユーザーIDプロジェクト名の2つのカラムのみでOKです。

テストデータ作成

モデルの作成も終わったので、シーダーを使用してテストデータを作成していきましょう。
ターミナルで下記コマンドをtask-appディレクトリ上で実行してください。

コマンド
$ ./vendor/bin/sail php artisan make:seeder ProjectsTableSeeder

作成したtask-app/database/seeders/ProjectsTableSeeder.phpを下記の通り編集してください。

ProjectsTableSeeder.php
<?php

namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB; // ここを追加

class ProjectsTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        // usersテーブルから1つのデータを取得
        $user = DB::table('users')->first();

        DB::table('projects')->insert([
            'project_name' => 'プロジェクト1',
            'user_id' => $user->id,
            'created_at' => date('Y-m-d H:i:s'),
            'updated_at' => date('Y-m-d H:i:s'),
        ]);

        DB::table('projects')->insert([
            'project_name' => 'プロジェクト2',
            'user_id' => $user->id,
            'created_at' => date('Y-m-d H:i:s'),
            'updated_at' => date('Y-m-d H:i:s'),
        ]);

        DB::table('projects')->insert([
            'project_name' => 'プロジェクト3',
            'user_id' => $user->id,
            'created_at' => date('Y-m-d H:i:s'),
            'updated_at' => date('Y-m-d H:i:s'),
        ]);
    }
}

特に難しい処理はしていません。
今回はテストデータとして3つのデータを作成しました。
また、user_idには、存在するuser_idが保存されていてほしいので、$user = DB::table('users')->first();と記述して、シーダーファイル内でユーザー情報を取得するようにしています。

次にtask-app/database/seeders/DatabaseSeeder.phpを下記の通り編集してください。

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,
        ]);
    }
}

これでprojectsテーブルのテストデータを作成する準備が整いました。
それでは、ターミナルで下記コマンドをtask-appディレクトリ上で実行してください。

コマンド
$ ./vendor/bin/sail php artisan migrate:refresh --seed

今回は新しくシーダーファイルを追加したので、migrate:refreshコマンドを実行します。
このmigrate:refreshコマンドを実行すると、全てのテーブルが一回削除され、もう一度マイグレーションが実行されます。
つまり、中のデータもリセットされるということです。

また、--seedオプションを付けると、シーダーの実行も一緒にしてくれるので、テーブルの作成とデータの作成を同時に行ってくれます。

migrate:refreshコマンド後にテーブルのデータを確認しておくといいですね!

リレーション

次はリレーションを定義していきます。

そもそもリレーションとは、データベース上のテーブルを関連付けるための機能です。
例えば今回の実装の場合、usersテーブルprojectsテーブルの2つが存在しています。
外部キーの設定でも少し説明しましたが、この2つのテーブルはiduser_idで関連付けられています。

これをLaravelでは、どのテーブルとどのテーブルが関連しているのかをモデルの中に定義することができます。

リレーションにはいくつかの種類があり、代表的なものを2つ紹介します。

リレーション メソッド
1 対 1 hasOne() 1人のユーザーに対してプロフィール画像は1枚
1 対 1(逆) belongsTo() 1枚のプロフィール画像に対してユーザーは1人
1 対 多 hasMany() 1人のユーザーに対して投稿は複数
1 対 多(逆) belongsTo() 1つの投稿に対してユーザーは1人

リレーションでは、このようにどのテーブルとどのテーブルがどのような関係で関連しているのかを定義することができます。

リレーションを設定していると、下記のような利点があります。

  • リレーション先のテーブルにあるデータを取得できる
  • テーブル情報に変更があれば、自動的に変更があったテーブルとリレーションのあるテーブルにも情報が反映される

なんとなくイメージはできたと思うので、さっそく実装していきます。

task-app/app/Models/User.phpを下記の通り編集してください。

app/Models/User.php
<?php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array<int, string>
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var array<int, string>
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * The attributes that should be cast.
     *
     * @var array<string, string>
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

    // --- ここから追加 ---

    /**
     * Projectsテーブルとのリレーション
     */
    public function projects()
    {
        return $this->hasMany(Project::class);
    }

    // --- ここまで追加 ---
}

今回は$this->hasMany(Project::class);と定義しています。
それは、1人のユーザーに対して複数のプロジェクトを保持することができるからです。

また、メソッド名はprojectsと複数形にしています。
これも複数のプロジェクトを保持できることから複数形にしています。

projectsテーブルから見た時のリレーションも設定していきましょう。
task-app/app/Models/Project.phpを下記の通り編集してください。

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);
    }

    // --- ここまで追加 ---
}

今回は$this->belongsTo(User::class);と定義しています。
それは、1つのプロジェクトに対して、1人のユーザーがそのプロジェクトの所有者だからです。

また、メソッド名はuserと単数形にしています。
これは1つのプロジェクトにつき、プロジェクトの保有者は1人だからです。

これで定義自体は終わりです。
定義したメソッドの使用方法は実装時に確認しましょう。

コントローラー編集

プロジェクトのテストデータを用意したので、projectsテーブルからデータを取得して表示できるようにしてみましょう。

task-app/app/Http/Controllers/ProjectController.phpを下記の通り編集してください。

TaskController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; // ここを追加

class ProjectController extends Controller
{
    /**
     * プロジェクト一覧画面
     */
    public function index()
    {
        // ログインユーザーが作成した全てのプロジェクトを取得
        $projects = Auth::user()->projects->all();

        return view('projects.index', compact('projects'));
    }
}

Laravelでは現在のログインユーザー情報をAuth::user()と記述することで取得することができます。
これはLaravelのAuthファサードという機能を使用することで実装することができるので、use文を使用して、Authファサードを読み込んでいます。

また、ユーザー情報に->projects->all()と記述するとリレーション先のprojectsテーブルの情報を取得することができます。
ここで先ほどUserモデルに定義したprojectsメソッドを使用することができるということです。

後は取得したプロジェクト情報をcompact関数を使用してビューに渡しています。

ビュー編集

次はビューを編集していきましょう。
task-app/resources/views/projects/index.blade.phpを下記の通り編集してください。

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="#">{{ $project->project_name }}</a></td>
                                </tr>
                            @endforeach
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </div>
@endsection

http://localhost/projectsにアクセスし、下記画像のような画面が表示されればOKです。
スクリーンショット 2022-07-01 23.07.05.png
これでプロジェクト一覧が表示されるようになりました。

新規登録・ログイン後のリダイレクト先を変更

現在、新規登録・ログイン後にリダイレクトされる先は下記の通りです。
スクリーンショット 2022-07-02 1.56.03.png
これを先ほど作成したプロジェクト一覧に遷移するように設定しましょう。

Laravel Breezeでログイン機能を実装した場合、新規登録・ログイン後のリダイレクト先を変更するには、RouteServiceProvider.phppublic const HOME = '/dashboard'を変更する必要があります。

task-app/app/Providers/RouteServiceProvider.phpを下記の通り編集してください。

RouteServiceProvider.php
<?php

namespace App\Providers;

use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\Route;

class RouteServiceProvider extends ServiceProvider
{
    /**
     * The path to the "home" route for your application.
     *
     * Typically, users are redirected here after authentication.
     *
     * @var string
     */
    public const HOME = '/projects';

    // --- 以下省略 ---
}

編集が終了したら、http://localhost/loginからログインしてください。
ログイン後のリダイレクト先が下記画像のようにプロジェクト一覧画面になっていればOKです。
スクリーンショット 2022-07-01 23.07.05.png
定数HOMEにプロジェクト一覧のパスを指定することで、指定したリダイレクト先にリダイレクトされるようになります。

これは新規登録処理のコントローラーRegisteredUserController.phpとログイン処理のコントローラーAuthenticatedSessionController.phpstoreメソッドの中身で、RouteServiceProviderの定数HOMEがリダイレクト先として指定してあるからです。

RegisteredUserController.phpstoreメソッドは下記の通りです。

RegisteredUserController.php
public function store(Request $request)
{
    $request->validate([
        'name' => ['required', 'string', 'max:255'],
        'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
        'password' => ['required', 'confirmed', Rules\Password::defaults()],
    ]);

    $user = User::create([
        'name' => $request->name,
        'email' => $request->email,
        'password' => Hash::make($request->password),
    ]);

    event(new Registered($user));

    Auth::login($user);

    return redirect(RouteServiceProvider::HOME);
}

return redirect(RouteServiceProvider::HOME);と記述されているのがわかると思います。

また、AuthenticatedSessionController.php storeメソッドは下記の通りです。

AuthenticatedSessionController.php
public function store(LoginRequest $request)
{
    $request->authenticate();

    $request->session()->regenerate();

    return redirect()->intended(RouteServiceProvider::HOME);
}

return redirect()->intended(RouteServiceProvider::HOME);と記述されています。

中身の処理をわざわざ覚える必要は全くありません。
リダイレクト先の変更はRouteServiceProvider.phpの定数HOMEを変更することで実装できることが理解できればOKです!

念の為、新規登録後のリダイレクト先もプロジェクト一覧になっているかも各自確かめておきましょう。

タスク一覧

プロジェクト一覧が表示できるようになったので、次はタスク一覧画面を作成していきましょう。

ルーティング

まずはルーティングから行っていきます。
task-app/routes/web.phpを下記の通り編集してください。

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は作成した日時なので、皆さんのファイルとは異なっています。

2022_06_23_223330_create_tasks_table.php
<?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を下記の通り編集してください。

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を下記の通り編集してください。

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を下記の通り編集してください。

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を下記の通り編集してください。

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を下記の通り編集してください。

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を下記の通り編集してください。

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を下記の通り編集してください。

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です。
スクリーンショット 2022-07-02 0.14.28.png

また、プロジェクト一覧画面からプロジェクト名がクリックされた時に、そのクリックされたプロジェクトに紐づくタスク一覧画面が表示するようにリンクを変更しましょう。

task-app/resources/views/projects/index.blade.phpを下記の通り編集してください。

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です。
スクリーンショット 2022-07-02 0.14.28.png

さらにヘッダーでいくつか変更するべき箇所があるので、そちらを変更していきましょう。
task-app/resources/views/layouts/header.blade.phpを下記の通り編集してください。

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を下記の通り編集してください。

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を下記の通り編集してください。

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です。
スクリーンショット 2022-07-02 2.48.58.png
まず、Bootstrapのクラス名を<span class="d-inline badge {{ $task->task_status_class }}">で設定しています。
メソッド名はgetTaskStatusClassAttribute だったので、取得時はtask_status_classになるだけですね。

また、進捗のテキストを{{ $task->task_status_string }}で設定しています。
こちらもメソッド名がgetTaskStatusStringAttributeだったものを、取得時にtask_status_stringにするだけです。

0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?