Питання
Найпопулярніші питання з реальних Laravel/PHP співбесід для всіх рівнів
41 питань
Оптимізація йде кількома шарами:
База даних
- Усунення N+1 (eager loading
with()), правильні індекси, аналіз черезEXPLAIN. - Read/write репліки, кешування важких запитів.
Кешування
Cache::remember()для дорогих обчислень; повне кешування сторінок/фрагментів.php artisan optimize(config/route/view/event cache), OPcache.
Фонова робота
- Винесення повільних задач (email, обробка медіа, виклики API) у черги.
Інфраструктура
- Laravel Octane (Swoole/FrankenPHP) тримає застосунок у пам'яті.
- CDN для статики, горизонтальне масштабування за load balancer, спільні сесії/кеш у Redis.
Перед оптимізацією - профілювання (Telescope, Clockwork, Debugbar), щоб бити по реальних вузьких місцях.
Шлях запиту:
public/index.php- єдина точка входу; підключає автозавантажувач Composer.- Створюється екземпляр застосунку (Service Container) із
bootstrap/app.php. - HTTP Kernel обробляє запит, завантажує Service Providers (
register→boot). - Запит проходить глобальні middleware (наприклад, обробка сесій, CSRF).
- Router зіставляє URL із маршрутом, виконуються middleware маршруту.
- Викликається контролер/замикання, формується Response.
- Відповідь проходить middleware у зворотному порядку й повертається клієнту; виконується
terminate().
Ключова ідея: контейнер і провайдери бутстрапять застосунок, а middleware утворюють «цибулю» навколо обробки запиту.
- CQRS (Command Query Responsibility Segregation) розділяє запис (Commands, що змінюють стан) і читання (Queries). Read-модель можна оптимізувати окремо (денормалізовані проєкції, окрема БД).
- Event Sourcing зберігає не поточний стан, а послідовність подій; поточний стан відновлюється їх відтворенням. Дає повний аудит і «подорож у часі».
// концептуально
$aggregate->retrieve($uuid)
->placeOrder($data) // emit OrderPlaced
->persist(); // зберегти подію
У Laravel зазвичай через пакет spatie/laravel-event-sourcing (aggregates, projectors, reactors). Застосовувати варто там, де критичні аудит і складна доменна логіка - це додає суттєву складність, тож не для типового CRUD.
Octane запускає застосунок через high-performance сервери (Swoole, FrankenPHP, RoadRunner): фреймворк бутстрапиться один раз і тримається в пам'яті, обслуговуючи наступні запити без повторної ініціалізації.
php artisan octane:start --server=frankenphp
Це усуває оверхед завантаження на кожному запиті й дає кратний приріст RPS.
Підводні камені: оскільки процес довготривалий, треба уникати витоків стану між запитами - статичні властивості, синглтони з накопиченим станом, глобальні змінні можуть «протікати» від запиту до запиту. Octane надає хуки flush і перезапускає воркери для безпеки.
PHP використовує підрахунок посилань + збирач циклічних посилань. У звичайному веб-запиті пам'ять звільняється наприкінці запиту, тож витоки малопомітні. Але у довготривалих процесах (черги, Octane) пам'ять накопичується.
Як уникати:
- Не зберігати стан у статичних властивостях/синглтонах між задачами.
unset()великих структур, скидати накопичувачі (логи запитівDB::flushQueryLog()).- Обробляти дані порціями (
chunk,lazy), не тримати все в пам'яті. - Перезапускати воркери за лімітом:
queue:work --max-jobs=1000 --max-time=3600або при досягненні--memory.
Octane має gc_collect_cycles()-хуки; Horizon автоматично перезапускає воркери, що «розпухли».
Pessimistic locking блокує рядок у БД до завершення транзакції - інші транзакції чекають.
DB::transaction(function () {
$account = Account::lockForUpdate()->find($id); // блокування на запис
$account->balance -= 100;
$account->save();
});
sharedLock() - блокування на читання.
Optimistic locking не блокує, а перевіряє версію/updated_at перед записом; якщо хтось уже змінив рядок - оновлення відхиляється, операцію повторюють.
UPDATE accounts SET balance = ?, version = version + 1
WHERE id = ? AND version = ?
- Pessimistic - для високої конкуренції за тими ж рядками (платежі, склад).
- Optimistic - коли конфлікти рідкісні; масштабується краще, бо не тримає блокувань.
Два основні підходи:
Single Database (shared schema) - усі орендарі в одній БД, розділення за tenant_id у кожній таблиці. Ізоляція забезпечується global scope, що автоматично додає where tenant_id = ?.
- Плюси: просто й дешево. Мінуси: ризик витоку даних при помилці у scope.
Multi Database - окрема БД (або схема) на орендаря, динамічне перемикання з'єднання за поточним tenant.
- Плюси: сильна ізоляція, легше бекапити/масштабувати окремого клієнта. Мінуси: складніші міграції (на кожну БД).
Tenancy::initialize($tenant); // перемкнути конфіг з'єднання/кеш/файли
Популярний пакет - stancl/tenancy. Вибір залежить від вимог до ізоляції та масштабу.
Гексагональна архітектура ізолює ядро бізнес-логіки від зовнішнього світу.
- Ports - інтерфейси, через які ядро спілкується зі світом (
PaymentGateway,UserRepository). - Adapters - конкретні реалізації портів (
StripeAdapter,EloquentUserRepository, HTTP-контролер).
[ HTTP / CLI / Queue ] → Port → [ Domain Core ] → Port → [ DB / API / Mail ]
(adapters) (бізнес-логіка) (adapters)
Ядро не знає про Laravel, БД чи HTTP - воно залежить лише від абстракцій. Перевага: домен тестується ізольовано, зовнішні залежності легко підмінювати. У Laravel порти біндять до адаптерів через Service Container.
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 знає, коли не переускладнювати - надмірна абстракція заради «чистоти» шкодить не менше за її відсутність.