Laravel FortifyとAdminLTEで管理画面を作ってみよう!

プログラミング

 Laravel10にAdminLTEでシンプルな管理画面を構築する方法を解説します。ログイン画面と管理画面を利用するユーザーの登録画面も作成します。Laravel BreezeやJetstreamは使用せず、認証バックエンドのみを利用したいためLaravel Fortifyを導入します。シンプルなログイン画面を実装するだけであれば、フロントエンドを自由に設計しつつ、バックエンドはLaravelの恩恵に与れますのでオススメです。

筆者の開発環境

PC:Apple M1 チップ搭載MacBook Air
OS:macOS Sonoma 14.1.2
MAMP:6.8
PHP:8.2.0

事前準備

MAMPとComposerを使用します。下記のnoteの記事を参考に事前の準備を整えてください。

M1MacにMAMPをインストールする|Papagram
Intel版Macへのインストール方法は下記の記事を参照してください。  「MAMP(マンプ)」とは、Webアプリケーションの開発に必要なフリーソフトをまとめて扱う無料のパッケージソフトウェアです。Macintosh(マッキントッシュ)、A...
Composerの使い方(基本)|Papagram
Composer(コンポーザー)とはPHPのパッケージ管理システムのことです。パッケージ管理システムとはコマンドライン上でソフトウェアやライブラリ等のインストールやアップデートを行うためのツールです。Composer はPHPの開発には欠か...

プロジェクトの作成

 まず、プロジェクトを配置するフォルダに移動します。今回はホームディレクトリの「Developments/papagram」フォルダにしますが任意の場所でかまいません。ターミナルを起動し、下記コマンドを実行してください。

cd ~/Developments/papagram

 Laravelをインストールします。最新の10.xをインストールするためにはPHPのバージョンが8.1以上である必要があります。下記のphpコマンドを実行し、PHPのバージョンが8.1以上であることを確かめてください。

php -v

私の環境の実行結果は下記の通りです。

PHP 8.2.0 (cli) (built: Dec  8 2022 17:48:05) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.2.0, Copyright (c) Zend Technologies

 次に、下記のコマンドを実行してください。最新のLaravelがインストールされます。「running_recoed」の部分は任意のプロジェクト名をつけてください。

composer create-project laravel/laravel running_record

 インストールが完了しましたら、プロジェクトフォルダに移動しましょう。下記のコマンドを実行してください。

cd running_record

Laravelのバージョンを確かめます。下記のコマンドを実行してください。

php artisan about

下記のような実行結果が表示されます。

  Environment ............................................................................................................................  
  Application Name ............................................................................................................... Laravel  
  Laravel Version ................................................................................................................ 10.47.0  
  PHP Version ..................................................................................................................... 8.2.0  
  Composer Version ................................................................................................................. 2.6.5  
  Environment ...................................................................................................................... local  
  Debug Mode ..................................................................................................................... ENABLED  
  URL .......................................................................................................................... localhost  
  Maintenance Mode ................................................................................................................... OFF  

  Cache ..................................................................................................................................  
  Config ...................................................................................................................... NOT CACHED  
  Events ...................................................................................................................... NOT CACHED  
  Routes ...................................................................................................................... NOT CACHED  
  Views ....................................................................................................................... NOT CACHED  

  Drivers ................................................................................................................................  
  Broadcasting ....................................................................................................................... log  
  Cache ............................................................................................................................. file  
  Database ......................................................................................................................... mysql  
  Logs .................................................................................................................... stack / single  
  Mail .............................................................................................................................. smtp  
  Queue ............................................................................................................................. sync  
  Session ........................................................................................................................... file 

 「Laravel Version」という欄に注目すると「10.47.0」とあります。最新版のLaravelがインストールされました。念の為、トップページにアクセスしてみましょう。MAMPのドキュメントルートの設定方法は下記の記事を参考にしてください。

MAMP環境にLaravelをインストールする|Papagram
PHPのWebフレームワークの中で一番人気のLaravel(ララベル)をMAMP環境で使えるようにするまでの解説です。Laravelは筆者も業務で使用しており大変お気に入りです。  ちなみにフレームワークとは、Webアプリケーショの開発によ...

下記のような画面が表示されればOKです。

以上でLaravelのインストールは完了です。Gitを使用している場合はここでコミットします。

AdminLTE3のインストール

 管理画面のテンプレートとしてBootstrap4ベースのAdminLTEバージョン3を使用します。下記のGitHub URLにアクセスして、最新版(v3.2.0)のZipファイルをダウンロードしてください。

Releases · ColorlibHQ/AdminLTE
AdminLTE - Free admin dashboard template based on Bootstrap 5 - ColorlibHQ/AdminLTE

 ターミナルに戻り、プロジェクトフォルダのルートに移動してください。publicフォルダの下に、AdminLTEを配置するためのvendorフォルダを作成します。下記のコマンドを実行してください。

mkdir public/vendor

 さらに、vendorフォルダの中に「AdminLTE-3.2.0」フォルダを作成するため、下記のコマンドを実行してください。

mkdir public/vendor/AdminLTE-3.2.0

 下記のコマンドを実行し、「css」フォルダと「js」フォルダを作成してください。(1行ずつ実行してください。)

mkdir public/vendor/AdminLTE-3.2.0/css
mkdir public/vendor/AdminLTE-3.2.0/js

 先ほどダウンロードした「AdminLTE-3.2.0.zip」を解凍し、下記のファイルを先ほど作成したフォルダに移動してください。

  • 「dist/css/adminlte.min.css」をプロジェクトフォルダの「public/vendor/AdminLTE-3.2.0/css」フォルダの中に移動してください
  • 「dist/js/adminlte.min.js」をプロジェクトフォルダの「public/vendor/AdminLTE-3.2.0/js」フォルダの中に移動してください

 次に依存関係にあるファイルも配置します。AdminLTEはBootstrap4とjQueryに依存しています。プロジェクトフォルダの「public/vendor/AdminLTE-3.2.0」フォルダの中に「plugins」フォルダを作成します。下記のコマンドを実行してください。

