Skip to content

Resources ​

Un Resource és una classe PHP que descriu un model Eloquent: quins camps té el seu formulari, quines columnes té la seva taula, quins filtres, quines accions i quines relacions es gestionen des del seu panell. Arrel converteix aquesta descripció en un endpoint /schema que el frontend renderitza dinàmicament; no generes cap fitxer Vue.

php
final class PostResource extends Resource
{
    protected static string $model = Post::class;

    public function fields(): array { /* ... */ }

    public function columns(): array { /* ... */ }
}

fields() i columns() són abstractes: tot Resource els ha d'implementar. actions(), filters() i relations() tenen una implementació per defecte que retorna un array buit, els afegeixes només quan els necessites.

Identitat ​

MètodePer defecteDescripció
model() (estàtic)CapRetorna static::$model. Cal declarar protected static string $model.
slug() (estàtic)Str::plural(Str::kebab(...)) del nom de la classe sense el sufix ResourceDeclara protected static ?string $slug per sobreescriure'l. PostResource → posts.
label()Str::headline(...) del nom de la classe sense ResourcePostResource → «Post». Sobreescriu-lo per traduir-lo.
pluralLabel()Str::plural($this->label())
php
final class PostResource extends Resource
{
    protected static string $model = Post::class;
    protected static ?string $slug = 'articles';

    public function label(): string
    {
        return __('posts.model_label');
    }
}

La query base ​

php
public function getEloquentQuery(): Builder
{
    $model = static::model();

    return $model::query();
}

Sobreescriu-lo per aplicar qualsevol scope global al llistat i a la cerca de registres (visibilitat per rol, soft deletes, relacions precarregades amb with()...). És un mètode normal de la classe: pots cridar $this->user() o $this->tenant() (vegeu més avall) des d'aquí.

php
public function defaultSort(): ?string
{
    return '-published_at'; // el prefix `-` inverteix l'ordre
}

Autorització ​

Arrel no té un DSL d'autorització propi: els cinc mètodes de comprovació deleguen directament a la Policy de Laravel del model, quan n'hi ha una registrada.

php
public function canViewAny(): bool
{
    return $this->authorize('viewAny', static::model());
}

public function canView(Model $record): bool { /* 'view' */ }
public function canCreate(): bool { /* 'create' */ }
public function canEdit(Model $record): bool { /* 'update' */ }
public function canDelete(Model $record): bool { /* 'delete' */ }

El comportament exacte:

php
protected function authorize(string $ability, Model|string $arg): bool
{
    if (Gate::getPolicyFor($arg) === null) {
        return true;
    }

    return Gate::forUser($this->user())->allows($ability, $arg);
}

Sense Policy registrada per al model, tot queda permès per a qualsevol usuari autenticat. En tenir-ne una, Arrel hi delega: no cal aprendre res nou, els mateixos mètodes viewAny/view/create/update/delete que ja fas servir a la resta de l'aplicació. Nota que canEdit() comprova l'habilitat update (no edit), el nom que Laravel fa servir per convenció a les seves Policies.

Sobreescriu qualsevol dels cinc mètodes si necessites lògica diferent de la teva Policy per al context del panell:

php
public function canDelete(Model $record): bool
{
    return $this->user()->can('posts.delete')
        && ! $record->isProtected();
}

canViewAny() tanca el Resource sencer. Si retorna fals, tots els endpoints del Resource responen 403 (llistat, esquema, creació, edició, esborrat, accions, relacions i opcions dels camps), no només el llistat. Per això, un Resource que ha de ser restringit només necessita una Policy amb viewAny, o un canViewAny() propi. Vegeu Seguretat i autorització.

Quatre mètodes controlen com apareix el Resource fora del seu propi formulari/taula. Tots retornen null per defecte (sense icona, sense grup, sense ordre explícit, sense títol per registre):

php
public function icon(): ?string
{
    return 'document-text';
}

public function navigationGroup(): ?string
{
    return 'school';
}

public function navigationSort(): ?int
{
    return 10;
}

public function recordTitleAttribute(): ?string
{
    return 'title';
}

