26 жовтня команда Filament випустила v5.11.0 і паралельно v4.16.0. Після v5.10.0 з її сіткою в rich editor і скелетонами таблиць цей мінорний реліз змінює дві речі, з якими стикається кожен, хто пише власні компоненти: їх тепер можна робити на JavaScript-фреймворках, а сервер жорсткіше контролює, що з Livewire-компонента можна чіпати з браузера.
Поля, компоненти й віджети на JavaScript
Досі власне поле у Filament означало Blade-в'ю з Alpine.js. Якщо в проєкті вже був готовий віджет на React чи Vue - вибір дат, карта, редактор - його доводилося або переписувати, або обгортати вручну. У v5.11.0 Ден Гаррін додав три типи JavaScript-компонентів: поля форм (#20494), компоненти схем (#20550) і віджети (#20548) на спільній інфраструктурі рендерера (#20553).
Генератори отримали прапорці для фреймворку і TypeScript:
php artisan make:filament-form-field LocationPicker --react
php artisan make:filament-form-field LocationPicker --vue --ts
php artisan make:filament-schema-component Chart --svelte
php artisan make:filament-widget RevenueOverview --js
Команда створює PHP-клас із трейтом HasJsRenderer і стартовий рендерер у resources/js/filament/..., доставляє відсутні залежності й оновлює конфігурацію Vite. Підтримуються React 18+, Vue 3.3+ і Svelte 5. Сам Filament жодного фреймворку не тягне: React, Vue і Svelte - лише dev-залежності його тестів, рендерер і його залежності належать вашому застосунку.
Можна обійтися й без окремого класу:
use Filament\Forms\Components\JsField;
use Illuminate\Support\Facades\Vite;
JsField::make('location')
->renderer(Vite::asset('resources/js/fields/location-picker.jsx'))
->rendererConfiguration(['zoom' => 12])
->live(onBlur: true)
Рендерер - ES-модуль, що за замовчуванням експортує функцію монтування. Вона отримує host (DOM-вузол, у якому малює компонент), props і utilities, а повертає update(props) і destroy(). Поточне значення приходить у props.value, змінене віддається через props.onChange(), дані з PHP - у props.configuration. Модуль завантажується ліниво, коли поле ініціалізується, без глобального реєстру і тегу <script>.
Розподіл ролей тут принциповий: стан форми, валідація, авторизація і HTTP-відповіді лишаються у Filament і Livewire. Лейбл, підказки й повідомлення про помилки малює звичайна обгортка поля, а модифікатори live(), live(debounce: 500) і live(onBlur: true) працюють як завжди. Чого немає: Alpine-директив у шаблонах React чи Vue і готових адаптерів для завантаження файлів, дій і модалок - для Livewire-специфіки є $wire.
Документація прямо попереджає: рендерери не ізольовані, тож URL модуля має бути лише довіреним, а все, що надсилає поле, треба валідувати в PHP, навіть для вимкнених чи read-only полів. І ще одна дрібниця з #20494: компоненти, винесені з сітки схеми, тепер загорнуті в обгортку .fi-sc-liberated з display: contents. Власний CSS, що чіплявся до них як до прямих нащадків .fi-sc, може потребувати правки.
Закриті методи і заблокований стан
Livewire відкриває для клієнтських запитів кожен публічний метод компонента, навіть якщо в інтерфейсі немає кнопки, що його викликає. #20812 обмежує це на рівні Filament, і частина обмежень працює одразу після оновлення:
- Методи, оголошений тип повернення яких містить
Filament\Support\Components\Component або підклас (form(): Schema, table(): Table, editAction(): Action), автоматично закриті для клієнта. Це стосується і nullable-типів, і union чи intersection.
- Публічні властивості, якими керує сервер, позначені Livewire-атрибутом
#[Locked]: присвоєння в PHP працює, а $wire.set() з браузера - ні. Замість нього треба викликати підтримувані методи на кшталт toggleTableReordering() чи mountAction().
Для власних методів з'явився атрибут #[Unexposed] - клієнтський виклик, wire:click чи слухач Livewire-подій отримують HTTP 403, а прямий PHP-виклик працює:
use Filament\Support\Components\Attributes\Unexposed;
#[Unexposed]
public function recalculateTotals(): void
{
// ...
}
Протилежний #[Exposed] відкриває метод, перекриваючи як успадкований #[Unexposed], так і правило за типом повернення.
Що може зламатися: власний JavaScript поверх панелі, який викликав такі методи чи напряму писав у стан Filament, тепер отримає 403 або відмову. Є і тонший момент: PHP не переносить атрибути властивостей на перевизначення, тож якщо ви перевизначаєте заблоковану властивість у своєму класі, додайте #[Locked] і собі. А #[Url(history: true)] із заблокованою властивістю несумісний. Нарешті, ні блокування, ні #[Unexposed] не замінюють авторизацію - документація з безпеки повторює це кілька разів.
Запобіжники, які треба вмикати самому
Решта нового в безпеці за замовчуванням вимкнена і поведінки не змінює:
maxItemsForProcessing() для Repeater, Builder і RepeatableEntry (#20810). Якщо сирий стан містить більше елементів, Filament відповідає HTTP 413 ще до побудови схем елементів і нічого не обрізає. Ліміт не залежить від валідаційного maxItems(). Підступ: він діє і на дані з бази, тож запис, що вже перевищує ліміт, через цю форму не відкриється.
enforcePaginationPageOptions() для таблиць (#20809). Без нього paginated([10, 25, 50]) лише задає варіанти в селекторі, а змінений запит міг попросити хоч 10 000 записів на сторінку. З ним розмір сторінки обмежений списком. Якщо в списку є 'all', захисту від нього не буде.
Обидва можна ввімкнути глобально через configureUsing() у сервіс-провайдері:
use Filament\Forms\Components\Repeater;
use Filament\Tables\Table;
Repeater::configureUsing(fn (Repeater $repeater) => $repeater->maxItemsForProcessing(100));
Table::configureUsing(fn (Table $table) => $table->enforcePaginationPageOptions());
Перед глобальним ввімкненням пагінації перевірте таблиці, що задають розмір сторінки в PHP: він має бути в списку опцій. Окрему таблицю можна вивести з-під правила через enforcePaginationPageOptions(false).
Окремо виправлено обхід maxSelectableRecords() у масових діях: глобальні скопи, запити з join (#20824) і lazyById() на деяких версіях Laravel (#20805) дозволяли обробити більше записів, ніж дозволено. Тут нічого вмикати не треба - це виправлення.
Коротко про інше
- Ліниве завантаження (#20831) уніфіковане навколо
lazyLoaded() для схем, таблиць, вкладених Livewire-компонентів, віджетів і бейджів вкладок. Наприклад, у relation manager $isLazy став $isLazyLoaded, а бейджі вкладок вантажаться через lazyLoadedBadge(). Старі API лишились як депрекейт, а lazy() для полів радять замінити на live(onBlur: true).
- Eyebrow у модалках (#20731):
modalEyebrow('Edit post') додає короткий рядок над заголовком, наприклад назву операції над записом, назва якого стоїть у заголовку. Доступна назва модалки не змінюється.
- Конвертації в
SpatieMediaLibraryFileAttachmentProvider (#20732): новий метод conversion() віддає у rich editor URL конвертації, наприклад WebP чи зменшеної копії, замість оригіналу.
- HTML кастомних блоків rich editor (#20808) тепер вважається довіреним і не ріжеться санітайзером, як і в прев'ю. Навколишній вміст санітизується далі, але екранувати дані, які вводить користувач у налаштуваннях блоку, тепер мусить автор блоку.
Що потрапило у v4.16.0
У гілку 4.x перенесено майже все, крім самих JavaScript-компонентів: обмеження Livewire-методів і блокування стану, ліміти обробки елементів, примусова пагінація, виправлення масового вибору, уніфіковане ліниве завантаження, eyebrow, конвертації медіа і довірений HTML блоків. Спільна інфраструктура рендерера в 4.x теж є, але JsField там немає - поля, компоненти схем і віджети на JavaScript лишаються перевагою 5.x.
Перед оновленням
Для типової панелі без власного фронтенд-коду це спокійний мінорний реліз: composer update і перевірка звичних сценаріїв. Хто дописував JavaScript поверх Filament - кастомні Blade-в'ю з $wire.call(), $wire.set() чи інтеграції з зовнішніми віджетами, - пройдіться по них до продакшену: саме вони першими наткнуться на 403. І якщо у вас є таблиці з великими даними чи репітери, куди можна напхати сотні елементів, увімкніть нові ліміти свідомо, звіривши їх з даними, які вже лежать у базі.