Skip to content

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():

php
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:

php
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ètodeDescripció
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ó:

php
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ó:

php
RelationManager::make('tags')
    ->filters([
        TernaryFilter::make('is_primary')->label('Primary'),
    ])

Consulta i eager loading ​

php
->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):

php
->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 ​

php
AttachAction::make(); // icona 'plus', standalone(), schema amb un camp 'id'

Ve amb un handler per defecte:

php
$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 ​

php
DetachAction::make(); // icona 'x-mark', requiresConfirmation()

Handler per defecte:

php
$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 ​

php
$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().