laravelの認証機構を見てみよう(4) : layoutへの変数注入 〜 Userのcrudとっかかり

ま、今adminロールというものが定義されたわけなのでいわゆる「ユーザー管理」というのができたりできなかったりする可能性が出てきたわけだ。こういう画面に関しては結構、各案件ごとに画面のカスタムが必要だったりとややこしいので、あんまり完璧に作りすぎない方がよかったりもするから、何となくの方針を示せればいいなみたいな感じで。

現在のログイン後の画面


とまあBreezeにより提供されたDashboardが1つ見えるだけである。これではアプリも何もあったもんじゃないので、ユーザー管理への導線となるUsersとかいうメニューを作ってみたいとする。まあこれはもちろん単純に増やせばいいだけといえばそう。

resources/js/Layouts/AuthenticatedLayout.jsx

<div className="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
    <NavLink href={route('dashboard')} active={route().current('dashboard')}>
        Dashboard
    </NavLink>
    <NavLink href={route('dashboard')} active={route().current('dashboard')}>
        Users
    </NavLink>
</div>

まあ単純なコピペすぎていろいろおかしいんだけども、増やすだけならこんなもんやろ

ただ、今ログインしているrole=adminの奴しか、このUsersを出したくないとするとこれはまあまあ厳しいわけだ。

inertiaにおけるlaravelへの変数注入

まあそもそも、layoutでは以下の変数にはアクセスできるから

export default function Authenticated({ user, header, children }) {

userとかは本来はアクセス可能だ。

export default function Authenticated({ user, header, children }) {
    console.log(user);
開発ツールのConsoleによる表示

このように、rolesというプロパティーに入ってるっちゃ入ってるんだけど、メソッドはさすがにコールできないので面倒っちゃ面倒なんだよね。たとえば

user.hasRole("admin")

と書くことは不可能なわけだ

事前に加工しとく

ある程度reactとかと連携する場合このようなデーターの加工が必要となる事が結構ある。このデーターの受け渡しはどこでやればいいかというと

app/Http/Middleware/HandleInertiaRequests.php

    public function share(Request $request): array
    {
        return array_merge(parent::share($request), [
            'auth' => [
                'user' => $request->user(),
                'isAdmin' => $request->user()?->hasRole('admin') ?? false,
            ],
            'ziggy' => function () use ($request) {
                return array_merge((new Ziggy)->toArray(), [
                    'location' => $request->url(),
                ]);
            },
        ]);
    }

このような。

そしたら

export default function Authenticated({ user, header, children }) {
    const { props } = usePage();
    const [showingNavigationDropdown, setShowingNavigationDropdown] = useState(false);
略
                            <div className="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
                                <NavLink href={route('dashboard')} active={route().current('dashboard')}>
                                    Dashboard
                                </NavLink>
                                {props.auth.isAdmin && (
                                    <NavLink href={route('dashboard')} active={route().current('dashboard')}>
                                        Users
                                    </NavLink>
                                )}
                            </div>

こんな形にしておけばよいだろう、ただ

{props.auth.isAdmin && (
    <NavLink href={route('dashboard')} active={route().current('dashboard')}>
        Users
    </NavLink>
)}

これは見ての通り文字列を変更しただけのDashboardへの参照なので以降で変化させていくよ〜

リソースフルなUserControllerとroute

まあこの辺は自分で作れと

% ./vendor/bin/sail artisan make:controller UserController -m User -r

   INFO  Controller [app/Http/Controllers/UserController.php] created successfully.
% ./vendor/bin/sail artisan make:controller UserController -m User -r

   INFO  Controller [app/Http/Controllers/UserController.php] created successfully.

-mでモデル指定、-rでリソース系メソッドを追加。これをroutes/web.phpに割り当てる

Route::middleware('auth')->group(function () {
    Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
    Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
    Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');

    Route::resource('users', UserController::class);
});

そうすれば /users 以下がリソースフルになるので、CRUDを組み立てやすいはずだ。

ユーザー一覧

ではこのrouteにもとづいてメニューを書き換える

<div className="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
    <NavLink href={route('dashboard')} active={route().current('dashboard')}>
        Dashboard
    </NavLink>
    {props.auth.isAdmin && (
        <NavLink href={route('users.index')} active={route().current('users.index')}>
            Users
        </NavLink>
    )}
</div>

まあcurrentはあやしいとしても、とりあえずこんな感じでUserController@indexにジャンプできるが、今は何も書いてないので何もおきない

UserController@index

ではここでUserの一覧を取り出してみる。

app/Http/Controllers/UserController.php

<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;

use Inertia\Inertia;
use Inertia\Response;

class UserController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index(): Response
    {
        $users = User::all();

        return Inertia::render('Users/Index', ['users' => $users]);
    }

まあ、こんなようなコードになるだろう。

resources/js/Pages/Users/Index.jsx

ざくっと

import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import { Head, Link } from '@inertiajs/react';

export default function UserIndex({ auth, users }) {
    return (
        <AuthenticatedLayout
            user={auth.user}
            header={<h2 className="font-semibold text-xl text-gray-800 leading-tight">Users</h2>}
        >
            <Head title="Users" />

            <div className="py-12">
                <div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
                    <div className="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                        {/* User Table */}
                        <table className="min-w-full divide-y divide-gray-200">
                            <thead className="bg-gray-50">
                                <tr>
                                    <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                                        ID
                                    </th>
                                    <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                                        Email
                                    </th>
                                    <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                                        Name
                                    </th>
                                    <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                                        Action
                                    </th>
                                </tr>
                            </thead>
                            <tbody className="bg-white divide-y divide-gray-200">
                                {users.map((user, index) => (
                                    <tr key={index}>
                                        <td className="px-6 py-4 whitespace-nowrap">
                                            {user.id}
                                        </td>
                                        <td className="px-6 py-4 whitespace-nowrap">
                                            {user.email}
                                        </td>
                                        <td className="px-6 py-4 whitespace-nowrap">
                                            {user.name}
                                        </td>
                                        <td className="px-6 py-4 whitespace-nowrap">
                                            <Link href={route('users.show', user.id)} className="text-blue-600 hover:text-blue-900">
                                                    <button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
                                                        Details
                                                    </button>
                                            </Link>
                                        </td>
                                    </tr>
                                ))}
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        </AuthenticatedLayout>
    );
}


ユーザー一覧

UserController@show (詳細)

    public function show(User $user): Response
    {
        return Inertia::render('Users/Show', ['user' => $user]);
    }

resources/js/Pages/Users/Show.jsx

import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import { Head } from '@inertiajs/react';

export default function UserShow({ auth, user }) {
    return (
        <AuthenticatedLayout
            user={auth.user}
            header={<h2 className="font-semibold text-xl text-gray-800 leading-tight">User Details</h2>}
        >
            <Head title="User Details" />

            <div className="py-12">
                <div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
                    <div className="bg-white overflow-hidden shadow-sm sm:rounded-lg p-4">
                        <section>
                            <header>
                                <h2 className="text-lg font-medium text-gray-900">Profile Information</h2>
                                <p className="mt-1 text-sm text-gray-600">
                                    Information about this user.
                                </p>
                            </header>

                            <div className="mt-6 space-y-6">
                                <div>
                                    <h3 className="text-lg font-medium leading-6 text-gray-900">ID</h3>
                                    <p className="mt-2 text-lg text-gray-500">{user.id}</p>
                                </div>
                                <div>
                                    <h3 className="text-lg font-medium leading-6 text-gray-900">Email</h3>
                                    <p className="mt-2 text-lg text-gray-500">{user.email}</p>
                                </div>
                                <div>
                                    <h3 className="text-lg font-medium leading-6 text-gray-900">Name</h3>
                                    <p className="mt-2 text-lg text-gray-500">{user.name}</p>
                                </div>
                            </div>
                        </section>
                    </div>
                </div>
            </div>
        </AuthenticatedLayout>
    );
}

まあこういうviewの詳細をじゃんじゃん解説してると紙面が到底たりないので、描画後の画面のみ示す。

これはProfile編集画面を多いに参考にしたものである。

次回

まあよくみりゃUserのcurrentがあやしいし、roleも出てなければemailのverificationの状況もよーわからんという中で、もうちょいviewをチューニングしていこう。やっぱりこの手のものはアイコンがあるないというのも結構重要になってくるというわけであるね。

admin以外の人はリンクが出てないだけで普通にアクセスできますしね、この辺もちゃんとやってきましょー

いいなと思ったら応援しよう!