mkdir public/vendor/AdminLTE-3.2.0/plugins

下記のコマンドを実行してください。(1行ずつ実行してください。)

mkdir public/vendor/AdminLTE-3.2.0/plugins/bootstrap
mkdir public/vendor/AdminLTE-3.2.0/plugins/jquery

それぞれのフォルダに必要なファイルを配置してください。
「bootstrap」フォルダに「js」フォルダを作成してください。

mkdir public/vendor/AdminLTE-3.2.0/plugins/bootstrap/js

 ダウンロードした「AdminLTE-3.2.0」より「plugins/bootstrap/js/bootstrap.bundle.min.js」を、上記で作成したプロジェクトフォルダの「public/vendor/AdminLTE-3.2.0/plugins/bootstrap/js」フォルダに移動してください。

 ダウンロードした「AdminLTE-3.2.0」より「plugins/jquery/jquery.min.js」を、上記で作成したプロジェクトフォルダの「public/vendor/AdminLTE-3.2.0/plugins/jquery」フォルダに移動してください。

 その他、管理画面の実装で必要になるプラグインファイルもここで配置しておきます。
 ダウンロードした「AdminLTE-3.2.0」より「plugins/fontawesome-free」フォルダをコピーし、プロジェクトフォルダの「public/vendor/AdminLTE-3.2.0/plugins」フォルダ内にペーストしてください。

 次に、プロジェクトフォルダの「public/vendor/AdminLTE-3.2.0/plugins」フォルダに「icheck-bootstrap」フォルダを作成してください。下記のコマンドを実行してください。

mkdir public/vendor/AdminLTE-3.2.0/plugins/icheck-bootstrap

 ダウンロードした「AdminLTE-3.2.0」より「plugins/icheck-bootstrap/icheck-bootstrap.min.css」をプロジェクトフォルダの「public/vendor/AdminLTE-3.2.0/plugins/icheck-bootstrap」フォルダに移動してください。

全ての作業が完了すると、「public」フォルダは下記のようなツリーになっています。

.
├── favicon.ico
├── index.php
├── robots.txt
└── vendor
    └── AdminLTE-3.2.0
        ├── css
        │   └── adminlte.min.css
        ├── js
        │   └── adminlte.min.js
        └── plugins
            ├── bootstrap
            │   └── js
            │       └── bootstrap.bundle.min.js
            ├── fontawesome-free
            │   ├── css
            │   │   ├── all.css
            │   │   ├── all.min.css
            │   │   ├── brands.css
            │   │   ├── brands.min.css
            │   │   ├── fontawesome.css
            │   │   ├── fontawesome.min.css
            │   │   ├── regular.css
            │   │   ├── regular.min.css
            │   │   ├── solid.css
            │   │   ├── solid.min.css
            │   │   ├── svg-with-js.css
            │   │   ├── svg-with-js.min.css
            │   │   ├── v4-shims.css
            │   │   └── v4-shims.min.css
            │   └── webfonts
            │       ├── fa-brands-400.eot
            │       ├── fa-brands-400.svg
            │       ├── fa-brands-400.ttf
            │       ├── fa-brands-400.woff
            │       ├── fa-brands-400.woff2
            │       ├── fa-regular-400.eot
            │       ├── fa-regular-400.svg
            │       ├── fa-regular-400.ttf
            │       ├── fa-regular-400.woff
            │       ├── fa-regular-400.woff2
            │       ├── fa-solid-900.eot
            │       ├── fa-solid-900.svg
            │       ├── fa-solid-900.ttf
            │       ├── fa-solid-900.woff
            │       └── fa-solid-900.woff2
            ├── icheck-bootstrap
            │   └── icheck-bootstrap.min.css
            └── jquery
                └── jquery.min.js

管理画面レイアウトの構築

 管理画面のURLは「/admin」から始まるものとします。「resources/views」フォルダ内に管理画面用のbladeファイルを配置する「admin」フォルダを作成します。下記のコマンドを実行してください。

mkdir  resources/views/admin

 今作成した「resources/views/admin」フォルダ内に「home.blade.php」を作成します。下記のコマンドを実行してください。

touch  resources/views/admin/home.blade.php

 上記でダウンロードした「AdminLTE-3.2.0」より「starter.html」というファイルの中身を、先ほど作成した「resources/views/admin/home.blade.php」にコピー&ペーストしてください。そして、CSSとJavaScriptの読み込みパスを調整しましょう。

15行目、17行目

<!-- Font Awesome Icons -->
<link rel="stylesheet" href="{{ asset('vendor/AdminLTE-3.2.0/plugins/fontawesome-free/css/all.min.css') }}">
<!-- Theme style -->
<link rel="stylesheet" href="{{ asset('vendor/AdminLTE-3.2.0/css/adminlte.min.css') }}">

348行目、350行目、352行目

<!-- jQuery -->
<script src="{{ asset('vendor/AdminLTE-3.2.0/plugins/jquery/jquery.min.js') }}"></script>
<!-- Bootstrap 4 -->
<script src="{{ asset('vendor/AdminLTE-3.2.0/plugins/bootstrap/js/bootstrap.bundle.min.js') }}"></script>
<!-- AdminLTE App -->
<script src="{{ asset('vendor/AdminLTE-3.2.0/js/adminlte.min.js') }}"></script>

 「/admin」というURLで画面にアクセスできるようにします。routes/web.phpに下記を追加してください。

Route::get('/admin', function () {
    return view('admin.home');
})->name('admin.home');

 コードの修正が完了したら、http://localhost:8888/adminにアクセスしてみましょう。下記のような画面が表示されたら成功です。画像はパスエラーでOKです。

 各ページのコンテンツ部分を除いた共通レイアウトをレイアウトファイルに切り出します。赤枠で囲んだ部分が各ページのコンテンツ部分になります。

 共通レイアウトはコンポーネント化します。「resources/views」フォルダの中に「components」フォルダを作成します。下記のコマンドを実行してください。

