Relation managers
Un RelationManager gestiona una relació Eloquent del registre des del mateix panell del Resource pare, amb la seva pròpia taula (amb cerca i ordenació), formulari inline de crear/editar, autorització pròpia i accions. Es declara dins de relations():
public function relations(): array
{
return [
RelationManager::make('tags')
->form([
Section::make()->schema([
Text::make('name')->rules('required', 'string', 'max:255'),
]),
])
->query(function (EloquentRelation $query, Post $post): void {
$query->where('name', '!=', 'Excluded by query() hook');
})
->canCreate(fn (Post $post): bool => $post->title !== 'Locked')
->canDelete(fn (Post $post): bool => $post->title !== 'Locked')
->columns([
Column::make('name')->sortable()->searchable(),
Column::make('is_primary')
->as('boolean')
->value(fn (Tag $tag): bool => (bool) $tag->pivot->is_primary),
])
->actions([
AttachAction::make(),
DetachAction::make()
->authorize(fn (Model $parent): bool => $parent->tags()->count() > 1),
Action::make('create_and_attach')
->label('New tag')
->icon('plus-circle')
->standalone()
->schema([
Text::make('name')->rules('required', 'string', 'max:255'),
])
->handle(function (array $data, Post $post): array {
$tag = Tag::firstOrCreate(['name' => $data['name']]);
$post->tags()->syncWithoutDetaching([$tag->id]);
return $tag->only(['id', 'name']);
}),
]),
];
}RelationManager::make($relationship) pren el nom del mètode de relació al model Eloquent ($post->tags() en aquest cas). columns() fa servir exactament la mateixa API de Column que un Resource, sortable()/searchable() inclosos; els callbacks de value()/color() reben el model relacionat, no el pare (aquí, Tag, incloent el seu pivot).
Formulari inline de crear/editar
form() declara l'schema d'un formulari propi de la relació, reutilitzant el mateix trait HasSchemaFields que ja fan servir Resource i Page, la validació, el moviment de fitxers pujats i la sanitització de RichEditor arriben gratis, sense res més a connectar:
RelationManager::make('tags')
->form([
Section::make()->schema([
Text::make('name')->rules('required', 'string', 'max:255'),
]),
])Amb un form() declarat i canCreate()/canEdit() autoritzant-ho, el panell mostra un botó «New {label}» i cada fila es pot obrir per editar-se inline, sense passar per cap Action a mida. És un concepte diferent d'una acció standalone() com create_and_attach de l'exemple de dalt (que és una acció pròpia amb el seu propi schema()/handle()): tots dos poden conviure a la mateixa relació.
Etiquetes, creació i dades del pivot
| Mètode | Descripció |
|---|---|
label(string $label) | Títol de la secció. Per defecte, Str::headline() del nom de la relació. |
createLabel(string $label) | Text del botó de crear. Per defecte, «New» seguit de l'etiqueta. |
createUsing(Closure $callback) | Substitueix com es crea el registre relacionat. Vegeu més avall. |
editsPivot(bool $editsPivot = true) | El formulari d'edició desa les dades a la taula pivot d'una relació belongsToMany, no al model relacionat. |
createUsing() rep les dades validades i el registre pare, i ha de retornar el model creat. És el lloc per a una creació que no és un simple create() sobre la relació:
RelationManager::make('comments')
->createUsing(fn (array $data, Post $post): Comment => app(AddComment::class)->handle($post, $data))Si no retorna un model, es llança una LogicException. Una RuntimeException es respon amb un 422 i el seu missatge.
Filtres
filters() accepta els mateixos filtres que un Resource, i es mostren a la barra de la taula de la relació:
RelationManager::make('tags')
->filters([
TernaryFilter::make('is_primary')->label('Primary'),
])Consulta i eager loading
->query(function (EloquentRelation $query, Post $post): void {
$query->where('name', '!=', 'Excluded by query() hook');
})El callback rep la relació Eloquent en si (una instància de HasMany, BelongsToMany...), no un query builder pelat, perquè where()/orderBy()/with() s'hi encadenin exactament igual que ho farien a getEloquentQuery() d'un Resource. Ha de mutar la relació que rep, no intentar construir-ne i retornar-ne una altra.
No és opcional en la pràctica: una aplicació que desactiva el lazy loading fora de producció rebentarà a la primera columna d'un RelationManager que toqui una altra relació, perquè la query $parent->{relationship}() per defecte no precarrega res pel seu compte, és aquí on hi seria el with(...).
Autorització
Quatre closures, avaluades contra el registre pare, que apareixen en el sentit invers al d'un Resource (que avalua contra el registre ell mateix, no el seu pare):
->canView(fn (Post $post): bool => ...)
->canCreate(fn (Post $post): bool => $post->title !== 'Locked')
->canEdit(fn (Post $post): bool => ...)
->canDelete(fn (Post $post): bool => $post->title !== 'Locked')Sense declarar-ne cap, totes retornen true. A més, el Resource pare també ha de permetre l'operació: veure la relació exigeix canView() del pare, i crear, editar o esborrar-hi exigeix canEdit() del pare. canCreate()/canEdit() controlen el botó «New» i si una fila s'obre en clicar-la; el resultat de les quatre s'envia per fila com a _authorizedActions, així que el frontend no renderitza mai un botó d'una acció que sap que rebutjarà, abans calia clicar per descobrir un 403.
Accions dins d'una relació
actions() fa servir la mateixa API d'Action, amb dues específiques d'aquest context:
AttachAction
AttachAction::make(); // icona 'plus', standalone(), schema amb un camp 'id'Ve amb un handler per defecte:
$action->handle(fn (array $data, Model $parent): mixed => $parent->{$relationship}()->attach(
$data['id'],
Arr::except($data, ['id']),
));Qualsevol clau addicional al data (més enllà de id) es passa com a atributs del pivot a attach(), útil per a relacions belongsToMany amb columnes pivot pròpies.
DetachAction
DetachAction::make(); // icona 'x-mark', requiresConfirmation()Handler per defecte:
$action->handle(fn (array $data, Model $parent, Model $related): mixed => $parent->{$relationship}()->detach(
$related->getKey(),
));Nota que el handler rep tres arguments ($data, el pare i el registre relacionat), a diferència d'una acció normal sobre un Resource.
Accions a mida dins d'una relació
Igual que a un Resource, una Action::make(...) normal amb el seu propi schema()/handle() funciona igual dins d'un RelationManager, l'exemple de create_and_attach de dalt crea una Tag nova (o en reutilitza una existent per nom) i l'adjunta al Post, sense passar per AttachAction.
Consultar una relació des del Resource
$resource->relation('tags'); // ?RelationManager, per getRelationship() === 'tags'Accions per fila amb formulari i missatges
Una Action normal dins d'un Relation manager es mostra a cada fila, i admet les mateixes coses que a un Resource: schema() per demanar dades, url() per ser un enllaç i un missatge de retorn. El resultat d'un handler que retorna ['message' => '...'] es mostra al panell.
Els camps Relation d'un formulari o d'una acció d'un Relation manager reben el registre pare com a tercer argument del seu query().