Junior: питання на співбесіді з теми «Дії, інфолисти й віджети»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
4 питання
Дія (action) у Filament - це кнопка разом із логікою: що станеться після натискання, чи відкриється модальне вікно, хто її бачить. Усі дії в Filament 5 живуть у просторі імен Filament\Actions - і в таблицях, і на сторінках, і у формах.
Готові дії для типових операцій з Eloquent:
CreateAction,EditAction,ViewAction,DeleteAction;ReplicateAction- копія запису (excludeAttributes(['slug'])- не копіювати унікальні поля);ForceDeleteAction,RestoreAction- для моделей зSoftDeletes;ImportAction,ExportAction- CSV/XLSX через черги;- масові варіанти:
DeleteBulkAction,ForceDeleteBulkAction,RestoreBulkAction.
use Filament\Actions\DeleteAction;
use Filament\Actions\EditAction;
use Filament\Actions\ReplicateAction;
->recordActions([
EditAction::make(),
ReplicateAction::make()->excludeAttributes(['slug']),
DeleteAction::make(),
])
Готові дії вже вміють модальне вікно, сповіщення про успіх і перевірку політики моделі в ресурсі.
Власна дія з підтвердженням:
use Filament\Actions\Action;
use Filament\Support\Icons\Heroicon;
Action::make('cancel')
->label('Скасувати замовлення')
->icon(Heroicon::OutlinedXCircle)
->color('danger')
->requiresConfirmation()
->modalHeading('Скасувати замовлення?')
->modalDescription('Клієнт отримає лист, а оплату буде повернено.')
->modalSubmitActionLabel('Так, скасувати')
->action(fn (Order $record) => $record->cancel())
->successNotificationTitle('Замовлення скасовано');
requiresConfirmation() відкриває стандартне вікно підтвердження, і код в action() виконається лише після згоди.
Пастки:
url()і підтвердження несумісні: якщо дія лише відкриває посилання (->url(...)), вікно підтвердження не з'явиться. Для підтвердження перед переходом потрібенaction()з редиректом усередині;- кілька дій з однаковою назвою в одному місці конфліктують - ім'я в
make()має бути унікальним; - підтвердження - не авторизація. Хто може натиснути кнопку, вирішують
visible()/authorize(), а не модальне вікно.
Спливаючі сповіщення («тости») створює клас Filament\Notifications\Notification з флюентним API:
use Filament\Actions\Action;
use Filament\Notifications\Notification;
Notification::make()
->title('Замовлення збережено')
->body('Клієнту надіслано лист з підтвердженням.')
->success()
->send();
Стан і вигляд:
success(),warning(),danger(),info()- колір та іконка;icon(),iconColor()- власна іконка;duration(5000)/seconds(5)- скільки показувати (за замовчуванням 6 секунд);persistent()- не зникати, доки користувач не закриє.
Дії в сповіщенні:
Notification::make()
->title('Імпорт завершено з помилками')
->warning()
->persistent()
->actions([
Action::make('view')
->label('Переглянути')
->button()
->url(route('imports.show', $import)),
])
->send();
Як це працює. Сповіщення записується в сесію і показується на наступному рендері. Тому його можна надіслати звідки завгодно: з дії, з контролера перед редиректом, зі звичайного Livewire-компонента. Є і JavaScript-версія: new FilamentNotification().title('...').send().
У готових діях (CreateAction, EditAction, сторінки ресурсів) сповіщення про успіх уже є - текст міняють через successNotificationTitle(), а прибирають - successNotification(null).
Пастки:
send()показує сповіщення поточному користувачу в браузері. Щоб повідомити іншого користувача чи зберегти повідомлення надовго - сповіщення в базу даних (sendToDatabase()) чи трансляція (broadcast());- заголовок може містити простий безпечний HTML. Дані користувача варто вставляти обережно - санітайзер Filament пропускає атрибути
style; - надіслане з джоби в черзі сповіщення через
send()нікого не досягне: у воркера немає сесії браузера.
Infolist - схема для перегляду даних запису у вигляді «підпис - значення». Вона використовується на сторінці перегляду ресурсу (ViewRecord), у модальних вікнах ViewAction, у relation managers.
use Filament\Infolists\Components\IconEntry;
use Filament\Infolists\Components\RepeatableEntry;
use Filament\Infolists\Components\TextEntry;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
public static function infolist(Schema $schema): Schema
{
return $schema
->components([
Section::make('Замовлення')
->schema([
TextEntry::make('number')->copyable(),
TextEntry::make('customer.name')->label('Клієнт'),
TextEntry::make('status')->badge(),
TextEntry::make('total')->money('UAH'),
IconEntry::make('is_paid')->boolean(),
TextEntry::make('created_at')->dateTime('d.m.Y H:i'),
])
->columns(3)
->columnSpanFull(),
RepeatableEntry::make('items')
->schema([
TextEntry::make('product.name'),
TextEntry::make('qty'),
])
->columns(2)
->columnSpanFull(),
]);
}
Записи (entries) живуть у Filament\Infolists\Components: TextEntry, IconEntry, ImageEntry, ColorEntry, CodeEntry, KeyValueEntry, RepeatableEntry. Макет - ті самі Section, Grid, Tabs зі схем.
Чим краще за форму з disabled():
- форматування як у таблицях: бейджі, гроші, дати, іконки, markdown, копіювання в буфер, посилання;
- жодних полів введення - менше HTML, немає стану форми в Livewire-компоненті;
- зв'язки й JSON читаються крапковою нотацією:
customer.name,meta.title; - вимкнена форма виглядає як «щось зламалося» і погано читається.
Що варто знати:
- сторінку перегляду додають у ресурс опцією
--viewпри генерації (або вручну вgetPages()); - якщо метод
infolist()не визначено, Filament показує на сторінці перегляду вимкнену форму; TextEntryзhtml()чиmarkdown()санітизує вміст - на відміну від власного Blade з{!! !!};- relation managers на сторінці перегляду за замовчуванням працюють лише в режимі читання.
Віджет статистики - кілька карток з числом, описом і міні-графіком.
php artisan make:filament-widget OrdersOverview --stats-overview
use Filament\Support\Icons\Heroicon;
use Filament\Widgets\StatsOverviewWidget;
use Filament\Widgets\StatsOverviewWidget\Stat;
class OrdersOverview extends StatsOverviewWidget
{
protected ?string $pollingInterval = '60s';
protected function getStats(): array
{
$today = Order::whereDate('created_at', today());
return [
Stat::make('Замовлень сьогодні', $today->count())
->description('+12% до вчора')
->descriptionIcon(Heroicon::ArrowTrendingUp)
->color('success')
->chart([7, 3, 4, 5, 6, 3, 9]),
Stat::make('Виручка сьогодні', Number::currency($today->sum('total'), 'UAH', 'uk')),
Stat::make('Нових клієнтів', Customer::whereDate('created_at', today())->count()),
];
}
}
Віджети з каталогу app/Filament/Widgets автоматично з'являються на дашборді панелі.
Корисні налаштування:
protected static ?int $sort = 1;- порядок на дашборді;protected int | string | array $columnSpan = 'full';- ширина;public static function canView(): bool- кому показувати;protected static bool $isLazy = false;- вимкнути ліниве завантаження.
Дві важливі поведінки за замовчуванням:
- опитування кожні 5 секунд. Віджет статистики (і графік) сам оновлюється раз на 5 секунд. Якщо в
getStats()важкі агрегати по великих таблицях, кожен відкритий дашборд створює постійне навантаження на базу. Інтервал варто збільшити ($pollingInterval = '60s') або вимкнути (null); - ліниве завантаження. Віджети завантажуються окремим запитом після сторінки, тож повільний віджет не блокує решту дашборда.
Пастки:
canView()ховає віджет з дашборда, але якщо той самий віджет вбудовано на іншу сторінку, умову треба перевірити й там;- важкі метрики варто кешувати (
Cache::rememberна кілька хвилин) - точність до секунди на дашборді рідко потрібна; - число без форматування (
1234567) погано читається: кращеNumber::format()чиNumber::abbreviate().