mkdir resources/views/components

さらにその中に「layouts」フォルダを作成します。下記のコマンドを実行してください。

mkdir resources/views/components/layouts

 「resources/views/components/layouts」フォルダの中に「admin.blade.php」を作成します。「resources/views/admin/home.blade.php」ファイルをコピーします。下記のコマンドを実行してください。

cp resources/views/admin/home.blade.php resources/views/components/layouts/admin.blade.php

「resources/views/components/layouts/admin.blade.php」を下記のように修正してください。

(省略)
<!-- Main content -->
    <div class="content">
       <div class="container-fluid">
           {{ $slot }}
       </div><!-- /.container-fluid -->
    </div>
<!-- /.content -->
(省略)

 「resources/views/admin/home.blade.php 」ファイルは中身を全て消して、下記のように修正してください。

<x-layouts.admin />

 http://localhost:8888/admin にアクセスしてください。コンテンツの中身が消えてレイアウト部分だけが表示されるようになります。

以上で管理画面のレイアウトの構築は完了です。

ユーザー登録画面の実装

 認証のバックエンド処理にLaravel Fortifyを使います。composerでインストールしましょう。下記のコマンドを実行してください。

composer require laravel/fortify

次にFortifyのリソースを公開します。下記のコマンドを実行してください。

php artisan vendor:publish --provider="Laravel\Fortify\FortifyServiceProvider"

サービスプロバイダーを追加します。「config/app.php」を下記のように修正してください。

'providers' => ServiceProvider::defaultProviders()->merge([
        /*
         * Package Service Providers...
         */

        /*
         * Application Service Providers...
         */
        App\Providers\AppServiceProvider::class,
        App\Providers\AuthServiceProvider::class,
        // App\Providers\BroadcastServiceProvider::class,
        App\Providers\EventServiceProvider::class,
        App\Providers\RouteServiceProvider::class,
        App\Providers\FortifyServiceProvider::class, // ココに追加
    ])->toArray(),

 続いて、マイグレーションを実行する必要がありますので、phpMyAdmin等を使ってデータベースの作成と.envへデータベースの設定値の登録を行ってください。データベースの連携方法が分からない場合は下記の記事を参考にしてください。

MAMP環境にLaravelをインストールする|Papagram
PHPのWebフレームワークの中で一番人気のLaravel(ララベル)をMAMP環境で使えるようにするまでの解説です。Laravelは筆者も業務で使用しており大変お気に入りです。  ちなみにフレームワークとは、Webアプリケーショの開発によ...

完了しましたら、下記のコマンドを実行してください。

php artisan migrate

 新規ユーザー登録画面用のbladeテンプレートを作成します。下記のコマンドを実行してください。(1行ずつ実行してください。)

mkdir resources/views/admin/auth
touch resources/views/admin/auth/register.blade.php

 上記でダウンロードした「AdminLTE-3.2.0」より「pages/examples/register.html」の中身を、今作成した「resources/views/admin/auth/register.blade.php」ファイルにコピー&ペーストしてください。CSSとJavaScriptのパスを下記のように調整してください。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>AdminLTE 3 | Registration Page</title>

    <!-- Google Font: Source Sans Pro -->
    <link rel="stylesheet"
        href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700&display=fallback">
    <!-- Font Awesome -->
    <link rel="stylesheet" href="{{ asset('vendor/AdminLTE-3.2.0/plugins/fontawesome-free/css/all.min.css') }}">
    <!-- icheck bootstrap -->
    <link rel="stylesheet" href="{{ asset('vendor/AdminLTE-3.2.0/plugins/icheck-bootstrap/icheck-bootstrap.min.css') }}">
    <!-- Theme style -->
    <link rel="stylesheet" href="{{ asset('vendor/AdminLTE-3.2.0/css/adminlte.min.css') }}">
</head>

<body class="hold-transition register-page">
(省略)
<!-- jQuery -->
<script src="{{ asset('vendor/AdminLTE-3.2.0/plugins/jquery/jquery.min.js') }}"></script>
<!-- Bootstrap 4 -->
<script src="{{ asset('vendor/AdminLTE-3.2.0/plugins/bootstrap/js/bootstrap.bundle.min.js') }}"></script>
<!-- AdminLTE App -->
<script src="{{ asset('vendor/AdminLTE-3.2.0/js/adminlte.min.js') }}"></script>
</body>
</html>

「routes/web.php」を下記のように修正してください。(後に設定するログイン用のルーティングも一緒に追記します。)

<?php

use Illuminate\Support\Facades\Route;
use Laravel\Fortify\Features;
use Laravel\Fortify\Http\Controllers\AuthenticatedSessionController;
use Laravel\Fortify\Http\Controllers\RegisteredUserController;
use Laravel\Fortify\RoutePath;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "web" middleware group. Make something great!
|
*/

(省略)

Route::group(['middleware' => config('fortify.middleware', ['web']), 'prefix' => 'admin', 'as' => 'admin.'], function () {
    $enableViews = config('fortify.views', true);

    // Authentication...
    if ($enableViews) {
        Route::get(RoutePath::for('login', '/login'), [AuthenticatedSessionController::class, 'create'])
            ->middleware(['guest'])
            ->name('login');
    }

    $limiter = config('fortify.limiters.login');

    Route::post(RoutePath::for('login', '/login'), [AuthenticatedSessionController::class, 'store'])
        ->middleware(array_filter([
            'guest',
            $limiter ? 'throttle:'.$limiter : null,
        ]));

    Route::post(RoutePath::for('logout', '/logout'), [AuthenticatedSessionController::class, 'destroy'])
        ->name('logout');

    // Registration...
    if (Features::enabled(Features::registration())) {
        if ($enableViews) {
            Route::get(RoutePath::for('register', '/register'), [RegisteredUserController::class, 'create'])
                ->middleware(['guest'])
                ->name('register');
        }

        Route::post(RoutePath::for('register', '/register'), [RegisteredUserController::class, 'store'])
            ->middleware(['guest']);
    }
});

