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);
このように、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以外の人はリンクが出てないだけで普通にアクセスできますしね、この辺もちゃんとやってきましょー