アクセス制御の深掘り——canCreate/canEdit/canDeleteのカスタマイズ
FilamentのアクセスコントロールはPolicyと連動する
FilamentのResourceは、デフォルトでLaravelの認可ポリシー(Policy)を自動的に参照します。PostPolicyが存在すれば、Filamentの一覧・作成・編集・削除の各操作は自動的にポリシーでチェックされます。
まずポリシーの基本構造を確認しましょう。
php artisan make:policy PostPolicy --model=Post
namespace App\Policies;
use App\Models\Post;
use App\Models\User;
class PostPolicy
{
// 一覧の表示権限
public function viewAny(User $user): bool
{
return $user->hasPermissionTo('posts.viewAny');
}
// 詳細の表示権限
public function view(User $user, Post $post): bool
{
return $user->hasPermissionTo('posts.view');
}
// 作成権限
public function create(User $user): bool
{
return $user->hasPermissionTo('posts.create');
}
// 更新権限
public function update(User $user, Post $post): bool
{
// 管理者か、記事の著者のみ編集可能
return $user->hasRole('admin')
|| ($user->hasPermissionTo('posts.update') && $post->user_id === $user->id);
}
// 削除権限
public function delete(User $user, Post $post): bool
{
return $user->hasRole('admin');
}
// 一括削除権限(Filament専用)
public function deleteAny(User $user): bool
{
return $user->hasRole('admin');
}
// 論理削除済みレコードの復元権限
public function restore(User $user, Post $post): bool
{
return $user->hasRole('admin');
}
// 完全削除権限
public function forceDelete(User $user, Post $post): bool
{
return $user->hasRole('super_admin');
}
}
Resourceでのオーバーライド
ポリシーより詳細な制御が必要な場合は、Resourceクラスでcan系メソッドをオーバーライドします。
class PostResource extends Resource
{
/**
* 「新規作成」ボタンの表示制御
*/
public static function canCreate(): bool
{
return auth()->user()->hasAnyRole(['admin', 'editor']);
}
/**
* 各レコードの編集権限
*/
public static function canEdit(Model $record): bool
{
$user = auth()->user();
return $user->hasRole('admin')
|| ($user->hasRole('editor') && $record->user_id === $user->id);
}
/**
* 各レコードの削除権限
*/
public static function canDelete(Model $record): bool
{
return auth()->user()->hasRole('admin');
}
/**
* 一覧ページ(パネル全体)へのアクセス
*/
public static function canAccess(): bool
{
return auth()->user()->hasAnyRole(['admin', 'editor', 'viewer']);
}
/**
* テーブルから特定レコードを「閲覧」できるか
*/
public static function canView(Model $record): bool
{
return auth()->check();
}
}
アクションレベルでの制御
テーブルのカスタムActionにも認可チェックを組み込みます。
Action::make('publish')
->label('公開する')
->visible(fn (Post $record): bool =>
auth()->user()->can('publish', $record)
&& $record->status === 'draft'
)
->action(function (Post $record): void {
// アクション実行前にもチェック(可視性とは別に重要)
abort_unless(auth()->user()->can('publish', $record), 403);
$record->update([
'status' => 'published',
'published_at' => now(),
]);
})
ポリシーにpublishメソッドを追加:
// PostPolicy.php
public function publish(User $user, Post $post): bool
{
return $user->hasAnyRole(['admin', 'editor'])
&& $post->status === 'draft';
}
フォームフィールドの編集制御
特定フィールドをロールによって読み取り専用にする:
public static function form(Form $form): Form
{
return $form
->schema([
TextInput::make('title')
->label('タイトル')
->required(),
Select::make('status')
->label('ステータス')
->options([
'draft' => '下書き',
'published' => '公開中',
'archived' => 'アーカイブ',
])
// 管理者のみステータスを変更できる
->disabled(fn (): bool => ! auth()->user()->hasRole('admin'))
->dehydrated(true), // disabledでもフォーム送信時に値を含める
TextInput::make('internal_notes')
->label('内部メモ(管理者のみ)')
->visible(fn (): bool => auth()->user()->hasRole('admin')),
]);
}
テーブルカラムのアクセス制御
センシティブな情報を含むカラムをロールに応じて表示/非表示にします。
public function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('title')
->searchable(),
TextColumn::make('author.email')
->label('投稿者メール')
// 管理者のみ表示
->visible(fn (): bool => auth()->user()->hasRole('admin')),
TextColumn::make('status')
->badge(),
TextColumn::make('revenue')
->money('JPY')
// 特定権限がある場合のみ表示
->visible(fn (): bool => auth()->user()->hasPermissionTo('view.revenue')),
]);
}
FilamentShieldとの連携
bezhansalleh/filament-shieldパッケージを使うと、ResourceごとのCRUD権限をUIで管理できます。
composer require bezhansalleh/filament-shield
php artisan shield:install --fresh
php artisan shield:generate --all
インストール後、ShieldはResourceに応じた権限(view_any_post・create_post・update_post・delete_postなど)を自動生成し、ロールへの割り当てUIが管理画面に追加されます。
// Resourceでの有効化
class PostResource extends Resource
{
// PolicyはShieldが生成したPostPolicyを使う(自動)
// canCreate, canEdit, canDeleteは自動的にShieldの権限チェックと連動
public static function canAccess(): bool
{
return auth()->user()->can('view_any_post');
}
}
Gate(ゲート)との組み合わせ
ポリシーではなくGateでシンプルに権限定義する場合:
// app/Providers/AppServiceProvider.php
Gate::define('manage-settings', fn (User $user): bool =>
$user->hasRole('super_admin')
);
Gate::define('view-analytics', fn (User $user): bool =>
$user->hasAnyRole(['admin', 'analyst'])
);
// Filamentのカスタムページ
class Analytics extends Page
{
public static function canAccess(): bool
{
return Gate::allows('view-analytics');
}
}
コツ・注意点・ハマりポイント
コツ: ->visible()は表示/非表示のみでサーバー側の保護にはなりません。アクション実行時にabort_unless()でサーバー側でも必ず認可チェックを行ってください。
注意点: ->disabled(fn)でフィールドを無効化しても、dehydrated(false)を設定しない限りフォーム送信時に値が送られます。逆に->dehydrated(true)で明示することで、disabled状態でも値の更新ができます。
ハマりポイント: canDeleteAnyとcanDeleteは別メソッドです。一括削除ボタンを非表示にしたい場合はcanDeleteAnyも実装する必要があります。PolicyのdeleteAnyに対応しています。
まとめ
FilamentのアクセスコントロールはLaravelのPolicy・Gate・カスタムcan系メソッドの3層で構成されています。Policyで基本ルールを定義し、Resourceのオーバーライドで細かい例外を処理し、フォーム/テーブルレベルでUIの可視性を制御するという3段階のアプローチで堅牢な権限管理が実現できます。