Middle: питання на співбесіді з теми «Filament»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
7 питань
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.
Filament використовує політики моделей Laravel. Якщо для моделі ресурсу зареєстровано політику, Filament перевіряє її методи:
viewAny()- чи бачить користувач ресурс узагалі: без нього ресурс зникає з навігації, а сторінки віддають 403.view(),create(),update(),delete()- доступ до перегляду, створення, редагування й видалення конкретного запису.deleteAny(),forceDeleteAny(),restoreAny()- масові операції. Filament за замовчуванням не перевіряєdelete()для кожного запису окремо - це повільно. Якщо потрібно, у масової дії є->authorizeIndividualRecords().reorder()- зміна порядку рядків у таблиці.
class PostPolicy
{
public function viewAny(User $user): bool
{
return $user->hasRole('editor');
}
public function update(User $user, Post $post): bool
{
return $user->isAdmin() || $post->author_id === $user->id;
}
}
Що варто знати:
- Немає політики - немає обмежень: якщо політику не зареєстровано, Filament дозволяє все. Тому політика для кожної моделі в панелі - обов'язкова звичка.
- Перевірки повторюються на кожному Livewire-запиті, а не лише при відкритті сторінки: якщо доступ відібрали, наступна дія вже буде заборонена.
- Доступ до панелі визначає
canAccessPanel()на моделі користувача (інтерфейсFilamentUser). Без нього на проді в панель не пустить нікого. - Власні дії й сторінки політики ресурсу автоматично не покривають - їм потрібні
->authorize()чиcanAccess(). - Обмеження видимих рядків (редактор бачить лише свої пости) - через
getEloquentQuery()ресурсу, а не лише через політику.
Filament дає три основні способи працювати зі зв'язками, і вибір залежить від типу зв'язку та обсягу даних.
1. Select / CheckboxList з relationship() - вибрати наявні записи прямо у формі. Підходить для BelongsTo, MorphTo і BelongsToMany:
Select::make('author_id')
->relationship('author', 'name')
->searchable()
->preload();
Select::make('tags')
->multiple()
->relationship(titleAttribute: 'name'); // зберігається в pivot-таблицю
Зміни зберігаються разом з основною формою.
2. Repeater з relationship() - редагувати кілька дочірніх записів (HasMany) усередині форми батька: позиції замовлення, телефони контакту. Добре, коли записів небагато і вони не мають сенсу окремо від батька.
3. Relation manager - окрема інтерактивна таблиця під формою редагування чи перегляду. Підтримує HasMany, HasManyThrough, BelongsToMany, MorphMany, MorphToMany:
php artisan make:filament-relation-manager CategoryResource posts title
public static function getRelations(): array
{
return [PostsRelationManager::class];
}
У ньому є пошук, фільтри, пагінація, дії створення, редагування, AttachAction/DetachAction, AssociateAction.
Як обирати:
| Ситуація | Інструмент |
|---|---|
| вибрати одного автора чи кілька тегів | Select |
| 2-10 залежних рядків, що редагуються разом з батьком | Repeater |
| десятки й сотні пов'язаних записів, потрібні пошук і пагінація | relation manager |
Що варто знати про relation managers:
- вони завантажуються ліниво і зберігають зміни одразу, а не разом із формою батька;
- на сторінці перегляду вони за замовчуванням лише для читання;
- фільтр списку в модальному вікні
AttachAction- це лише відображення. Обмежити, які записи взагалі можна приєднати, треба черезrecordSelectOptionsQuery(), інакше підроблений запит приєднає будь-який запис.
Глобальний пошук - поле у верхній панелі, що шукає одразу по всіх ресурсах. Ресурс бере в ньому участь, якщо в нього задано атрибут-назву запису:
protected static ?string $recordTitleAttribute = 'title';
Пошук по кількох полях і зв'язках:
public static function getGloballySearchableAttributes(): array
{
return ['title', 'slug', 'author.name'];
}
Додаткові деталі під назвою результату:
public static function getGlobalSearchResultDetails(Model $record): array
{
return [
'Автор' => $record->author->name,
'Категорія' => $record->category->name,
];
}
Головна пастка - N+1. Деталі звертаються до зв'язків, і без жадібного завантаження кожен результат робить окремі запити. Зв'язки треба підвантажити в запиті пошуку:
public static function getGlobalSearchEloquentQuery(): Builder
{
return parent::getGlobalSearchEloquentQuery()->with(['author', 'category']);
}
Що ще впливає на швидкість:
- пошук будується на
LIKE '%...%'по кожному атрибуту - звичайний B-tree-індекс тут не допомагає. На великих таблицях варто обмежити кількість атрибутів і ресурсів у пошуку; - результатів на ресурс за замовчуванням не більше 50 (
$globalSearchResultsLimit); - ресурс, якому пошук не потрібен, вимикають
$isGloballySearchable = false; - ресурси без доступу (політика
viewAny) у пошук не потрапляють.
Зручності: гарячі клавіші для фокусу на полі пошуку задають у панелі через globalSearchKeyBindings(['command+k', 'ctrl+k']), а до результатів можна додати дії через getGlobalSearchResultActions().
Filament працює й без додаткових кроків, але кілька команд помітно впливають на швидкість і на те, чи взагалі відкриється панель на продакшені.
1. Кешування компонентів і іконок:
php artisan filament:optimize
Це скорочення для двох команд:
filament:cache-components- індексує ресурси, сторінки, віджети й relation managers уbootstrap/cache/filament, щоб не сканувати каталоги на кожному запиті;icons:cache- кешує Blade Icons, які Filament використовує всюди.
Скинути кеш - php artisan filament:optimize-clear.
Локально кеш компонентів вмикати не варто: нові ресурси й сторінки не з'являться, доки кеш не перебудувати.
2. Публікація ресурсів після оновлення пакета. У composer.json Laravel-проєкту з Filament зазвичай є скрипт:
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi",
"@php artisan filament:upgrade"
]
filament:upgrade оновлює опубліковані CSS і JavaScript Filament. Якщо ці файли не оновилися (наприклад, composer install запускався з --no-scripts), після оновлення пакета панель може зламатися через старі ресурси.
3. Звичайні оптимізації Laravel - config:cache, route:cache, view:cache - Filament підтримує.
4. Доступ. На продакшені користувач має реалізувати FilamentUser::canAccessPanel(), інакше всі отримають 403.
5. Черги. Імпорт і експорт, а також сповіщення в базу даних і через трансляцію працюють через черги. Без запущеного воркера імпорт «зависне», а сповіщення не з'являться.
6. Файли. FileUpload за замовчуванням зберігає файли з видимістю private. Якщо зображення мають бути публічними, потрібні ->visibility('public'), правильний APP_URL і storage:link для диска public.
Relation manager - таблиця пов'язаних записів, що показується під формою редагування чи переглядом запису ресурсу: коментарі поста, товари категорії, ролі користувача.
php artisan make:filament-relation-manager CategoryResource posts title
Після створення його реєструють у ресурсі:
public static function getRelations(): array
{
return [PostsRelationManager::class];
}
Attach чи associate - залежить від типу зв'язку:
| Дії | Зв'язок | Що відбувається |
|---|---|---|
AttachAction, DetachAction, DetachBulkAction |
BelongsToMany, MorphToMany |
рядок у pivot-таблиці додається чи видаляється, сам запис лишається |
AssociateAction, DissociateAction |
HasMany, MorphMany |
у пов'язаного запису змінюється зовнішній ключ (чи стає null) |
CreateAction, EditAction, DeleteAction |
будь-який | створення, редагування й видалення самих пов'язаних записів |
Прапорці --attach і --associate команди генерації одразу додають відповідні дії.
Detach проти Delete: DetachAction лише розриває зв'язок, а DeleteAction видаляє сам запис - для всіх, хто з ним пов'язаний. Плутанина тут коштує даних.
Режим лише для читання. На сторінці перегляду (ViewRecord) relation manager-и за замовчуванням ховають дії зміни - щоб сторінка перегляду лишалася переглядом. Вимкнути можна для одного менеджера (перевизначити isReadOnly() і повернути false) або для всієї панелі через ->readOnlyRelationManagersOnResourceViewPagesByDefault(false).
Нестандартна назва зворотного зв'язку: Filament обмежує запити через зворотний зв'язок (від поста до категорії). Якщо він називається не за правилами Laravel - ->inverseRelationship('section') у table().
Бейджі на вкладці:
public static function getBadge(Model $ownerRecord, string $pageClass): ?string
{
return (string) $ownerRecord->posts()->count();
}
Пам'ятайте, що це запит на кожне відкриття сторінки - для важких підрахунків є відкладене завантаження бейджів.
Доступ: canViewForRecord() за замовчуванням перевіряє політику viewAny пов'язаної моделі. CreateAction, EditAction, DeleteAction перевіряють відповідні методи політики (create, update, delete). А от AttachAction, DetachAction, AssociateAction і DissociateAction політику за замовчуванням не перевіряють - лише режим «тільки для читання». Якщо прив'язувати записи можна не всім, обмежте ці дії явно (->authorize(...) чи ->visible(...)), інакше будь-хто з доступом до сторінки редагування може змінювати зв'язки.
М'яке видалення: прапорець --soft-deletes додає фільтр видалених, відновлення й остаточне видалення.
Filament має два рівні налаштування вигляду: CSS (як усе виглядає) і render hooks (що ще вивести в певному місці інтерфейсу).
1. Власна тема. Стандартна панель використовує вже скомпільований CSS Filament - власні класи Tailwind у ваших Blade-шаблонах сторінок і віджетів у ньому відсутні й просто не працюють. Для цього потрібна тема:
php artisan make:filament-theme admin
Команда встановлює залежності Tailwind CSS, створює resources/css/filament/admin/theme.css, додає файл у input плагіна Laravel у vite.config.js і реєструє тему в провайдері панелі:
return $panel->viteTheme('resources/css/filament/admin/theme.css');
Після цього тему збирає Vite (npm run build для продакшену). У CSS-файлі теми можна додати власні стилі й джерела класів (@source для ваших шаблонів).
Простіші налаштування без теми: кольори панелі - ->colors([...]), шрифт - ->font(...), логотип - ->brandLogo(...). CSS hooks - класи на кшталт fi-sidebar чи fi-btn, на які Filament розраховує для перевизначення стилів у вашій темі.
2. Render hooks - точки в шаблонах Filament, куди можна вставити свій HTML: банер, лічильник, скрипт аналітики, кнопку в шапці.
use Filament\Support\Facades\FilamentView;
use Filament\View\PanelsRenderHook;
use Illuminate\Contracts\View\View;
FilamentView::registerRenderHook(
PanelsRenderHook::BODY_START,
fn (): View => view('filament.maintenance-banner'),
);
// або в конфігурації конкретної панелі
$panel->renderHook(PanelsRenderHook::USER_MENU_BEFORE, fn () => view('filament.env-badge'));
Область дії (scopes) - хук лише на певних сторінках:
FilamentView::registerRenderHook(
PanelsRenderHook::PAGE_START,
fn (): View => view('warning-banner'),
scopes: [EditUser::class, CreateUser::class],
);
Що варто знати:
- хук виконується на кожному рендері - важкі запити в замиканні сповільнюють усю панель;
- через
$panel->renderHook()хук прив'язаний до однієї панелі, черезFilamentView- до всіх; - перевизначення шаблонів пакета (
vendor:publishз видами Filament) ламається при оновленнях - render hooks і CSS hooks існують саме для того, щоб цього не робити; - власні Blade-шаблони (сторінки, віджети) з класами Tailwind без теми виглядатимуть «голими» - це найчастіша причина питання «чому мої класи не працюють».