Són mètodes d'instància normals, com label()/pluralLabel(), no propietats estàtiques: la icona o la posició a la navegació és presentació, no identitat.

  • icon() ha de ser un dels noms del subconjunt curat de Heroicons (vegeu Accions → Icones); un nom que no hi és no renderitza res.
  • navigationSort() ordena els Resources de manera ascendent; un que no en declara cap s'ordena després de tots els que sí, mantenint el seu ordre de registre original respecte als altres sense ordre. Després s'agrupen per navigationGroup(), en l'ordre en què cada grup apareix per primer cop en aquesta llista ja ordenada, no alfabèticament. Un grup null és el seu propi calaix (els Resources sense grup no es barregen ni es descarten).
  • recordTitleAttribute() anomena un camp o columna que es mostra a la capçalera de la pàgina d'edició/vista d'un registre concret (p. ex. «Edit Post: Guia d'instal·lació») quan el registre carregat en té un valor. Sense definir-lo, la capçalera es queda amb el «Edit {label}» genèric.

Lifecycle hooks ​

Els hooks envolten les peticions de crear, actualitzar i esborrar un registre:

php
public function mutateDataBeforeCreate(array $data): array;
public function handleRecordCreation(array $data): ?Model;
public function afterCreate(Model $record): void;

public function mutateDataBeforeUpdate(array $data, Model $record): array;
public function handleRecordUpdate(Model $record, array $data): bool;
public function afterUpdate(Model $record): void;

public function handleRecordDeletion(Model $record): bool;

public function mutateDataBeforeFill(array $data, Model $record): array;

mutateDataBeforeCreate()/mutateDataBeforeUpdate() s'executen després de la validació i abans d'escriure el registre, i retornen l'array que efectivament es persisteix. afterCreate()/afterUpdate() s'executen un cop desat (i, en el cas d'afterCreate(), recarregat), així que $record ja té la seva clau primària.

php
public function mutateDataBeforeCreate(array $data): array
{
    if (isset($data['category'])) {
        $data['category'] = strtolower($data['category']);
    }

    return $data;
}

public function afterCreate(Model $record): void
{
    if (! $record instanceof Post || $record->tags()->exists()) {
        return;
    }

    $tag = Tag::firstOrCreate(['name' => 'general']);
    $record->tags()->attach($tag->id);
}

Substituir com s'escriu el registre ​

handleRecordCreation(), handleRecordUpdate() i handleRecordDeletion() substitueixen l'escriptura per defecte (Model::create(), update() i delete()) quan el model no es pot desar així: un servei de domini, una transacció pròpia, un model que s'esborra amb una regla de negoci.

  • handleRecordCreation(array $data): ?Model ha de retornar el model creat. Amb null, Arrel crea el registre amb Model::create().
  • handleRecordUpdate(Model $record, array $data): bool retorna true si ja ha fet l'actualització. Amb false, Arrel fa $record->update($data).
  • handleRecordDeletion(Model $record): bool retorna true si ja ha fet l'esborrat. Amb false, Arrel fa $record->delete().
php
public function handleRecordCreation(array $data): ?Model
{
    return app(CreatePost::class)->handle($data);
}

Una RuntimeException llançada durant l'esborrat es respon amb un 422 i el seu missatge.

Omplir el formulari d'edició ​

mutateDataBeforeFill() rep les dades que s'envien al formulari d'edició d'un registre i retorna les que realment s'hi mostren. Serveix per calcular valors que no són una columna (per exemple, el text d'un camp Placeholder):

php
public function mutateDataBeforeFill(array $data, Model $record): array
{
    $data['summary'] = "{$record->comments()->count()} comments";

    return $data;
}

Tots els hooks tenen una implementació per defecte que no fa res; només els sobreescrius quan els necessites.

Pàgina de detall ​

Quan un Resource té una ViewAction, el registre es mostra en una pàgina de només lectura. Per defecte, aquesta pàgina reutilitza les columns() del Resource. Per organitzar-la en seccions, declara infolist():

php
public function infolist(): array
{
    return [
        InfolistSection::make('Post')
            ->columns(2)
            ->schema([
                Column::make('title'),
                Column::make('status')->as('badge'),
            ]),
        InfolistSection::make('Dates')
            ->schema([
                Column::make('published_at')->as('date')->displayFormat('d/m/Y'),
            ]),
    ];
}

Cada entrada és una Column, amb el mateix as(), value() i color(). InfolistSection::make($label) accepta columns(int) per a la graella. Amb infolist() buit (el valor per defecte), la vista fa servir columns().

Helpers disponibles ​

Dins de qualsevol mètode del Resource tens accés a:

  • $this->user(): ?Authenticatable: l'usuari autenticat actual.
  • $this->tenant(): mixed: el resultat de TenantResolver::resolve() si n'hi ha un enllaçat al contenidor (vegeu Multi-tenancy); null si no.

Següents peces ​

Un Resource es construeix combinant: