Питання на співбесіді з Laravel
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
378 питань
Horizon - панель і конфігурація черг на Redis. Дає те, чого немає в базовому queue:work.
// config/horizon.php
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['high', 'default'],
'balance' => 'auto', // авто-балансування воркерів
'maxProcesses' => 10,
],
Можливості:
- Реалтайм-метрики: throughput, час очікування, runtime завдань.
- Авто-балансування процесів між чергами за навантаженням.
- Керування невдалими завданьами, теги, сповіщення про довге очікування (
LongWaitDetected).
Запуск - php artisan horizon; під капотом це менеджер довготривалих воркерів. Дашборд захищають gate viewHorizon.
- Паролі - лише одностороннє хешування (Bcrypt/Argon2):
Hash::make()/Hash::check(). Ніколи не шифрування й не власні алгоритми. - PII (двостороннє) -
Crypt::encryptString()або кастencryptedна атрибуті моделі:protected $casts = ['ssn' => 'encrypted']; - Ключі та секрети - у
.env/ секретних сховищах (AWS Secrets Manager, Vault), не в git. РотаціяAPP_KEYпотребує перешифрування. - Транзит - лише HTTPS/TLS.
- Логи - маскувати PII; уникати
dd()у проді. - Доступ - принцип найменших привілеїв, audit log (наприклад,
spatie/laravel-activitylog).
DDD фокусується на моделюванні бізнес-домену спільною мовою з експертами. Ключові поняття: Entities, Value Objects, Aggregates, Domain Events, Bounded Contexts.
У Laravel це зазвичай означає відхід від стандартної структури (app/Models, app/Http) на користь організації за доменами:
app/Domain/Ordering/
Models/Order.php
Actions/PlaceOrder.php
ValueObjects/Money.php
Events/OrderPlaced.php
- Бізнес-логіка живе в домені, а не в контролерах чи моделях-«божках».
- Контролери стають тонкими адаптерами, що викликають доменні дії.
DDD виправданий у складних доменах; для CRUD він додає зайвий оверхед.
Деплой без простою: користувачі весь час бачать робочу версію.
Atomic (symlink) deploy - кожен реліз клонується в нову папку, там встановлюються залежності й збираються ассети, після чого current атомарно перемикається через symlink:
releases/2026_06_05_120000/ ← новий
current → releases/... ← атомарне перемикання
Кроки на деплої: composer install --no-dev, npm run build, migrate --force, кеш конфіг/маршрутів, перезапуск воркерів (queue:restart) і OPcache.
Інструменти: Envoyer, Deployer, CI/CD-пайплайни, Kubernetes (rolling update). Окрема увага - сумісність міграцій із попередньою версією коду під час перемикання.
Idempotency (ідемпотентність) - багаторазове виконання операції дає той самий результат, що й однократне. Критично для платежів і повторів завдань у чергах (де доставка «at least once»).
Реалізація для API - idempotency key:
$key = $request->header('Idempotency-Key');
return Cache::lock("idem:$key")->block(5, function () use ($key) {
if ($cached = Cache::get("idem:result:$key")) {
return $cached; // повернути попередній результат
}
$result = $this->charge(); // виконати один раз
Cache::put("idem:result:$key", $result, now()->addDay());
return $result;
});
Для завдань: перевірка «вже оброблено» за унікальним ключем, ShouldBeUnique, або БД-обмеження, що відсікають дублі.
Ключове правило - не тримати весь файл у пам'яті.
- Стрімінг замість читання цілком:
return Storage::disk('s3')->response($path); // стрім на скачування Storage::writeStream($path, fopen($source, 'r')); // стрім на запис - Direct uploads на S3 - клієнт вантажить напряму в сховище за pre-signed URL, минаючи PHP-процес (не блокує воркер, обходить ліміти
upload_max_filesize). - Chunked upload - великі файли частинами (resumable).
- Фонова обробка - конвертацію відео/зображень виносити в черги.
- Враховувати
max_execution_time, таймаути nginx і ліміти пам'яті воркера.
П'ять принципів ООП-дизайну. У Laravel вони реалізуються природно завдяки сервіс-контейнеру.
S - Single Responsibility: клас має одну причину для зміни. На практиці - виносити бізнес-логіку з «товстих» контролерів у Service/Action-класи, валідацію - у Form Requests, логіку життєвого циклу моделі - в Observers.
O - Open/Closed: відкритий для розширення, закритий для модифікації. Приклад - драйвери Laravel (cache, queue, filesystem): новий драйвер додається через extend(), не змінюючи ядро.
L - Liskov Substitution: реалізації взаємозамінні через спільний інтерфейс без поломки логіки - напр., будь-який драйвер кешу можна підставити замість іншого.
I - Interface Segregation: багато вузьких інтерфейсів краще за один «товстий»; клас не має реалізовувати методи, які не використовує.
D - Dependency Inversion: залежати від абстракцій, а не від реалізацій. Сервіс-контейнер - пряме втілення:
class OrderController
{
public function __construct(private PaymentGateway $gateway) {} // інтерфейс
}
$this->app->bind(PaymentGateway::class, StripeGateway::class); // реалізація в провайдері
Користь: тестованість (легко підставити mock), гнучкість (зміна реалізації в одному місці). Водночас Senior знає, коли не переускладнювати - надмірна абстракція заради «чистоти» шкодить не менше за її відсутність.
Telescope - інструмент дебагу/спостереження. Збирає й показує у дашборді: запити, винятки, SQL-запити (з часом і дублями), завдання черг, листи, нотифікації, кеш, події, HTTP-клієнт.
php artisan telescope:install
- Незамінний для пошуку N+1 (бачиш усі запити сторінки), повільних місць, помилок у чергах.
- Дані зберігаються в БД; у проді обмежують доступ через gate
viewTelescopeта вмикають sampling, бо обсяг записів великий.
На відміну від Horizon (керування чергами), Telescope - про діагностику всього застосунку. У продакшені часто доповнюють зовнішнім APM (Sentry, Datadog).
У кластері з кількох інстансів локальний лічильник не годиться - потрібен спільний стан у Redis. Фасад RateLimiter під капотом використовує атомарні операції Redis (INCR + EXPIRE), тож підрахунок точний між усіма серверами.
RateLimiter::attempt(
key: 'send-sms:'.$user->id,
maxAttempts: 5,
callback: fn () => $this->sendSms(),
decaySeconds: 60,
);
Для складніших схем - алгоритми sliding window чи token bucket на Lua-скриптах (атомарність на стороні Redis, без гонок). Ключове: лічильник і його TTL мають змінюватися атомарно, інакше за конкурентного доступу ліміт «протікає».
Circuit Breaker захищає від каскадних збоїв при зверненні до ненадійної залежності (зовнішнє API, що «лежить»). Якщо помилок забагато - «ланцюг розривається», і запити певний час відхиляються миттєво, не витрачаючи ресурси на марні спроби.
Стани:
- Closed - усе працює, запити йдуть.
- Open - поріг помилок перевищено; запити одразу падають (fail fast).
- Half-Open - через таймаут пропускаються пробні запити; успіх → Closed, провал → знову Open.
// концептуально через Cache як лічильник збоїв
if (Cache::get('cb:payments') === 'open') {
throw new ServiceUnavailableException;
}
У Laravel реалізують через лічильники в Redis/Cache або пакети-обгортки HTTP-клієнта. Часто поєднують із retry + backoff.
CI автоматично перевіряє кожен пуш, CD - автоматично доставляє код.
Типовий пайплайн (GitHub Actions / GitLab CI):
- composer install
- vendor/bin/pint --test # стиль
- vendor/bin/phpstan analyse # статичний аналіз
- php artisan test --parallel # тести
- npm ci && npm run build # ассети
# → деплой при успіху
Деплой: SSH-скрипт, Docker-образ у реєстрі + rolling update у Kubernetes, або сервіси на кшталт Forge/Envoyer. На етапі деплою - migrate --force, кешування конфіга/маршрутів, queue:restart. CI/CD дає швидкий зворотний зв'язок і знижує ризик людської помилки при релізі.
За замовчуванням php artisan down створює файл у storage/framework - лише на тому сервері, де виконали команду. На п'яти серверах решта чотири продовжать приймати запити.
Драйвер на основі кешу:
APP_MAINTENANCE_DRIVER=cache
APP_MAINTENANCE_STORE=redis
Сховище має бути спільним для всіх серверів - тоді досить одного down.
Корисні опції:
php artisan down --secret="team-preview" --render="errors::503" --retry=60
--secret- команда відкриваєhttps://example.com/team-previewі бачить сайт через cookie обходу;--render- сторінку віддають до завантаження фреймворку, тож вона не впаде під часcomposer install;--retry- заголовокRetry-Afterдля клієнтів і пошукових ботів;--redirect=/- перенаправляти всі запити.
Черги: поки застосунок у режимі обслуговування, воркери не обробляють завдання - вони накопичуються й виконаються після up. Довге обслуговування - велика черга на виході; варто оцінити, чи витримають її воркери, і чи не застаріють завдання (листи «ваш код дійсний 10 хвилин»).
Планувальник теж не запускає завдання, окрім evenInMaintenanceMode().
Для деплою без простою режим обслуговування взагалі не потрібен - його залишають для робіт, що справді вимагають зупинки.
Докладніше в документації: Режим обслуговування на кількох серверах
Журнал корисний, лише якщо за ним можна відновити, що сталося з конкретним запитом чи завданням.
1. Контекст у кожному записі:
// middleware
Context::add('request_id', (string) Str::uuid());
Context::add('user_id', $request->user()?->id);
Log::info('Замовлення оформлено', ['order_id' => $order->id]);
Дані з Context автоматично додаються до кожного запису журналу й передаються в завдання черги, які цей запит поставив. Тоді один request_id зв'язує запит, завдання й виклики API.
2. Структурований формат - JSON (JsonFormatter) замість рядків, щоб журнал фільтрувався в Loki, ELK чи Datadog за полями.
3. Рівні з розумом: error - те, що вимагає уваги; warning - підозріле, але оброблене; info - бізнес-події; debug - вимкнений на продакшені. Якщо все error, сигнал губиться.
4. Канали: stack з кількох каналів - stderr для платформи, окремий канал для платежів, Slack лише для critical.
5. Винятки - у сервіс відстеження помилок (Sentry, Flare, Nightwatch) з групуванням і стектрейсом; throttle() у withExceptions(), щоб шторм однакових помилок не з'їв квоту.
Чого не писати: паролі, токени, повні номери карток, персональні дані без потреби. Витік журналу - теж витік.
Локально журнал зручно читати через php artisan pail з фільтрами за рівнем і користувачем.
- Stateful - сервер зберігає стан клієнта між запитами (наприклад, сесія у файлі/пам'яті конкретного інстансу). Тоді потрібна «липкість» (sticky sessions) або спільне сховище.
- Stateless - сервер не зберігає стану; кожен запит самодостатній і містить усе потрібне (наприклад, JWT/токен з даними автентифікації).
Stateful: сесія на сервері → потрібен спільний Redis/sticky LB
Stateless: токен у запиті → будь-який інстанс обробить запит
Stateless легше масштабувати горизонтально - інстанси взаємозамінні. У Laravel веб-частина зазвичай stateful (сесії в Redis), API - stateless (Sanctum-токени). Для масштабування цей стан виносять у спільні Redis/БД.
Sharding - горизонтальне розбиття даних між кількома незалежними БД (шардами) за ключем (tenant_id, user_id, geo). Дозволяє вийти за межі одного сервера.
shard 1: користувачі 1–1M
shard 2: користувачі 1M–2M
- Shard key обирають так, щоб дані рівномірно розподілялись і більшість запитів влучали в один шард.
- Складнощі: запити між шардами, унікальність ID (часто UUID/Snowflake), ребалансування при додаванні шарда, відсутність крос-шардових операцій JOIN і транзакцій.
У Laravel реалізують через множинні з'єднання й маршрутизацію за shard key. Sharding - крайній захід, коли вертикальне масштабування та репліки вже не справляються.
Питання з реальних технічних співбесід - 378 питань у 43 темах, розібраних із відповідями. Нижче - розбивка за рівнями та темами, якщо хочете звузити підготовку.
- Теми
- Eloquent 31 Архітектура 19 Тестування 14 Черги 14 Продуктивність 12 Безпека 11 Автентифікація 10 Бази даних 10
Готуєтесь до співбесіди не просто так: зараз на сайті 146 відкритих вакансій Laravel і PHP. Переглянути вакансії