「app/Providers/FortifyServiceProvider.php」を下記のように修正してください。(ログイン関連の設定も一緒に行います)

<?php

namespace App\Providers;

use App\Actions\Fortify\CreateNewUser;
use App\Actions\Fortify\ResetUserPassword;
use App\Actions\Fortify\UpdateUserPassword;
use App\Actions\Fortify\UpdateUserProfileInformation;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str;
use Laravel\Fortify\Fortify;

class FortifyServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        //
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        (省略)

        if (request()->is('admin') || request()->is('admin/*')) {
            Fortify::viewPrefix('admin.auth.');

            config([
                'fortify.home' => '/admin',
                'fortify.redirects.register' => '/admin',
                'fortify.redirects.logout' => '/admin/login',
            ]);
        }
    }
}

修正できたら、http://localhost:8888/admin/register にアクセスしてください。下記の画面が表示されれば成功です。

Facebookログインのボタン等は不要ならば削除してください。

フォームを修正します。

<div class="register-box">
        <div class="register-logo">
            <a href="{{ route('admin.home') }}"><b>Admin</b>LTE</a>
        </div>

        <div class="card">
            <div class="card-body register-card-body">
                <p class="login-box-msg">Register a new membership</p>

                <form action="{{ url('admin/register') }}" method="post">
                    @csrf
                    <div class="input-group mb-3">
                        <input type="text" name="name" class="form-control @error('name') is-invalid @enderror" value="{{ old('name') }}" placeholder="Full name" required>
                        <div class="input-group-append">
                            <div class="input-group-text">
                                <span class="fas fa-user"></span>
                            </div>
                        </div>
                        @error('name')
                            <div class="invalid-feedback">{{ $message }}</div>
                        @enderror
                    </div>
                    <div class="input-group mb-3">
                        <input type="email" name="email" class="form-control @error('email') is-invalid @enderror" value="{{ old('email') }}" placeholder="Email" required>
                        <div class="input-group-append">
                            <div class="input-group-text">
                                <span class="fas fa-envelope"></span>
                            </div>
                        </div>
                        @error('email')
                            <div class="invalid-feedback">{{ $message }}</div>
                        @enderror
                    </div>
                    <div class="input-group mb-3">
                        <input type="password" name="password" class="form-control @error('password') is-invalid @enderror"  placeholder="Password" required>
                        <div class="input-group-append">
                            <div class="input-group-text">
                                <span class="fas fa-lock"></span>
                            </div>
                        </div>
                        @error('password')
                            <div class="invalid-feedback">{{ $message }}</div>
                        @enderror
                    </div>
                    <div class="input-group mb-3">
                        <input type="password" name="password_confirmation" class="form-control" placeholder="Retype password" required>
                        <div class="input-group-append">
                            <div class="input-group-text">
                                <span class="fas fa-lock"></span>
                            </div>
                        </div>
                    </div>
                    <div class="row">
                        <div class="col-8">
                            <div class="icheck-primary">
                                <input type="checkbox" id="agreeTerms" name="terms" value="agree">
                                <label for="agreeTerms">
                                    I agree to the <a href="#">terms</a>
                                </label>
                            </div>
                        </div>
                        <!-- /.col -->
                        <div class="col-4">
                            <button type="submit" class="btn btn-primary btn-block">Register</button>
                        </div>
                        <!-- /.col -->
                    </div>
                </form>

                {{-- <div class="social-auth-links text-center">
                    <p>- OR -</p>
                    <a href="#" class="btn btn-block btn-primary">
                        <i class="fab fa-facebook mr-2"></i>
                        Sign up using Facebook
                    </a>
                    <a href="#" class="btn btn-block btn-danger">
                        <i class="fab fa-google-plus mr-2"></i>
                        Sign up using Google+
                    </a>
                </div> --}}

                <a href="{{ route('admin.login') }}" class="text-center">I already have a membership</a>
            </div>
            <!-- /.form-box -->
        </div><!-- /.card -->
    </div>
    <!-- /.register-box -->

 修正できたら、実際にユーザーを登録してみてください。ユーザー登録後、http://localhost:8888/admin にリダイレクトされれば成功です。

ログアウトの実装

 レイアウトの左サイドメニューの最下部にログアウトのリンクを作成します。「resources/views/components/layouts/admin.blade.php」を下記のように修正してください。

(省略)
        <!-- Main Sidebar Container -->
        <aside class="main-sidebar sidebar-dark-primary elevation-4">
            <!-- Brand Logo -->
            <a href="index3.html" class="brand-link">
                <img src="dist/img/AdminLTELogo.png" alt="AdminLTE Logo" class="brand-image img-circle elevation-3"
                    style="opacity: .8">
                <span class="brand-text font-weight-light">AdminLTE 3</span>
            </a>

            <!-- Sidebar -->
            <div class="sidebar">
                <!-- Sidebar user panel (optional) -->
                <div class="user-panel mt-3 pb-3 mb-3 d-flex">
                    <div class="image">
                        <img src="dist/img/user2-160x160.jpg" class="img-circle elevation-2" alt="User Image">
                    </div>
                    <div class="info">
                        <a href="#" class="d-block">Alexander Pierce</a>
                    </div>
                </div>

                <!-- SidebarSearch Form -->
                <div class="form-inline">
                    <div class="input-group" data-widget="sidebar-search">
                        <input class="form-control form-control-sidebar" type="search" placeholder="Search"
                            aria-label="Search">
                        <div class="input-group-append">
                            <button class="btn btn-sidebar">
                                <i class="fas fa-search fa-fw"></i>
                            </button>
                        </div>
                    </div>
                </div>

                <!-- Sidebar Menu -->
                <nav class="mt-2">
                    <ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="menu"
                        data-accordion="false">
                        <!-- Add icons to the links using the .nav-icon class
               with font-awesome or any other icon font library -->
                        <li class="nav-item menu-open">
                            <a href="#" class="nav-link active">
                                <i class="nav-icon fas fa-tachometer-alt"></i>
                                <p>
                                    Starter Pages
                                    <i class="right fas fa-angle-left"></i>
                                </p>
                            </a>
                            <ul class="nav nav-treeview">
                                <li class="nav-item">
                                    <a href="#" class="nav-link active">
                                        <i class="far fa-circle nav-icon"></i>
                                        <p>Active Page</p>
                                    </a>
                                </li>
                                <li class="nav-item">
                                    <a href="#" class="nav-link">
                                        <i class="far fa-circle nav-icon"></i>
                                        <p>Inactive Page</p>
                                    </a>
                                </li>
                            </ul>
                        </li>
                        <li class="nav-item">
                            <a href="#" class="nav-link">
                                <i class="nav-icon fas fa-th"></i>
                                <p>
                                    Simple Link
                                    <span class="right badge badge-danger">New</span>
                                </p>
                            </a>
                        </li>
                        {{-- ここから --}}
                        <li class="nav-item">
                            <a href="#" class="nav-link" onclick="event.preventDefault(); document.querySelector('#logoutForm').submit();">
                                <i class="nav-icon fas fa-sign-out-alt"></i>
                                <p>ログアウト</p>
                            </a>
                            <form action="{{ route('admin.logout') }}" method="POST" style="display: none;" id="logoutForm">
                                @csrf
                            </form>
                        </li>
                        {{-- ここまで --}}
                    </ul>
                </nav>
                <!-- /.sidebar-menu -->
            </div>
            <!-- /.sidebar -->
        </aside>
