Laravel Filament Tips


En los últimos días he adquirido un gusto particular por Laravel Filament y a pesar de que el proyecto es muy bueno para desarrollar features de forma rápida, la documentación carece de algunas secciones importantes. Lo ideal es crear un pull request para actualizar la documentación, pero, por temas de tiempo, escribiré un blog con pequeños fragmentos de código esperando que nos puedan servir a todos.

Contenido

Customizar el comportamiento de HMR

Laravel trae por default una configuración de vite que te permite hacer HMR, sin embargo, se puede modificar el comportamiento para adaptarlo a escuchar cambios solo en los archivos y directorios de nuestro interés, para ello:

  1. Se debe modificar el archivo vite.config.js para cambiar las reglas dependiendo de tus necesidades (aumentar o disminuir los archivos)
import { defineConfig } from 'vite';
import laravel, { refreshPaths } from 'laravel-vite-plugin';

export default defineConfig({
    plugins: [
        laravel({
            input: ['resources/css/app.css', 'resources/js/app.js'],
            refresh: [
                ...refreshPaths,
                // Agrea los paths que nos interesa escuchar y elimina los que no son de utilidad para HMR...
                'app/Livewire/**',
                'app/Filament/**',
                'app/Providers/**',
            ],
        }),
    ],
});

  1. Actualizar el archivo PanelServiceProvider para cargar los estilos de vite (causando en consecuendia el HMR):
<?php

namespace App\Providers\Filament;

use Filament\PanelProvider;
use Filament\Support\Facades\FilamentView;
use Illuminate\Support\Facades\Blade;

class AdminPanelProvider extends PanelProvider
{
    public function register(): void
    {
        parent::register();

        FilamentView::registerRenderHook(
            'panels::body.end',
            fn(): string => Blade::render("@vite('resources/js/app.js')")
        );
    }
}

Acciones encapsuladas en clases

Los recursos de Filament están preparados para que en la definición de las tablas, o en las páginas de creación, edición y vista puedas crear acciones usando el patrón builder sobre las clases Filament\Actions y Filament\Tables\Actions\Action. Sin emabrgo, las acciones pueden tener formularios muy complejos o acciones muy complejas, haciendo que tu archivo ModelResource o tus archivos de Página crezcan rápidamente.

Para mitigarlo, puedes crear tus propias clases que extiendan de las Acciones base (Filament\Actions o Filament\Tables\Actions\Action dependiendo del caso) y cuya configuración se setea en el método setUp, aquí tenemos un ejemplo:

namespace App\Filament\Actions;

use Filament\Tables\Actions\Action;
// o en su defecto
// use Filament\Tables\Actions\Action;

class MyComplexAction extends Action 
{
    public function setUp(): void
    {
        parent::setUp();

        $this->name('complex_action')
            // ... Propiedades adicionales, como color(), label(), etc.
            ->form([/* ... */])
            ->action(/* ... */);
    }
}

De esta manera puedes simplificar tu definición principal en to archivo Resource:

namespace App\Filament\Resources;

use App\Filament\Actions\MyComplexAction;
use Filament\Tables;
use Filament\Resources\Resource;

class ModelResource extends Resource 
{
    public function table(Table $table): Table
    {
        $table->actions([
            MyComplexAction::make(),
        ]);
    }
}

Accediendo a la relación padre en un RelationManager

Siguendo este mismo approach de encapsular accioens en clases, ocasionalmente necesitarás accesar al modelo del recurso. Para ello, puedes definir la propiedad $ownerRecord y un par de métodos para setearlos y obtenerlos dentro de tu acción:

class MyComplexAction extends Action 
{
    private ?Model $ownerRecord;

    public static function setOwnerRecord(?Model $ownerRecord): self
    {
        $this->ownerRecord = $ownerRecord;

        return $this;
    }

    public function setUp(): void
    {
        parent::setUp();

        $this->action(function () {
            $parent = $this->ownerRecord;

            // ...
        });
    }
}

Y al crear la acción lo relacionas de la siguiente manera:

namespace App\Filament\Resources;

use App\Filament\Actions\MyComplexAction;
use Filament\Tables;
use Filament\Resources\Resource;

class ModelResource extends Resource 
{
    public function table(Table $table): Table
    {
        $table->actions([
            MyComplexAction::make()
                ->setOwnerRecord($this->getOwnerRecord()),
        ]);
    }
}

Creando páginas públicas usando los features de Laravel Filament

Ocasionalmente necesitarás crear páginas que sean accesibles fuera de los Paneles, para ello, si bien es cierto que podemos construir componentes de Livewire, al hacerlo, perdemos los estilos y funcionalidades de Filament, como las Notificaciones.

Un workaround para este problema es utilizar la página simple que se usa para la autenticación de Filament. Para ello:

  1. Crea una página usando el comando:
php artisan make:filament-page MyPublicPage
  1. Cambia la clase base de Page a SimplePage

namespace App\Filament\Pages;

use Filament\Facades\Filament;
use Filament\Pages\SimplePage;

class MyPublicPage extends SimplePage
{
    // ...    
}
  1. Utiliza el layout page.simple en tu vista.
<x-filament-panels::page.simple>
    <!-- Tu contenido... -->

    <!-- Opcionalmente puedes usar el siguiente panel si tienes formularios: -->
    <x-filament-panels::form id="form" wire:submit="sbmitAction">
        <!-- {{ $this->form }} -->
    </x-filament-panels::form>
</x-filament-panels::page.simple>
  1. Finalmente registra la vista de forma pública modificando el archivo routes/web.php:
use App\Filament\Pages\MyPublicPage;
use Illuminate\Support\Facades\Route;

Route::get('public-route', MyPublicPage::class);

Esto es super útil si quieres crear páignas de términos y condiciones o política de privacidad.

Cambiando la alineación de las acciones en formularios y modales

Una característica curiosa de Laravel Filament, es que las acciones se encuentran en un formato ltr, es decir:

Crear    Crear otro     Cancelar

Sin embargo, muchos sistemas suelen tener las acciones un un formaro rtl, es decir:

Cancelar    Crear otro     Crear

Si estás interesado en seguir este último estándar, puedes cambiar la alineación de acciones de toda la app utilizando las siguientes lineas en tu AppServiceProvider:

use Filament\Actions\Action;
use Filament\Pages\Page;
use Filament\Support\Enums\Alignment;
use Filament\Tables\Actions\Action as TableAction;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Action::configureUsing(function (Action $action) {
            $action->modalFooterActionsAlignment(Alignment::Right);
        });

        TableAction::configureUsing(function (TableAction $action) {
            $action->modalFooterActionsAlignment(Alignment::Right);
        });

        Page::$formActionsAlignment = Alignment::Right;
    }
}

Accediendo al modelo creado usando acciones CRUD en ModelResource.

Cuándo estas interesado en ejecutar acciones usando los callbacks de ciclo de vida de Filament, puedes acceder al modelo creado utilizando la propiedad $record disponible en las páginas que extienden de CreateRecord y EditRecord.

namespace App\Filament\Resources\ModelResource\Pages;

use App\Events\ModelSaved;
use App\Filament\Resources\ModelResource;
use App\Models\Transaction;
use Filament\Resources\Pages\CreateRecord;

class CreateModel extends CreateRecord
{
    protected static string $resource = ModelResource::class;

    public function afterCreate(): void
    {
        $model = $this->getRecord();
        event(new ModelSaved($model->id));

        // Nota importante, si deseas un tipado fuerte, se recomienda buscar el modelo en eloquent
        // Porque por default lo considera un objecto y no un Modelo, es decir:
        // $model = Model::find($this->getRecord()->id) // Type Model
        // $object = $this->getRecord()->id // Type object
    }

    public function afterSave(): void
    {
        $model = $this->getRecord();
    }
}

Post deployment

En general, al desplegar una aplicación en Laravel, se recomienda seguir una serie de pasos para optimizar el performance de la aplicación, como generar un caché de los eventos, listeners, vistas, etc…

Laravel filament no es la excepción, también tiene algunos comandos para generar caché de íconos y algunos componentes.

Si estas haciendo deployments continuos, te recomendaría automatizar todos estos pasos, existen diversas técnicas, pero puedes utilizar los mismo features de laravel (como un Comando) para generar este caché de forma automática en tus despliegues.

Un ejemplo esto sería:

  1. Crear un comando de post-depliegue:
php artisan make:command PostDeploy
  1. Ejecutar los comandos regulares de construcción de caché en este comando:
namespace App\Console\Commands;

use Illuminate\Console\Command;

class PostDeploy extends Command
{
    protected $signature = 'app:post-deploy';

    protected $description = 'Clear cache after deploying our application.';

    public function handle(): void
    {
        $this->call('config:cache');
        $this->call('route:cache');
        $this->call('icons:cache');
        $this->call('event:cache');
        $this->call('filament:cache-components');
    }
}

Finalmente una vez que logres subir tus cambios a ambiente productivo puedes simplificar estos pasos ejecutando el nuevo comando:

php artisan app:post-deploy

Esto te salvará un par de minutos por cada despliegue, en efecto, hay algunas otras opciones como utlizar un orquestador tipo Jenkins, github actions, etc.

Conclusión

Epsero que estas notas te sirvan tanto como a mi, y te ahorren un poco de tiempo al crear tus proyectos con Laravel Filament.

Happy coding! 🚀