Actions
Les actions sont ce qu'on peut faire avec un enregistrement (ou avec le Resource entier). Elles se déclarent dans actions() :
public function actions(): array
{
return [
ViewAction::make(),
EditAction::make(),
DeleteAction::make()->bulk(),
RestoreAction::make(),
ForceDeleteAction::make(),
Action::make('publish')
->icon('check')
->requiresConfirmation()
->bulk()
->handle(function (array $data, Post $post): array {
$post->update(['is_published' => true]);
return $post->only(['id', 'title', 'is_published']);
}),
Action::make('purge_drafts')
->label('Purge drafts')
->icon('trash')
->standalone()
->requiresConfirmation()
->handle(function (array $data): array {
return ['deleted' => Post::where('is_published', false)->delete()];
}),
];
}L'API d'Action
| Méthode | Description |
|---|---|
make(string $name) | Nom interne de l'action (il apparaît dans l'URL de l'endpoint). |
label(string $label) | Libellé du bouton. Par défaut, Str::headline($name). |
icon(string $icon) | Nom d'icône (voir « Icônes » plus bas). |
standalone(bool = true) | Action sur le Resource entier, pas sur un enregistrement précis (par exemple, « Purge drafts »). |
bulk(bool = true) | Peut s'appliquer à une sélection de plusieurs enregistrements à la fois ; réutilise le même endpoint par enregistrement, il n'y a pas d'API bulk séparée. |
requiresConfirmation(bool = true) | Le frontend demande une confirmation avant de l'exécuter. |
withTrashed(bool = true) | L'action opère sur des enregistrements en soft delete (nécessaire pour restore/forceDelete). |
authorize(Closure $callback) | Décide si l'action est visible et exécutable. Reçoit les mêmes arguments que handle(). Voir « Autorisation ». |
schema(array<Field> $fields) | Champs d'un formulaire propre à l'action. Voir « Actions avec formulaire ». |
url(Closure $callback, bool $openInNewTab = false) | Transforme l'action en lien. Voir « Actions qui sont un lien ». |
handle(Closure $callback) | La logique de l'action. Reçoit (array $data, Model $record) pour les actions sur un enregistrement, ou seulement (array $data) pour standalone(). |
Autorisation
- Avec
authorize(), l'action utilise cette closure et rien d'autre. - Sans
authorize(), l'action exigecanEdit()sur l'enregistrement, oucanCreate()si elle eststandalone(). Une action qui doit rester ouverte à des utilisateurs qui ne peuvent pas modifier doit déclarerauthorize()explicitement. - Les actions intégrées suivent leur habilité :
ViewActionaveccanView(),EditActionetRestoreActionaveccanEdit(),DeleteActionetForceDeleteActionaveccanDelete(). Si vous déclarezauthorize()sur l'une d'elles, les deux conditions doivent être remplies. - Un Resource que l'utilisateur ne peut pas lister (
canViewAny()faux) ferme toutes ses actions.
Actions avec formulaire
schema() demande des données à l'utilisateur avant d'exécuter l'action. Une action standalone() avec schema() ouvre le formulaire sans aucun enregistrement :
Action::make('invite')
->label('Invite people')
->icon('paper-airplane')
->standalone()
->schema([
Tags::make('emails')
->itemRules('email')
->rules('required', 'array'),
Select::make('role')
->options(['editor' => 'Editor', 'viewer' => 'Viewer'])
->rules('required'),
])
->handle(function (array $data): array {
Invitations::send($data['emails'], $data['role']);
return ['message' => count($data['emails']).' invitations sent'];
})handle() reçoit les données déjà validées par rapport aux règles des champs. Les erreurs de validation s'affichent avec le libellé du champ, pas avec son nom interne. Les champs Relation d'une action ne servent leurs options qu'aux utilisateurs qui peuvent l'exécuter, et leur query() s'applique aussi à l'envoi du formulaire.
Messages de l'action
Si le handler retourne un tableau avec une clé message de type texte, le panneau l'affiche à l'utilisateur une fois l'action terminée. Les autres clés sont retournées telles quelles au frontend. Un handler qui ne retourne rien répond { "success": true }.
Actions qui sont un lien
Avec url(), une action n'exécute aucun handler : elle navigue. La closure reçoit l'enregistrement et retourne l'URL, ou null pour ne pas afficher l'action sur cette ligne.
Action::make('open_public')
->label('Open public page')
->icon('eye')
->url(fn (Post $post): ?string => $post->is_published ? route('posts.show', $post) : null, openInNewTab: true)Actions intégrées
ViewAction, EditAction, DeleteAction, RestoreAction et ForceDeleteAction sont des Action avec une icône (et, pour les deux dernières, withTrashed()/requiresConfirmation()) prédéfinies :
ViewAction::make(); // icône 'eye'
EditAction::make(); // icône 'pencil'
DeleteAction::make(); // icône 'trash'
RestoreAction::make(); // icône 'arrow-uturn-left', withTrashed()
ForceDeleteAction::make(); // icône 'trash', withTrashed(), requiresConfirmation()DeleteAction, RestoreAction et ForceDeleteAction reçoivent un handler par défaut si vous n'en déclarez aucun : supprimer/restaurer/forcer la suppression de l'enregistrement via Eloquent (en respectant SoftDeletes quand le modèle l'utilise). Il n'est nécessaire d'appeler handle() explicitement que si vous voulez remplacer ce comportement.
Bloquer une suppression depuis le modèle : si un listener de l'événement deleting de votre modèle lève une RuntimeException (par exemple, « il a encore des dépendants »), l'endpoint de suppression l'attrape et répond 422 avec le message de l'exception, au lieu d'un 500 sans corps que le frontend ne pourrait pas afficher. C'est le même schéma que celui de Filament avec DeleteAction::configureUsing().
protected static function booted(): void
{
static::deleting(function (AcademicYear $year): void {
if ($year->courses()->exists()) {
throw new RuntimeException('Ce cours a encore des matières.');
}
});
}AttachAction et DetachAction sont propres aux Relation managers ; elles y sont expliquées en détail.
Icônes
icon() prend un nom d'un sous-ensemble choisi de Heroicons qu'Arrel embarque déjà. Ce n'est pas tout Heroicons : seulement les icônes qu'Arrel utilise dans ses propres actions, widgets et navigation, pour ne pas payer de taille de bundle pour des icônes qu'aucun panneau n'utilise.
eye, trash, pencil, pencil-square, check, plus, plus-circle, x-mark, inbox, paper-airplane, key, envelope-open, arrow-uturn-left, document-text, home, arrow-right-on-rectangle, academic-cap, book-open, shield-check, squares-2x2, user-group, users, clipboard-document-check, bars-3, cog-6-tooth, chart-bar, calendar-days, calendar, chat-bubble-left-right, document-duplicate, information-circle, clock, user, exclamation-triangle, arrow-down-tray et bell.
Un nom hors de cette liste ne s'affiche pas. Les icônes se trouvent dans resources/js/support/icons.ts du paquet.
Actions standard vs. actions sur mesure
ViewAction/EditAction/DeleteAction n'ont rien de magique : ce sont des Action avec des valeurs par défaut pratiques. Toute action sur mesure se définit exactement de la même façon, avec son propre schema() et handle() :
Action::make('rename')
->icon('pencil-square')
->schema([
Text::make('title')->rules('required', 'string', 'max:255'),
])
->handle(function (array $data, Post $post): array {
$post->update(['title' => $data['title']]);
return $post->only(['id', 'title']);
})