(省略)

 実際にログアウトできるか試してみてください。http://localhost:8888/admin/login にリダイレクトされれば成功です。(現時点ではエラーが出ます。)

ログイン画面の実装

ログイン画面用のbladeテンプレートを作成します。下記のコマンドを実行してください。

touch resources/views/admin/auth/login.blade.php

 上記でダウンロードした「AdminLTE-3.2.0」より「pages/examples/login.html」の中身を、今作成した「resources/views/admin/auth/login.blade.php」ファイルにコピー&ペーストしてください。CSSとJavaScriptのパスを下記のように調整してください。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>AdminLTE 3 | Log in</title>

    <!-- Google Font: Source Sans Pro -->
    <link rel="stylesheet"
        href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700&display=fallback">
    <!-- Font Awesome -->
    <link rel="stylesheet" href="{{ asset('vendor/AdminLTE-3.2.0/plugins/fontawesome-free/css/all.min.css') }}">
    <!-- icheck bootstrap -->
    <link rel="stylesheet" href="{{ asset('vendor/AdminLTE-3.2.0/plugins/icheck-bootstrap/icheck-bootstrap.min.css') }}">
    <!-- Theme style -->
    <link rel="stylesheet" href="{{ asset('vendor/AdminLTE-3.2.0/css/adminlte.min.css') }}">
</head>

<body class="hold-transition login-page">
(省略)

    <!-- jQuery -->
    <script src="{{ asset('vendor/AdminLTE-3.2.0/plugins/jquery/jquery.min.js') }}"></script>
    <!-- Bootstrap 4 -->
    <script src="{{ asset('vendor/AdminLTE-3.2.0/plugins/bootstrap/js/bootstrap.bundle.min.js') }}"></script>
    <!-- AdminLTE App -->
    <script src="{{ asset('vendor/AdminLTE-3.2.0/js/adminlte.min.js') }}"></script>
</body>

</html>

フォームを修正します。

(省略)
<body class="hold-transition login-page">
    <div class="login-box">
        <div class="login-logo">
            <a href="../../index2.html"><b>Admin</b>LTE</a>
        </div>
        <!-- /.login-logo -->
        <div class="card">
            <div class="card-body login-card-body">
                <p class="login-box-msg">Sign in to start your session</p>

                <form action="{{ url('admin/login') }}" method="post" novalidate>
                    @csrf
                    <div class="input-group mb-3">
                        <input type="email" name="email" class="form-control @error('email') is-invalid @enderror" value="{{ old('email') }}" placeholder="Email" required>
                        <div class="input-group-append">
                            <div class="input-group-text">
                                <span class="fas fa-envelope"></span>
                            </div>
                        </div>
                        @error('email')
                            <div class="invalid-feedback">{{ $message }}</div>
                        @enderror
                    </div>
                    <div class="input-group mb-3">
                        <input type="password" class="form-control @error('password') is-invalid @enderror" name="password" placeholder="Password" required>
                        <div class="input-group-append">
                            <div class="input-group-text">
                                <span class="fas fa-lock"></span>
                            </div>
                        </div>
                        @error('password')
                            <div class="invalid-feedback">{{ $message }}</div>
                        @enderror
                    </div>
                    <div class="row">
                        <div class="col-8">
                            <div class="icheck-primary">
                                <input type="checkbox" id="remember">
                                <label for="remember">
                                    Remember Me
                                </label>
                            </div>
                        </div>
                        <!-- /.col -->
                        <div class="col-4">
                            <button type="submit" class="btn btn-primary btn-block">Sign In</button>
                        </div>
                        <!-- /.col -->
                    </div>
                </form>

                {{-- <div class="social-auth-links text-center mb-3">
                    <p>- OR -</p>
                    <a href="#" class="btn btn-block btn-primary">
                        <i class="fab fa-facebook mr-2"></i> Sign in using Facebook
                    </a>
                    <a href="#" class="btn btn-block btn-danger">
                        <i class="fab fa-google-plus mr-2"></i> Sign in using Google+
                    </a>
                </div> --}}
                <!-- /.social-auth-links -->

                {{-- <p class="mb-1">
                    <a href="forgot-password.html">I forgot my password</a>
                </p> --}}
                <p class="mb-0">
                    <a href="{{ route('admin.register') }}" class="text-center">Register a new membership</a>
                </p>
            </div>
            <!-- /.login-card-body -->
        </div>
    </div>
    <!-- /.login-box -->

    (省略)
