Питання на співбесіді рівня Senior
Найпопулярніші питання з реальних Laravel/PHP співбесід для всіх рівнів
41 питань
Найчастіші причини:
- Великий серіалізований стан - Livewire серіалізує всі public-властивості в кожен запит. Тримайте в public лише необхідне; похідні дані вираховуйте у computed properties (
#[Computed]). - Надмірний
wire:model.live- кожне натискання шле запит. Використовуйте deferred або.debounce. - Відсутність
wire:keyу циклах - ламає узгодження DOM, спричиняє «стрибки» та зайвий ререндер. - «Божественні» компоненти - розбивайте на дрібніші, ізольовані.
- Завантаження всіх записів - використовуйте пагінацію та
#[Lazy]-компоненти.
@foreach ($posts as $post)
<livewire:post-row :post="$post" :key="$post->id" />
@endforeach
Також допомагають islands (частковий ререндер) та винесення важкої логіки в черги замість синхронних дій.
Для Livewire - хелпер livewire() (із pest-plugin-livewire):
livewire(SearchPosts::class)
->set('query', 'laravel')
->assertSee('Laravel Queues')
->call('clear')
->assertSet('query', '')
->assertDispatched('posts-updated');
Для Filament - спершу автентифікуйте користувача, потім тестуйте сторінки ресурсів:
livewire(CreatePost::class)
->fillForm(['title' => 'Hello'])
->call('create')
->assertHasNoFormErrors();
livewire(ListPosts::class)
->callAction(TestAction::make('publish')->table($post))
->assertNotified();
Ключові асерти: assertSet, assertSee, assertDispatched, assertHasFormErrors, assertCanSeeTableRecords.
Одна команда робить більшість:
php artisan optimize # config + route + view + event cache
Окремо:
php artisan config:cache # об'єднує конфіг у один файл
php artisan route:cache # компілює маршрути
php artisan view:cache # прекомпілює Blade
php artisan event:cache # кеш мапінгу подій/слухачів
composer install --no-dev --optimize-autoloader
Додатково: увімкнений OPcache (а краще з JIT), prebuilt ассети (npm run build).
Підводний камінь: після config:cache виклики env() поза config/ повертають null - усі env-значення мають читатися лише у конфіг-файлах. На деплої не забути php artisan optimize:clear перед повторним кешуванням.
Головний ризик - несумісність схеми зі старим кодом під час деплою та блокування таблиць.
Безпечні зміни (expand → migrate → contract):
- Додати нову колонку (nullable) - старий код працює.
- Задеплоїти код, що пише і в стару, і в нову.
- Перенести дані (фоновий job), перемкнути читання.
- Окремим релізом видалити стару колонку.
Практики:
- Не покладатися на
migrate:rollbackу проді -down()може втрачати дані. Краще forward-fix. - Великі
ALTERна величезних таблицях блокують → онлайн-міграції (pt-online-schema-change, gh-ost). php artisan migrate --forceу пайплайні;--isolated, щоб не виконати паралельно на кількох воркерах.- Бекап перед руйнівними операціями; прогін міграцій на staging.
Реплікація розвантажує основний сервер: запис іде на primary, читання - на replicas. Laravel маршрутизує запити автоматично, якщо в конфізі з'єднання задані секції read/write:
'mysql' => [
'read' => ['host' => ['10.0.0.2', '10.0.0.3']], // репліки
'write' => ['host' => ['10.0.0.1']], // primary
'sticky' => true,
// ...спільні параметри
],
SELECT→ репліка,INSERT/UPDATE/DELETE→ primary.sticky => trueкритично важливе: після запису в межах того ж запиту читання теж піде з primary, інакше через replication lag можна прочитати застарілі дані.- Реплікація асинхронна → завжди закладайте можливе відставання реплік у логіці.
Cache Stampede (dogpile) - коли популярний ключ кешу протермінувався, і сотні одночасних запитів кидаються перераховувати важке значення одночасно, перевантажуючи БД.
Рішення в Laravel:
// блокування: лише один процес перераховує, інші чекають результат
$value = Cache::lock('report:lock', 10)->block(5, function () {
return Cache::remember('report', 3600, fn () => $this->heavyReport());
});
Інші стратегії:
Cache::flexible()(stale-while-revalidate) - віддає «протухле» значення, поки одне фонове оновлення його перераховує.- Розмазування TTL (jitter), щоб ключі не протухали одночасно.
- Прогрів кешу (cache warming) за розкладом, а не за запитом користувача.
Завдання можуть падати через тимчасові збої (мережа, rate limit) - потрібна стратегія повторів і обробки остаточних провалів.
class CallApi implements ShouldQueue
{
public int $tries = 5; // спроб
public int $maxExceptions = 2;
public int $timeout = 30;
// прогресивна затримка між спробами
public function backoff(): array
{
return [10, 30, 60]; // 10с, 30с, 60с...
}
public function failed(Throwable $e): void
{
// викликається після вичерпання спроб
}
}
- Остаточно провалені завдання осідають у таблиці
failed_jobs. php artisan queue:retry all- повторити,queue:flush- очистити.releaseAfter,WithoutOverlapping,RateLimitedmiddleware керують поведінкою.- Ідемпотентність (idempotency) обов'язкова - бо завдання може виконатися повторно.
Contextual binding дозволяє віддавати різні реалізації одного інтерфейсу залежно від класу, що його запитує.
$this->app->when(PhotoController::class)
->needs(Filesystem::class)
->give(fn () => Storage::disk('local'));
$this->app->when(VideoController::class)
->needs(Filesystem::class)
->give(fn () => Storage::disk('s3'));
Тобто PhotoController отримає локальний диск, VideoController - S3, хоча обидва просять Filesystem.
Споріднені можливості:
giveTagged()- впорснути всі сервіси з певним тегом.- Прив'язка примітивів:
->needs('$apiKey')->give(config('services.x.key')).
Корисно, коли одна абстракція має кілька конфігурацій у різних частинах застосунку.
PHP-FPM - «shared nothing»: кожен запит стартує з чистого стану, наприкінці все звільняється. Просто, безпечно, але є оверхед бутстрапу фреймворку на кожному запиті.
Octane тримає застосунок у пам'яті між запитами → кратно вищий throughput і нижча латентність.
| PHP-FPM | Octane | |
|---|---|---|
| Стан між запитами | чистий | зберігається |
| Throughput | нижчий | значно вищий |
| Ризик витоків стану | немає | є |
| Складність деплою | проста | вища (воркери, рестарти) |
Ціна Octane: треба остерігатися «протікання» стану (статика, синглтони, глобальні змінні), правильно скидати/перезапускати воркери, уважно з пам'яттю. FPM лишається розумним дефолтом, доки немає потреби в екстремальній продуктивності.
- Ресурсна модель URL: іменники в множині (
/posts,/posts/{id}/comments), дія - через HTTP-метод, а не в URL. - Коректні статус-коди: 200/201/204, 422 (валідація), 401/403, 404, 429.
- API Resources для відповіді - щоб відв'язати JSON від схеми БД і контролювати формат.
- Версіонування (
/v1) із самого старту. - Пагінація, фільтрація, сортування через query-параметри; не віддавати все одразу.
- Consistent error format - єдина структура помилок (Laravel дає
{ "message": ..., "errors": {...} }для 422). - Автентифікація через Sanctum/Passport, rate limiting на маршрутах.
- Idempotency для небезпечних повторюваних операцій (платежі).
- Документація (OpenAPI/Scribe) і контрактні тести.
Одне з ключових архітектурних рішень. Контролери й моделі швидко «розпухають», тож логіку виносять в окремі класи.
Проблема «товстих» контролерів: контролер має лише приймати запит, делегувати роботу й повертати відповідь. Бізнес-логіка в ньому не тестується ізольовано й не перевикористовується.
Service-класи - групують пов'язану логіку домену:
class OrderService
{
public function __construct(
private PaymentGateway $gateway,
private InventoryManager $inventory,
) {}
public function place(User $user, Cart $cart): Order
{
return DB::transaction(function () use ($user, $cart) {
$this->inventory->reserve($cart->items);
$order = $user->orders()->create([/* ... */]);
$this->gateway->charge($user, $cart->total);
OrderPlaced::dispatch($order);
return $order;
});
}
}
Action-класи (single-action) - один клас = одна операція. Дрібніша гранулярність, дуже тестовано:
class PlaceOrderAction
{
public function handle(User $user, Cart $cart): Order { /* ... */ }
}
«Товсті» моделі - логіку, тісно пов'язану з даними самої моделі (скоупи, аксесори, прості методи стану), доречно лишати в моделі. Складні міждоменні операції - у сервіси.
Рекомендації:
- Контролер тонкий: запит → виклик сервісу/екшену → відповідь.
- Складні операції з кількома моделями - у Service/Action із транзакцією.
- Логіка одного агрегату - у моделі (скоупи, обчислювані атрибути).
- Не плодіть абстракцій передчасно - починайте простіше, виносьте за потреби.