Recursos (Resources)
Introducción
Los Resources son clases estáticas que se utilizan para construir interfaces CRUD para tus modelos Eloquent. Describen cómo los administradores deberían poder interactuar con los datos de tu aplicación usando tablas y formularios.
Creando un resource
Para crear un resource para el modelo App\Models\Customer
:
php artisan make:filament-resource Customer
Esto creará varios archivos en el directorio app/Filament/Resources
:
.
+-- Customers
| +-- CustomerResource.php
| +-- Pages
| | +-- CreateCustomer.php
| | +-- EditCustomer.php
| | +-- ListCustomers.php
| +-- Schemas
| | +-- CustomerForm.php
| +-- Tables
| | +-- CustomersTable.php
Tu nueva clase resource se encuentra en CustomerResource.php
.
Las clases en el directorio Pages
se usan para personalizar las páginas de la aplicación que interactúan con tu resource. Son componentes Livewire de página completa que puedes personalizar como desees.
Las clases en el directorio Schemas
se usan para definir el contenido de los formularios e infolists de tu resource. Las clases en el directorio Tables
se usan para construir la tabla de tu resource.
¿Has creado un resource pero no aparece en el menú de navegación? Si tienes una policy del modelo, asegúrate de retornar true
desde el método viewAny()
.
Resources simples (modales)
A veces, tus modelos son lo suficientemente simples como para que solo quieras gestionar registros en una página, usando modales para crear, editar y eliminar registros. Para generar un resource simple con modales:
php artisan make:filament-resource Customer --simple
Tu resource tendrá una página "Manage", que es una página List con modales añadidos.
Además, tu resource simple no tendrá método getRelations()
, ya que los relation managers solo se muestran en las páginas Edit y View, que no están presentes en resources simples. Todo lo demás es igual.
Generación automática de formularios y tablas
Si quieres ahorrar tiempo, Filament puede generar automáticamente el formulario y la tabla basándose en las columnas de la base de datos de tu modelo, usando --generate
:
php artisan make:filament-resource Customer --generate
Manejo de soft-deletes
Por defecto, no podrás interactuar con registros eliminados en la aplicación. Si quieres añadir funcionalidad para restaurar, forzar eliminación y filtrar registros eliminados en tu resource, usa la bandera --soft-deletes
al generar el resource:
php artisan make:filament-resource Customer --soft-deletes
Puedes encontrar más información sobre soft-deleting aquí.
Generando una página View
Por defecto, solo se generan las páginas List, Create y Edit para tu resource. Si también quieres una página View, usa la bandera --view
:
php artisan make:filament-resource Customer --view
Especificando un namespace personalizado del modelo
Por defecto, Filament asumirá que tu modelo existe en el directorio App\Models
. Puedes pasar un namespace diferente para el modelo usando la bandera --model-namespace
:
php artisan make:filament-resource Customer --model-namespace=Custom\Path\Models
En este ejemplo, el modelo debería existir en Custom\Path\Models\Customer
. Ten en cuenta las dobles barras invertidas \
en el comando que son requeridas.
Ahora al generar el resource, Filament podrá localizar el modelo y leer el esquema de la base de datos.
Generando el modelo, migración y factory al mismo tiempo
Si quieres ahorrar tiempo al crear tus resources, Filament también puede generar el modelo, migración y factory para el nuevo resource al mismo tiempo usando las banderas --model
, --migration
y --factory
en cualquier combinación:
php artisan make:filament-resource Customer --model --migration --factory
Títulos de registros
Se puede establecer un $recordTitleAttribute
para tu resource, que es el nombre de la columna en tu modelo que puede usarse para identificarlo de otros.
Por ejemplo, esto podría ser el title
de un post de blog o el name
de un cliente:
protected static ?string $recordTitleAttribute = 'name';
Esto es requerido para que funcionen características como búsqueda global.
Puedes especificar el nombre de un accessor de Eloquent si una sola columna es inadecuada para identificar un registro.
Formularios de resources
Las clases resource contienen un método form()
que se usa para construir los formularios en las páginas Create y Edit.
Por defecto, Filament crea un archivo de esquema de formulario para ti, que se referencia en el método form()
. Esto es para mantener tu clase resource limpia y organizada, de lo contrario puede volverse bastante grande:
use App\Filament\Resources\Customers\Schemas\CustomerForm;
use Filament\Schemas\Schema;
public static function form(Schema $schema): Schema
{
return CustomerForm::configure($schema);
}
En la clase CustomerForm
, puedes definir los campos y diseño de tu formulario:
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Schema;
public static function configure(Schema $schema): Schema
{
return $schema
->components([
TextInput::make('name')->required(),
TextInput::make('email')->email()->required(),
// ...
]);
}
El método components()
se usa para definir la estructura de tu formulario. Es un array de campos y componentes de diseño, en el orden que deberían aparecer en tu formulario.
Consulta la documentación de Forms para una guía sobre cómo construir formularios con Filament.
Si prefieres definir el formulario directamente en la clase resource, puedes hacerlo y eliminar la clase de esquema de formulario por completo:
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Schema;
public static function form(Schema $schema): Schema
{
return $schema
->components([
TextInput::make('name')->required(),
TextInput::make('email')->email()->required(),
// ...
]);
}
Ocultando componentes basado en la operación actual
El método hiddenOn()
de los componentes de formulario te permite ocultar dinámicamente campos basándose en la página o acción actual.
En este ejemplo, ocultamos el campo password
en la página edit
:
use Filament\Forms\Components\TextInput;
use Filament\Support\Enums\Operation;
TextInput::make('password')
->password()
->required()
->hiddenOn(Operation::Edit),
Alternativamente, tenemos un método de atajo visibleOn()
para mostrar un campo solo en una página o acción:
use Filament\Forms\Components\TextInput;
use Filament\Support\Enums\Operation;
TextInput::make('password')
->password()
->required()
->visibleOn(Operation::Create),
Tablas de resources
Las clases resource contienen un método table()
que se usa para construir la tabla en la página List.
Por defecto, Filament crea un archivo de tabla para ti, que se referencia en el método table()
. Esto es para mantener tu clase resource limpia y organizada, de lo contrario puede volverse bastante grande:
use App\Filament\Resources\Customers\Schemas\CustomersTable;
use Filament\Tables\Table;
public static function table(Table $table): Table
{
return CustomersTable::configure($table);
}
En la clase CustomersTable
, puedes definir las columnas, filtros y acciones de la tabla:
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\Filter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
public static function configure(Table $table): Table
{
return $table
->columns([
TextColumn::make('name'),
TextColumn::make('email'),
// ...
])
->filters([
Filter::make('verified')
->query(fn (Builder $query): Builder => $query->whereNotNull('email_verified_at')),
// ...
])
->recordActions([
EditAction::make(),
])
->toolbarActions([
BulkActionGroup::make([
DeleteBulkAction::make(),
]),
]);
}
Consulta la documentación de tables para descubrir cómo añadir columnas de tabla, filtros, acciones y más.
Si prefieres definir la tabla directamente en la clase resource, puedes hacerlo y eliminar la clase de tabla por completo:
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\Filter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('name'),
TextColumn::make('email'),
// ...
])
->filters([
Filter::make('verified')
->query(fn (Builder $query): Builder => $query->whereNotNull('email_verified_at')),
// ...
])
->recordActions([
EditAction::make(),
])
->toolbarActions([
BulkActionGroup::make([
DeleteBulkAction::make(),
]),
]);
}
Personalizando la etiqueta del modelo
Cada resource tiene una "etiqueta de modelo" que se genera automáticamente desde el nombre del modelo. Por ejemplo, un modelo App\Models\Customer
tendrá una etiqueta customer
.
La etiqueta se usa en varias partes de la UI, y puedes personalizarla usando la propiedad $modelLabel
:
protected static ?string $modelLabel = 'cliente';
Alternativamente, puedes usar getModelLabel()
para definir una etiqueta dinámica:
public static function getModelLabel(): string
{
return __('filament/resources/customer.label');
}
Personalizando la etiqueta plural del modelo
Los resources también tienen una "etiqueta plural del modelo" que se genera automáticamente desde la etiqueta del modelo. Por ejemplo, una etiqueta customer
se pluralizará en customers
.
Puedes personalizar la versión plural de la etiqueta usando la propiedad $pluralModelLabel
:
protected static ?string $pluralModelLabel = 'clientes';
Alternativamente, puedes establecer una etiqueta plural dinámica en el método getPluralModelLabel()
:
public static function getPluralModelLabel(): string
{
return __('filament/resources/customer.plural_label');
}
Capitalización automática de la etiqueta del modelo
Por defecto, Filament capitalizará automáticamente cada palabra en la etiqueta del modelo, para algunas partes de la UI. Por ejemplo, en títulos de página, el menú de navegación y las migas de pan.
Si quieres deshabilitar este comportamiento para un resource, puedes establecer $hasTitleCaseModelLabel
en el resource:
protected static bool $hasTitleCaseModelLabel = false;
Elementos de navegación del resource
Filament generará automáticamente un elemento del menú de navegación para tu resource usando la etiqueta plural.
Si quieres personalizar la etiqueta del elemento de navegación, puedes usar la propiedad $navigationLabel
:
protected static ?string $navigationLabel = 'Mis Clientes';
Alternativamente, puedes establecer una etiqueta de navegación dinámica en el método getNavigationLabel()
:
public static function getNavigationLabel(): string
{
return __('filament/resources/customer.navigation_label');
}
Estableciendo un ícono de navegación del resource
La propiedad $navigationIcon
soporta el nombre de cualquier componente Blade. Por defecto, Heroicons están instalados. Sin embargo, puedes crear tus propios componentes de íconos personalizados o instalar una librería alternativa si lo deseas.
use BackedEnum;
protected static string | BackedEnum | null $navigationIcon = 'heroicon-o-user-group';
Alternativamente, puedes establecer un ícono de navegación dinámico en el método getNavigationIcon()
:
use BackedEnum;
use Illuminate\Contracts\Support\Htmlable;
public static function getNavigationIcon(): string | BackedEnum | Htmlable | null
{
return 'heroicon-o-user-group';
}
Ordenando elementos de navegación del resource
La propiedad $navigationSort
te permite especificar el orden en que se listan los elementos de navegación:
protected static ?int $navigationSort = 2;
Alternativamente, puedes establecer un orden dinámico del elemento de navegación en el método getNavigationSort()
:
public static function getNavigationSort(): ?int
{
return 2;
}
Agrupando elementos de navegación del resource
Puedes agrupar elementos de navegación especificando una propiedad $navigationGroup
:
use UnitEnum;
protected static string | UnitEnum | null $navigationGroup = 'Tienda';
Alternativamente, puedes usar el método getNavigationGroup()
para establecer una etiqueta de grupo dinámica:
public static function getNavigationGroup(): ?string
{
return __('filament/navigation.groups.shop');
}
Agrupando elementos de navegación del resource bajo otros elementos
Puedes agrupar elementos de navegación como hijos de otros elementos, pasando la etiqueta del elemento padre como $navigationParentItem
:
use UnitEnum;
protected static ?string $navigationParentItem = 'Productos';
protected static string | UnitEnum | null $navigationGroup = 'Tienda';
Como se ve arriba, si el elemento padre tiene un grupo de navegación, ese grupo de navegación también debe definirse, para que el elemento padre correcto pueda ser identificado.
También puedes usar el método getNavigationParentItem()
para establecer una etiqueta de elemento padre dinámica:
public static function getNavigationParentItem(): ?string
{
return __('filament/navigation.groups.shop.items.products');
}
Si estás buscando un tercer nivel de navegación como este, deberías considerar usar clusters en su lugar, que son una agrupación lógica de resources y páginas personalizadas, que pueden compartir su propia navegación separada.
Generando URLs a páginas de resources
Filament proporciona el método estático getUrl()
en las clases resource para generar URLs a resources y páginas específicas dentro de ellos. Tradicionalmente, necesitarías construir la URL a mano o usando el helper route()
de Laravel, pero estos métodos dependen del conocimiento del slug del resource o convenciones de nomenclatura de rutas.
El método getUrl()
, sin argumentos, generará una URL a la página List del resource:
use App\Filament\Resources\Customers\CustomerResource;
CustomerResource::getUrl(); // /admin/customers
También puedes generar URLs a páginas específicas dentro del resource. El nombre de cada página es la clave del array en el array getPages()
del resource. Por ejemplo, para generar una URL a la página Create:
use App\Filament\Resources\Customers\CustomerResource;
CustomerResource::getUrl('create'); // /admin/customers/create
Algunas páginas en el método getPages()
usan parámetros de URL como record
. Para generar una URL a estas páginas y pasar un registro, deberías usar el segundo argumento:
use App\Filament\Resources\Customers\CustomerResource;
CustomerResource::getUrl('edit', ['record' => $customer]); // /admin/customers/edit/1
En este ejemplo, $customer
puede ser un objeto modelo Eloquent, o un ID.
Generando URLs a modales de resources
Esto puede ser especialmente útil si estás usando resources simples con solo una página.
Para generar una URL para una acción en la tabla del resource, deberías pasar tableAction
y tableActionRecord
como parámetros de URL:
use App\Filament\Resources\Customers\CustomerResource;
use Filament\Actions\EditAction;
CustomerResource::getUrl(parameters: [
'tableAction' => EditAction::getDefaultName(),
'tableActionRecord' => $customer,
]); // /admin/customers?tableAction=edit&tableActionRecord=1
O si quieres generar una URL para una acción en la página como un CreateAction
en el header, puedes pasarla al parámetro action
:
use App\Filament\Resources\Customers\CustomerResource;
use Filament\Actions\CreateAction;
CustomerResource::getUrl(parameters: [
'action' => CreateAction::getDefaultName(),
]); // /admin/customers?action=create
Generando URLs a resources en otros paneles
Si tienes múltiples paneles en tu aplicación, getUrl()
generará una URL dentro del panel actual. También puedes indicar con qué panel está asociado el resource, pasando el ID del panel al argumento panel
:
use App\Filament\Resources\Customers\CustomerResource;
CustomerResource::getUrl(panel: 'marketing');
Personalizando la consulta Eloquent del resource
Dentro de Filament, cada consulta a tu modelo resource comenzará con el método getEloquentQuery()
.
Debido a esto, es muy fácil aplicar tus propias restricciones de consulta o scopes del modelo que afecten todo el resource:
public static function getEloquentQuery(): Builder
{
return parent::getEloquentQuery()->where('is_active', true);
}
Deshabilitando global scopes
Por defecto, Filament observará todos los global scopes que están registrados en tu modelo. Sin embargo, esto puede no ser ideal si deseas acceder, por ejemplo, a registros soft-deleted.
Para superar esto, puedes sobrescribir el método getEloquentQuery()
que usa Filament:
public static function getEloquentQuery(): Builder
{
return parent::getEloquentQuery()->withoutGlobalScopes();
}
Alternativamente, puedes remover global scopes específicos:
public static function getEloquentQuery(): Builder
{
return parent::getEloquentQuery()->withoutGlobalScopes([ActiveScope::class]);
}
Más información sobre remover global scopes puede encontrarse en la documentación de Laravel.
Personalizando la URL del resource
Por defecto, Filament generará una URL basada en el nombre del resource. Puedes personalizar esto estableciendo la propiedad $slug
en el resource:
protected static ?string $slug = 'pending-orders';
Sub-navegación del resource
La sub-navegación permite al usuario navegar entre diferentes páginas dentro de un resource. Típicamente, todas las páginas en la sub-navegación estarán relacionadas con el mismo registro en el resource. Por ejemplo, en un resource Customer, puedes tener una sub-navegación con las siguientes páginas:
- Ver cliente, una página
ViewRecord
que proporciona una vista de solo lectura de los detalles del cliente. - Editar cliente, una página
EditRecord
que permite al usuario editar los detalles del cliente. - Editar contacto del cliente, una página
EditRecord
que permite al usuario editar los detalles de contacto del cliente. Puedes aprender cómo crear más de una página Edit. - Gestionar direcciones, una página
ManageRelatedRecords
que permite al usuario gestionar las direcciones del cliente. - Gestionar pagos, una página
ManageRelatedRecords
que permite al usuario gestionar los pagos del cliente.
Para añadir una sub-navegación a cada página de "registro singular" en el resource, puedes añadir el método getRecordSubNavigation()
a la clase resource:
use Filament\Resources\Pages\Page;
public static function getRecordSubNavigation(Page $page): array
{
return $page->generateNavigationItems([
ViewCustomer::class,
EditCustomer::class,
EditCustomerContact::class,
ManageCustomerAddresses::class,
ManageCustomerPayments::class,
]);
}
Cada elemento en la sub-navegación puede personalizarse usando los mismos métodos de navegación que las páginas normales.
Si estás buscando añadir sub-navegación para cambiar entre resources completos y páginas personalizadas, podrías estar buscando clusters, que se usan para agrupar estos juntos. El método getRecordSubNavigation()
está destinado a construir una navegación entre páginas que se relacionan con un registro particular dentro de un resource.
Estableciendo la posición de sub-navegación para un resource
La sub-navegación se renderiza al inicio de la página por defecto. Puedes cambiar la posición para todas las páginas en un resource estableciendo la propiedad $subNavigationPosition
en el resource. El valor puede ser SubNavigationPosition::Start
, SubNavigationPosition::End
, o SubNavigationPosition::Top
para renderizar la sub-navegación como pestañas:
use Filament\Pages\Enums\SubNavigationPosition;
protected static ?SubNavigationPosition $subNavigationPosition = SubNavigationPosition::End;
Eliminando páginas de resources
Si quieres eliminar una página de tu resource, puedes simplemente eliminar el archivo de página del directorio Pages
de tu resource, y su entrada en el método getPages()
.
Por ejemplo, puedes tener un resource con registros que no pueden ser creados por nadie. Elimina el archivo de página Create
, y luego remuévelo de getPages()
:
public static function getPages(): array
{
return [
'index' => ListCustomers::route('/'),
'edit' => EditCustomer::route('/{record}/edit'),
];
}
Eliminar una página no eliminará ninguna acción que enlace a esa página. Cualquier acción abrirá un modal en lugar de enviar al usuario a la página no existente. Por ejemplo, el CreateAction
en la página List, el EditAction
en la tabla o página View, o el ViewAction
en la tabla o página Edit. Si quieres remover esos botones, debes eliminar las acciones también.
Seguridad
Autorización
Para autorización, Filament observará cualquier policy de modelo que esté registrada en tu aplicación. Se usan los siguientes métodos:
viewAny()
se usa para ocultar completamente resources del menú de navegación, y previene que el usuario acceda a cualquier página.create()
se usa para controlar crear nuevos registros.update()
se usa para controlar editar un registro.view()
se usa para controlar ver un registro.delete()
se usa para prevenir que un solo registro sea eliminado.deleteAny()
se usa para prevenir que los registros sean eliminados en lote. Filament usa el métododeleteAny()
porque iterar a través de múltiples registros y verificar la policydelete()
no es muy performante. Al usar unDeleteBulkAction
, si quieres llamar al métododelete()
para cada registro de todos modos, deberías usar el métodoDeleteBulkAction::make()->authorizeIndividualRecords()
. Cualquier registro que falle la verificación de autorización no será procesado.forceDelete()
se usa para prevenir que un solo registro soft-deleted sea forzado a eliminarse.forceDeleteAny()
se usa para prevenir que los registros sean forzados a eliminarse en lote. Filament usa el métodoforceDeleteAny()
porque iterar a través de múltiples registros y verificar la policyforceDelete()
no es muy performante. Al usar unForceDeleteBulkAction
, si quieres llamar al métodoforceDelete()
para cada registro de todos modos, deberías usar el métodoForceDeleteBulkAction::make()->authorizeIndividualRecords()
. Cualquier registro que falle la verificación de autorización no será procesado.restore()
se usa para prevenir que un solo registro soft-deleted sea restaurado.restoreAny()
se usa para prevenir que los registros sean restaurados en lote. Filament usa el métodorestoreAny()
porque iterar a través de múltiples registros y verificar la policyrestore()
no es muy performante. Al usar unRestoreBulkAction
, si quieres llamar al métodorestore()
para cada registro de todos modos, deberías usar el métodoRestoreBulkAction::make()->authorizeIndividualRecords()
. Cualquier registro que falle la verificación de autorización no será procesado.reorder()
se usa para controlar reordenar registros en una tabla.
Omitiendo autorización
Si quieres omitir la autorización para un resource, puedes establecer la propiedad $shouldSkipAuthorization
a true
:
protected static bool $shouldSkipAuthorization = true;
Protegiendo atributos del modelo
Filament expondrá todos los atributos del modelo a JavaScript, excepto si están $hidden
en tu modelo. Este es el comportamiento de Livewire para el binding de modelos. Preservamos esta funcionalidad para facilitar la adición y eliminación dinámica de campos de formulario después de que se cargan inicialmente, mientras se preservan los datos que pueden necesitar.
Aunque los atributos pueden ser visibles en JavaScript, solo aquellos con un campo de formulario son realmente editables por el usuario. Esto no es un problema con la asignación masiva.
Para remover ciertos atributos de JavaScript en las páginas Edit y View, puedes sobrescribir el método mutateFormDataBeforeFill()
:
protected function mutateFormDataBeforeFill(array $data): array
{
unset($data['is_admin']);
return $data;
}
En este ejemplo, removemos el atributo is_admin
de JavaScript, ya que no está siendo usado por el formulario.