</body>
(省略)

 修正できたら、実際にログインしてみてください。ログイン後、http://localhost:8888/admin にリダイレクトされれば成功です。

 管理画面をログインしていないとアクセスできないように保護しましょう。ルーティングにauthミドルウェアを追加します。「routes/web.php」を下記のように修正してください。

Route::get('/admin', function () {
    return view('admin.home');
})->middleware(['auth'])->name('admin.home');

 ログイン画面にリダイレクトされるように調整します。「app/Http/Middleware/Authenticate.php」を下記のように修正してください。

    protected function redirectTo(Request $request): ?string
    {
        return $request->expectsJson() ? null : route('admin.login');
    }

 ログイン中にログインフォームにアクセスされた場合、管理画面のホーム画面にリダイレクトされるように調整します。下記のように「app/Http/Middleware/RedirectIfAuthenticated.php」を修正してください。

    public function handle(Request $request, Closure $next, string ...$guards): Response
    {
        $guards = empty($guards) ? [null] : $guards;

        foreach ($guards as $guard) {
            if (Auth::guard($guard)->check()) {
                return redirect(route('admin.home'));
            }
        }

        return $next($request);
    }

 以上でログイン付きの管理画面の構築は完了です。あとは中身のページをどんどん追加していけばOKです。

ユーザー管理画面の追加

試しにユーザー管理の画面を追加してみましょう。

 まず管理画面用のコントローラーを作成します。下記のコマンドを実行してください。管理画面用のコントローラーと分かるように「Admin」というフォルダの中にコントローラーファイルは配置します。

php artisan make:controller Admin/UserController --resource --model=User

 次にルーティングを定義します。下記の行を消して

Route::get('/admin', function () {
    return view('admin.home');
})->middleware(['auth'])->name('admin.home');

下記の行を追加してください。

Route::group(['middleware' => ['auth'], 'prefix' => 'admin', 'as' => 'admin.'], function () {
    Route::get('/', fn() => view('admin.home'))->name('home');
    Route::resource('users', UserController::class);
});

ユーザー一覧画面から作成します。UserControllerのindexメソッドを下記のように修正してください。

$users = User::all();

return view('admin.users.index', compact('users'));

一覧画面用のbladeファイルを作成します。下記のコマンドを実行してください。(1行ずつ実行してください。)

mkdir resources/views/admin/users
touch resources/views/admin/users/index.blade.php

作成したbladeファイルを下記のように編集してください。

<x-layouts.admin>
    <x-slot:title>
        ユーザー一覧
    </x-slot>

    <x-slot:pageTitle>
        ユーザー一覧
    </x-slot>

    <x-slot:breadcrumb>
        <li class="breadcrumb-item active">ユーザー一覧</li>
    </x-slot>

    <div class="row">
        <div class="col-12">
            <div class="card">
                <div class="card-header">
                    <div class="card-tools">
                        <a href="{{ route('admin.users.create') }}" class="btn btn-primary btn-flat">新規登録</a>
                    </div>
                </div>
                <div class="card-body table-responsive p-0">
                    <table class="table table-head-fixed table-hover text-nowrap">
                        <thead>
                            <tr>
                                <th>ID</th>
                                <th>名前</th>
                                <th>メールアドレス</th>
                                <th class="text-center">操作</th>
                            </tr>
                        </thead>
                        <tbody>
                            @foreach ($users as $user)
                                <tr>
                                    <td>{{ $user->id }}</td>
                                    <td>{{ $user->name }}</td>
                                    <td>{{ $user->email }}</td>
                                    <td class="text-center">
                                        <div class="btn-group">
                                            <a href="{{ route('admin.users.show', $user) }}" class="btn btn-info btn-flat">詳細</a>
                                            <a href="{{ route('admin.users.edit', $user) }}" class="btn btn-success btn-flat">編集</a>
                                            <a class="btn btn-danger btn-flat" href="#"
                                                onclick="event.preventDefault(); if(confirm('このデータを削除してもよろしいですか?')) { document.getElementById('delete-form-{{ $user->id }}').submit(); }">削除</a>
                                            <form id="delete-form-{{ $user->id }}" action="{{ route('admin.users.destroy', $user) }}" method="POST" style="display: none;">
                                                @csrf
                                                @method('DELETE')
                                            </form>
                                        </div>
                                    </td>
                                </tr>
                            @endforeach
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </div>
</x-layouts.admin>

レイアウトファイルを調整します。「resources/views/components/layouts/admin.blade.php」を下記のように編集してください。

<!DOCTYPE html>
<!--
This is a starter template page. Use this page to start your new project from
scratch. This page gets rid of all links and provides the needed markup only.
-->
<html lang="ja">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>{{ $title ?? config('app.name') }}</title>

    <!-- Google Font: Source Sans Pro -->
    <link rel="stylesheet"
        href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700&display=fallback">
    <!-- Font Awesome Icons -->
    <link rel="stylesheet" href="{{ asset('vendor/AdminLTE-3.2.0/plugins/fontawesome-free/css/all.min.css') }}">
    <!-- Theme style -->
    <link rel="stylesheet" href="{{ asset('vendor/AdminLTE-3.2.0/css/adminlte.min.css') }}">
    {{ $style ?? '' }}
</head>

<body class="hold-transition sidebar-mini">
    <div class="wrapper">

        <!-- Navbar -->
        <nav class="main-header navbar navbar-expand navbar-white navbar-light">
            <!-- Left navbar links -->
            <ul class="navbar-nav">
                <li class="nav-item">
                    <a class="nav-link" data-widget="pushmenu" href="#" role="button"><i
                            class="fas fa-bars"></i></a>
                </li>
            </ul>
        </nav>
        <!-- /.navbar -->

        <!-- Main Sidebar Container -->
        <aside class="main-sidebar sidebar-dark-primary elevation-4">
            <!-- Brand Logo -->
            <a href="{{ route('admin.home') }}" class="brand-link">
                <img src="dist/img/AdminLTELogo.png" alt="AdminLTE Logo" class="brand-image img-circle elevation-3"
                    style="opacity: .8">
                <span class="brand-text font-weight-light">AdminLTE 3</span>
            </a>

            <!-- Sidebar -->
            <div class="sidebar">
                <!-- Sidebar Menu -->
                <nav class="mt-2">
                    <ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="menu"
                        data-accordion="false">
                        <li class="nav-item">
                            <a href="{{ route('admin.users.index') }}" class="nav-link">
                                <i class="nav-icon fas fa-users"></i>
                                <p>ユーザー管理</p>
                            </a>
                        </li>
                        <li class="nav-item">
                            <a href="#" class="nav-link" onclick="event.preventDefault(); document.querySelector('#logoutForm').submit();">
                                <i class="nav-icon fas fa-sign-out-alt"></i>
                                <p>ログアウト</p>
                            </a>
                            <form action="{{ route('admin.logout') }}" method="POST" style="display: none;" id="logoutForm">
                                @csrf
                            </form>
                        </li>
                    </ul>
                </nav>
                <!-- /.sidebar-menu -->
            </div>
            <!-- /.sidebar -->
        </aside>

        <!-- Content Wrapper. Contains page content -->
        <div class="content-wrapper">
            <!-- Content Header (Page header) -->
            <div class="content-header">
                <div class="container-fluid">
                    <div class="row mb-2">
                        <div class="col-sm-6">
                            <h1 class="m-0">{{ $pageTitle ?? '' }}</h1>
                        </div><!-- /.col -->
                        <div class="col-sm-6">
                            <ol class="breadcrumb float-sm-right">
                                @unless (request()->path() === 'admin')
                                    <li class="breadcrumb-item"><a href="{{ route('admin.home') }}">ホーム</a></li>
                                @endunless
                                {{ $breadcrumb ?? '' }}
                            </ol>
                        </div><!-- /.col -->
                    </div><!-- /.row -->
                </div><!-- /.container-fluid -->
            </div>
            <!-- /.content-header -->

            <!-- Main content -->
            <div class="content">
                <div class="container-fluid">
                    @if (session()->has('success'))
                        <div class="alert alert-success alert-dismissible">
                            <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
                            <h5><i class="icon fas fa-check"></i> Success!</h5>
                            {{ session()->pull('success') }}
                        </div>
                    @endif
                    @if (session()->has('error'))
                        <div class="alert alert-danger alert-dismissible">
                            <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
                            <h5><i class="icon fas fa-ban"></i> Error!</h5>
                            {{ session()->pull('error') }}
                        </div>
                    @endif
                    {{ $slot }}
                </div><!-- /.container-fluid -->
            </div>
            <!-- /.content -->
        </div>
        <!-- /.content-wrapper -->

        <!-- Main Footer -->
        <footer class="main-footer">
            <!-- Default to the left -->
            <strong>Copyright &copy; {{ date('Y') }} <a href="https://papagram.co.jp" target="_blank">株式会社パパグラム</a>.</strong> All rights
            reserved.
        </footer>
    </div>
    <!-- ./wrapper -->

    <!-- REQUIRED SCRIPTS -->

    <!-- jQuery -->
    <script src="{{ asset('vendor/AdminLTE-3.2.0/plugins/jquery/jquery.min.js') }}"></script>
    <!-- Bootstrap 4 -->
    <script src="{{ asset('vendor/AdminLTE-3.2.0/plugins/bootstrap/js/bootstrap.bundle.min.js') }}"></script>
    <!-- AdminLTE App -->
    <script src="{{ asset('vendor/AdminLTE-3.2.0/js/adminlte.min.js') }}"></script>
    {{ $script ?? '' }}
</body>

</html>

 修正が完了しましたら、http://localhost/admin/users にアクセスしてください。下記のような画面が表示されればOKです。

詳細画面を作成します。UserControllerのshowメソッドを下記のように修正してください。

return view('admin.users.show', compact('user'));

詳細画面用のbladeファイルを作成します。下記のコマンドを実行してください。

touch resources/views/admin/users/show.blade.php

作成したbladeファイルを下記のように編集してください。

<x-layouts.admin>
    <x-slot:title>
        ユーザー詳細
    </x-slot>

    <x-slot:pageTitle>
        ユーザー詳細
    </x-slot>

    <x-slot:breadcrumb>
        <li class="breadcrumb-item"><a href="{{ route('admin.users.index') }}">ユーザー一覧</a></li>
        <li class="breadcrumb-item active">ユーザー詳細</li>
    </x-slot>

    <div class="row">
        <div class="col-12">
            <div class="card">
                <div class="card-body">
                    <dl class="row">
                        <dt class="col-2">ID</dt>
                        <dd class="col-10">{{ $user->id }}</dd>
                        <dt class="col-2 mt-3">名前</dt>
                        <dd class="col-10 mt-3">{{ $user->name }}</dd>
                        <dt class="col-2 mt-3">メールアドレス</dt>
                        <dd class="col-10 mt-3">{{ $user->email }}</dd>
                        <dt class="col-2 mt-3">登録日時</dt>
                        <dd class="col-10 mt-3">{{ $user->created_at }}</dd>
                        <dt class="col-2 mt-3">更新日時</dt>
                        <dd class="col-10 mt-3">{{ $user->updated_at }}</dd>
                    </dl>
                </div>
                <div class="card-footer">
                    <a href="{{ route('admin.users.index') }}">戻る</a>
                </div>
            </div>
        </div>
    </div>
</x-layouts.admin>

新規登録画面を作成します。UserControllerのcreateメソッドを下記のように修正してください。

return view('admin.users.create');

新規登録画面用のbladeファイルを作成します。下記のコマンドを実行してください。

touch resources/views/admin/users/create.blade.php

作成したbladeファイルを下記のように編集してください。

<x-layouts.admin>
    <x-slot:title>
        ユーザー登録
    </x-slot>

    <x-slot:pageTitle>
        ユーザー登録
    </x-slot>

    <x-slot:breadcrumb>
        <li class="breadcrumb-item"><a href="{{ route('admin.users.index') }}">ユーザー一覧</a></li>
        <li class="breadcrumb-item active">ユーザー登録</li>
    </x-slot>

    <div class="row">
        <div class="col-12">
            <div class="card">
                <form action="{{ route('admin.users.store') }}" method="POST">
                    <div class="card-body">
                        @csrf
                        <div class="form-group">
                            <label class="required" for="userName">名前</label>
                            <input type="text" name="name" class="form-control @error('name') is-invalid @enderror" id="userName" value="{{ old('name') }}" required>
                            @error('name')
                                <div class="invalid-feedback">{{ $message }}</div>
                            @enderror
                        </div>
                        <div class="form-group">
                            <label for="userEmail">メールアドレス</label>
                            <input type="email" name="email" class="form-control @error('email') is-invalid @enderror" id="userEmail" value="{{ old('email') }}" required>
                            @error('email')
                                <div class="invalid-feedback">{{ $message }}</div>
                            @enderror
                        </div>
                        <div class="form-group">
                            <label for="userPassword">パスワード</label>
                            <input type="password" name="password" class="form-control @error('password') is-invalid @enderror" id="userPassword" required>
                            @error('password')
                                <div class="invalid-feedback">{{ $message }}</div>
                            @enderror
                        </div>
                        <div class="form-group">
                            <label for="userPasswordConfirmation">パスワード(確認)</label>
                            <input type="password" name="password_confirmation" class="form-control @error('password_confirmation') is-invalid @enderror" id="userPasswordConfirmation" required>
                            @error('password_confirmation')
                                <div class="invalid-feedback">{{ $message }}</div>
                            @enderror
                        </div>
                    </div>
                    <div class="card-footer">
                        <button type="submit" class="btn btn-primary btn-flat">登録</button>
                    </div>
                </form>
            </div>
        </div>
    </div>
</x-layouts.admin>

UserControllerのstoreメソッドを下記のように修正してください。

    public function store(Request $request, CreateNewUser $creator)
    {
        $user = $creator->create($request->all());

        return redirect()->route('admin.users.show', $user)->with('success', 'ユーザーを登録しました。');
    }

 修正できましたら、実際にユーザーを登録してみてください。登録後、ユーザー詳細画面にリダイレクトされれば成功です。

 編集画面を作成します。編集対象は名前とメールアドレスとし、パスワードの更新は本記事では扱いません。UserControllerのeditメソッドを下記のように修正してください。

return view('admin.users.edit', compact('user'));

編集画面用のbladeファイルを作成します。下記のコマンドを実行してください。

touch resources/views/admin/users/edit.blade.php

作成したbladeファイルを下記のように編集してください。

<x-layouts.admin>
    <x-slot:title>
        ユーザー編集
    </x-slot>

    <x-slot:pageTitle>
        ユーザー編集
    </x-slot>

    <x-slot:breadcrumb>
        <li class="breadcrumb-item"><a href="{{ route('admin.users.index') }}">ユーザー一覧</a></li>
        <li class="breadcrumb-item active">ユーザー編集</li>
    </x-slot>

    <div class="row">
        <div class="col-12">
            <div class="card">
                <form action="{{ route('admin.users.update', $user) }}" method="POST">
                    <div class="card-body">
                        @csrf
                        @method('PATCH')
                        <div class="form-group">
                            <label class="required" for="userName">名前</label>
                            <input type="text" name="name" class="form-control @error('name') is-invalid @enderror" id="userName" value="{{ old('name', $user->name) }}" required>
                            @error('name')
                                <div class="invalid-feedback">{{ $message }}</div>
                            @enderror
                        </div>
                        <div class="form-group">
                            <label for="userEmail">メールアドレス</label>
                            <input type="email" name="email" class="form-control @error('email') is-invalid @enderror" id="userEmail" value="{{ old('email', $user->email) }}" required>
                            @error('email')
                                <div class="invalid-feedback">{{ $message }}</div>
                            @enderror
                        </div>
                    </div>
                    <div class="card-footer">
                        <button type="submit" class="btn btn-primary btn-flat">更新</button>
                    </div>
                </form>
            </div>
        </div>
    </div>
</x-layouts.admin>

バリデーション実施のためにフォームリクエストを作成します。下記のコマンドを実行してください。

php artisan make:request Admin/UpdateUserRequest

「app/Http/Requests/Admin/UpdateUserRequest.php」を下記のように修正してください。

<?php

namespace App\Http\Requests\Admin;

use App\Models\User;
use Illuminate\Validation\Rule;
use Illuminate\Foundation\Http\FormRequest;

class UpdateUserRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'name' => ['required', 'string', 'max:255'],
            'email' => [
                'required',
                'string',
                'email',
                'max:255',
                Rule::unique(User::class),
            ],
        ];
    }
}

UserControllerのupdateメソッドを下記のように修正してください。

    public function update(UpdateUserRequest $request, User $user)
    {
        $user->fill($request->except(['_token', '_method']))->save();

        return redirect()->route('admin.users.index')->with('success', 'ユーザー情報の更新に成功しました。');
    }

修正が完了しましたら、実際にユーザー情報を更新してみてください。

削除処理を実装します。UserControllerのdestroyメソッドを下記のように修正してください。

$user->delete();

return redirect()->route('admin.users.index')->with('success', 'ユーザーを削除しました。');

修正できましたら、ユーザー一覧画面より実際にユーザーデータを削除してみてください。

以上で解説は終了です。お疲れさまでした。

案件のご依頼、ご相談はコチラ

CONTACT | 株式会社パパグラム
お問い合わせはこちらから | 西新宿にあるWebシステム開発会社です。代表の地元である京都府城陽市、宇治市、久御山町周辺のご要望にも対応可能です。“パソコンで 効率化する おてつだい”いたします。PHP / Laravel / Vue.js...
株式会社パパグラム
西新宿にあるWebシステム開発会社です。代表の地元である京都府城陽市、宇治市、久御山町周辺のご要望にも対応可能です。“パソコンで 効率化する おてつだい”いたします。PHP / Laravel / Vue.js / Rails etc...

コメント

タイトルとURLをコピーしました