Livewire і Filament
20 питань · ~20 хв · Версія v3.0
Увійдіть, щоб продовжити
Livewire 4 (стан компонента, однофайлові компоненти, острови, відкладене завантаження, файли, безпека) і Filament 5 (ресурси, форми й таблиці, авторизація, мультитенантність, тестування).
- За спробу
- 20
- У пулі
- 100
- Проходжень
- 0
- Середній бал
- -
- Пройшли на 70%+
- -
Питання для підготовки
113 питаньFilament - фреймворк Server-Driven UI для Laravel: інтерфейси (адмінки, панелі) описуються на PHP через структуровані об'єкти, а не верстку.
public static function form(Schema $schema): Schema
{
return $schema->components([
TextInput::make('title')->required(),
Select::make('status')->options(Status::class),
]);
}
- Побудований на Livewire, Alpine.js і Tailwind CSS.
- Будівельні блоки: Resources, Forms, Tables, Actions, Infolists, Widgets.
- Компоненти ініціалізуються статичними
make()-методами; динамічні значення задаються замиканнями з утилітамиGet/Set.
Після кожної дії сервер повертає новий HTML компонента. Livewire не замінює ним старий цілком, а морфить: проходить обидва дерева елемент за елементом і змінює лише те, що відрізняється. Завдяки цьому зберігаються фокус, введений текст, позиція прокрутки й обробники подій.
Де морфінг помиляється. Алгоритм порівнює елементи за позицією. Якщо між ними з'являється новий елемент, він може «зсунути» відповідність:
<div><input wire:model="title"></div>
@if ($errors->has('title'))
<div>{{ $errors->first('title') }}</div>
@endif
<div><button>Зберегти</button></div>
Коли з'являється помилка, Livewire бачить на другому місці новий div і може вирішити, що це змінений старий, - результат: зайві перерисовки, втрачений стан сторонніх елементів. Livewire додає в шаблон службові маркери навколо @if і @foreach, щоб це згладити, але найнадійніший інструмент - ключі.
wire:key - ідентичність елемента:
@foreach ($todos as $todo)
<li wire:key="todo-{{ $todo->id }}">...</li>
@endforeach
Morph зіставляє елементи за ключем, а не за позицією: видалення першого рядка не змушує «перейменовувати» всі наступні. Для дочірніх компонентів у циклі ключ обов'язковий.
wire:ignore - не чіпати вміст елемента взагалі:
<div wire:ignore>
<div x-init="new Chart($el, ...)"></div>
</div>
Для сторонніх бібліотек (редактори, графіки, карти), які самі керують своїм DOM: інакше Livewire «виправить» їхні зміни назад до HTML з сервера. wire:ignore.self - ігнорувати атрибути самого елемента, але оновлювати дітей.
wire:replace - протилежне: не морфити, а замінити вміст повністю:
<div wire:replace>
<json-viewer>@json($payload)</json-viewer>
</div>
Для веб-компонентів і елементів зі своїм внутрішнім станом, який треба скинути при кожному оновленні. wire:replace.self - замінити й сам елемент.
Типові симптоми й причини:
| Симптом | Причина |
|---|---|
| введене «переїжджає» в інше поле | немає wire:key у циклі |
| сторонній віджет зникає чи ламається після дії | немає wire:ignore |
| елемент зберігає старий стан, хоча дані змінилися | морф перевикористав елемент - wire:key зі зміною ключа чи wire:replace |
| дочірній компонент не перестворюється | той самий wire:key - змінити ключ, щоб Livewire створив новий екземпляр |
Продуктивність: морфінг великого HTML (таблиця на тисячі рядків) коштує і на сервері (рендер), і в браузері (порівняння). Острівці, ліниві компоненти й пагінація зменшують обсяг, який морфиться за одну дію.
Livewire сильний там, де інтерфейс - це форми, таблиці, фільтри й адмінки поверх серверних даних: логіка й валідація в PHP, без окремого API і дублювання правил на фронтенді. Але кожна його взаємодія - це запит на сервер і рендер компонента, і з цього випливають межі.
Коли Livewire - поганий вибір:
- миттєва реакція на кожен рух: перетягування з анімацією, малювання, редактори з частими змінами, ігри. Затримка мережі (навіть 50-100 мс) помітна;
- робота офлайн чи на нестабільному з'єднанні - без сервера інтерфейс не працює;
- складний клієнтський стан, що майже не стосується сервера: багатокрокові конструктори, полотна, планувальники з десятками елементів на екрані;
- кілька клієнтів одного API: мобільний застосунок і вебверсія - API все одно потрібен, і Livewire додає другий шлях до тих самих даних;
- висока кількість одночасних користувачів з частими діями: кожна дія навантажує PHP-воркери, тоді як SPA ходить на сервер лише за даними.
Що обрати замість:
| Ситуація | Інструмент |
|---|---|
| дрібна інтерактивність без сервера: меню, вкладки, модальні вікна | Alpine.js - разом з Livewire або без нього |
| багатий інтерфейс на React чи Vue, але маршрутизація й дані з Laravel без окремого API | Inertia |
| кілька клієнтів, офлайн, окрема фронтенд-команда | SPA чи мобільний застосунок + API (Sanctum) |
| сайт зі статичним вмістом | звичайний Blade, кеш на краю мережі |
Поєднання частіше, ніж вибір:
- Livewire + Alpine: клієнтські дрібниці на Alpine, дані й збереження на Livewire.
$wireу Alpine дає доступ до властивостей і методів компонента; - острівці й ізоляція: важку частину сторінки винести в окремий компонент чи острівець, щоб її оновлення не перерендерювали все;
- окремий віджет на JavaScript (графік, редактор) усередині Livewire-сторінки з
wire:ignore.
Як зважувати: склад команди (PHP-розробники чи фронтенд), вимоги до швидкості реакції, кількість клієнтів API. Переписування з Livewire на SPA посеред проєкту дороге - тому питання «чи буде мобільний застосунок» варто поставити на початку.
wire:model приймає не лише ім'я властивості, а й шлях усередині неї через крапку.
Масиви й вкладені дані:
public array $address = ['city' => '', 'street' => ''];
public array $items = [];
public array $roles = [];
<input wire:model="address.city">
<input wire:model="address.street">
@foreach ($items as $index => $item)
<div wire:key="item-{{ $item['id'] }}">
<input wire:model="items.{{ $index }}.qty">
</div>
@endforeach
{{-- кілька чекбоксів у масив --}}
<input type="checkbox" value="editor" wire:model="roles">
<input type="checkbox" value="author" wire:model="roles">
Хук updated отримує повний шлях: updatedItems($value, $key) з $key = '2.qty' - видно, який рядок змінився.
Об'єкти форм - те саме, але властивості типізовані й валідація поруч:
<input wire:model="form.title">
Чому не модель напряму. У Livewire 3 і 4 цей код кидає виняток:
public Post $post;
<input wire:model="post.title"> {{-- Can't set model properties directly --}}
Причини:
- безпека: властивості компонента - це дані, які надсилає браузер. Зв'язування з моделлю дозволило б змінювати будь-який атрибут моделі запитом, включно з тими, що не виводяться у формі (
is_admin,user_id), - аналог масового присвоєння без$fillable; - стан: модель серіалізується лише як клас і ключ, а при кожному запиті завантажується з бази заново - незбережені зміни атрибутів між запитами губилися б.
Як правильно: окремі властивості чи об'єкт форми, а при збереженні - явне заповнення провалідованими даними:
public function save(): void
{
$this->post->update($this->form->validate());
}
Стару поведінку можна ввімкнути (legacy_model_binding у конфігурації разом з правилами валідації для кожного поля), але для нового коду це не рекомендований шлях.
Пастки з масивами:
- без
wire:keyу циклі після видалення рядка введені значення «переїжджають» в сусідні поля; - індекси масиву після видалення:
unset($this->items[$i])лишає дірку в ключах;array_values()після видалення - іwire:keyза стабільним ідентифікатором, а не індексом; - великий масив у властивості - весь масив серіалізується в кожен запит. Для сотень рядків краще окремі дочірні компоненти чи редагування по одному.
Ресурс - це опис того, як модель виглядає в адмінці: таблиця, форма й сторінки навколо них.
php artisan make:filament-resource Vacancy --generate
Команда створює клас ресурсу, сторінки List/Create/Edit, а форму й таблицю виносить в окремі файли (Schemas/VacancyForm.php, Tables/VacancyTable.php). Прапорці --embed-schemas і --embed-table лишають їх усередині класу ресурсу.
Дві головні частини - форма й таблиця:
public static function form(Schema $schema): Schema
{
return $schema->components([
TextInput::make('title')
->required()
->maxLength(255),
Select::make('level')
->options(VacancyLevel::class)
->required(),
Toggle::make('is_published'),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('title')->searchable()->sortable(),
TextColumn::make('company.name')->label('Компанія'),
IconColumn::make('is_published')->boolean(),
])
->filters([
SelectFilter::make('level')->options(VacancyLevel::class),
]);
}
Що це дає без додаткового коду: список із пошуком, сортуванням і пагінацією, форми створення й редагування з валідацією, масові дії, фільтри.
На чому побудовано: Livewire відповідає за реактивність без написання JS, Alpine - за дрібні взаємодії в браузері, Tailwind - за оформлення. Тому Filament - це PHP-код, а не окремий фронтенд-застосунок.
Що варто знати одразу: форма й таблиця - це звичайні PHP-масиви обʼєктів, тож видимість поля, опції списку чи доступність дії задаються замиканнями й можуть залежати від користувача та стану запису.
Прочитати - ще не значить знати
20 питань, по одному на екран, ~20 хв. Після завершення - розбір кожної помилки з посиланням на питання.