Питання на співбесіді: Налагодження
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
7 питань
Обидва виводять значення в зручному вигляді через Symfony VarDumper:
dump($var)- друкує значення й продовжує виконання.dd($var)- «dump and die»: друкує й зупиняє виконання.
dump($user); // подивитись і йти далі
dd($request->all()); // подивитись і зупинитись
Споріднене: dump() для ланцюжків (->dump() на колекції/запиті), ray() (пакет), Log::debug() для логів замість виводу на екран.
Коли APP_DEBUG=true, Laravel показує сторінку помилки з усім потрібним для розслідування. Головне - читати її в правильному порядку.
1. Клас винятку й повідомлення вгорі сторінки - найважливіше:
| Що бачите | Що це зазвичай означає |
|---|---|
ModelNotFoundException |
findOrFail чи прив'язка моделі до маршруту не знайшли запис |
QueryException |
помилка SQL: немає колонки, таблиці, порушено обмеження |
ErrorException: Undefined array key |
звернення до ключа, якого немає |
TypeError |
у функцію передано значення не того типу (часто null) |
ViewException |
помилка всередині Blade-шаблону - справжня причина нижче в ланцюжку |
2. Перший кадр вашого коду. Стек викликів читається згори вниз: верхній кадр - де виняток кинуто. Часто це код фреймворку чи пакета, і він не винен. Сторінка позначає кадри з vendor/ окремо й згортає їх - шукайте перший кадр з app/, routes/ чи resources/: там рядок, який викликав проблему.
3. Попередні винятки. Якщо виняток обгорнуто (як ViewException), справжня причина - у «previous exception». Сторінка показує весь ланцюжок.
4. Контекст запиту: маршрут, контролер, middleware, параметри, заголовки, тіло запиту й виконані SQL-запити. Часто відповідь там: не той ідентифікатор у параметрі, порожнє поле форми.
Корисні дрібниці:
- кнопка копіювання помилки в Markdown - зручно вставити в задачу чи в чат з AI-асистентом;
- посилання на файли відкриваються в IDE, якщо вказати редактор у
config/app.php:'editor' => 'phpstorm'; - те саме, що на сторінці, записано в
storage/logs/laravel.log- для помилок у чергах і командах, де сторінки немає.
На продакшені APP_DEBUG має бути false: сторінка розкриває змінні оточення, запити й код. Користувач бачить загальну сторінку 500, а деталі - в логах і системі моніторингу помилок.
Логування в Laravel побудоване на Monolog; канали налаштовуються в config/logging.php.
Log::info('Замовлення створено', ['id' => $order->id]);
Log::channel('slack')->critical('Платіж не пройшов');
Типи каналів: single (один файл), daily (ротація по днях), slack, papertrail, stderr, а також stack - який пише одразу в кілька:
'stack' => ['driver' => 'stack', 'channels' => ['daily', 'slack']],
Практики: рівні (debug…emergency), структуроване логування з контекстом, маскування PII, окремий канал для критичних подій у Slack/Sentry. У проді LOG_LEVEL зазвичай warning+.
Подивитися SQL одного запиту без виконання:
User::where('active', true)->orderBy('name')->toSql();
// select * from "users" where "active" = ? order by "name" asc
User::where('active', true)->toRawSql();
// select * from "users" where "active" = 1 ... - з підставленими значеннями
Вивести й продовжити чи зупинитися:
User::where('active', true)->dumpRawSql()->get(); // вивести й виконати
User::where('active', true)->ddRawSql(); // вивести й зупинити
toRawSql() зручний, щоб скопіювати запит у консоль бази й запустити EXPLAIN. Але для реального виконання Laravel завжди використовує підготовлені запити з окремими параметрами - підставлені значення лише для читання людиною.
Усі запити за запит чи команду:
DB::enableQueryLog();
// ... код ...
dump(DB::getQueryLog()); // SQL, параметри й час кожного
Слухач запитів - для логування в розробці:
// AppServiceProvider::boot()
if (app()->isLocal()) {
DB::listen(function (QueryExecuted $query): void {
Log::debug($query->toRawSql(), ['ms' => $query->time]);
});
}
Сповіщення про повільну роботу з базою за весь запит:
DB::whenQueryingForLongerThan(500, function (Connection $connection, QueryExecuted $event): void {
Log::warning('Багато часу в базі', ['connection' => $connection->getName()]);
});
Як знайти проблеми системно:
- N+1:
Model::preventLazyLoading(! app()->isProduction())- виняток при лінивому завантаженні зв'язку в циклі; - Laravel Debugbar чи Telescope - список запитів сторінки з дублікатами й часом;
- сторінка помилки Laravel також показує виконані запити;
- на продакшені - журнал повільних запитів бази (
pg_stat_statements, slow query log), а не логування кожного запиту.
Пастка: enableQueryLog у довгому процесі (воркер, команда з мільйоном записів) накопичує всі запити в пам'яті - вмикайте його лише на час налагодження.
Класичний спосіб - tail -f storage/logs/laravel.log. Він працює лише з файловим каналом, показує сирий текст разом з багаторядковими стеками, і його незручно фільтрувати.
Laravel Pail - Artisan-команда, яка показує записи логів у реальному часі в зручному вигляді:
composer require --dev laravel/pail
php artisan pail
Головна перевага: Pail отримує записи напряму від застосунку, тому працює з будь-яким драйвером логів - навіть якщо записи йдуть у Sentry, Papertrail чи stderr і локального файлу немає.
Фільтри:
php artisan pail --filter="QueryException" # тип винятку, файл, текст
php artisan pail --message="Payment" # лише за текстом повідомлення
php artisan pail --level=error # рівень
php artisan pail --user=42 # записи, зроблені в запитах користувача 42
php artisan pail -v # без скорочень
php artisan pail -vv # зі стеком викликів
Фільтр за користувачем особливо корисний: можна відтворити проблему під потрібним обліковим записом і бачити лише свої записи серед загального потоку.
Типові сценарії:
- налагодження черги чи планувальника, де немає сторінки помилки: запустити
pailпоруч з воркером; - розробка інтеграції з вебхуками - бачити, що прийшло й що записано, одразу;
composer run devу новому Laravel-проєкті запускає Pail разом із сервером, чергою й Vite.
Обмеження:
- потрібне розширення PCNTL - на Windows лише через WSL;
- Pail показує записи, що з'являються після запуску, - історію дивляться в логах;
- це інструмент розробника: на продакшені для пошуку причин збоїв потрібне централізоване сховище логів з пошуком і зберіганням, а не перегляд у терміналі.
Щоб логи були корисними в Pail: структурований контекст замість склеювання рядків - Log::info('Order paid', ['order_id' => $order->id]).
Telescope - інструмент дебагу/спостереження. Збирає й показує у дашборді: запити, винятки, SQL-запити (з часом і дублями), завдання черг, листи, нотифікації, кеш, події, HTTP-клієнт.
php artisan telescope:install
- Незамінний для пошуку N+1 (бачиш усі запити сторінки), повільних місць, помилок у чергах.
- Дані зберігаються в БД; у проді обмежують доступ через gate
viewTelescopeта вмикають sampling, бо обсяг записів великий.
На відміну від Horizon (керування чергами), Telescope - про діагностику всього застосунку. У продакшені часто доповнюють зовнішнім APM (Sentry, Datadog).
dd() і логи показують значення в одній точці. Xdebug дає повноцінне налагодження: точки зупинки в IDE, покрокове виконання, перегляд усіх змінних і стеку, умовні точки зупинки. Для складної логіки (rebase-подібні алгоритми, вкладені колекції, події) це в рази швидше за десятки dump().
Режими Xdebug 3 вмикаються через xdebug.mode:
| Режим | Для чого |
|---|---|
debug |
покрокове налагодження з IDE |
develop |
покращені повідомлення про помилки й var_dump |
profile |
профілювання: файли cachegrind з часом кожної функції |
coverage |
покриття коду для тестів |
off |
вимкнено (на продакшені Xdebug не встановлюють взагалі) |
Базове налаштування:
xdebug.mode=debug
xdebug.start_with_request=trigger ; лише коли є тригер, інакше все сповільнюється
xdebug.client_host=host.docker.internal
xdebug.client_port=9003
start_with_request=trigger вмикає налагодження лише за наявності cookie чи параметра XDEBUG_TRIGGER (розширення браузера Xdebug Helper ставить його кнопкою). Без цього кожен запит намагається підключитися до IDE і помітно гальмує.
У Docker головна пастка - напрямок з'єднання: Xdebug сам підключається до IDE, тобто з контейнера на хост. Тому client_host - адреса хоста: host.docker.internal (на Linux потрібен extra_hosts: host.docker.internal:host-gateway). Laravel Sail робить це через змінну SAIL_XDEBUG_MODE.
Мапінг шляхів: у контейнері код лежить у /var/www/html, на хості - в іншому каталозі. У PhpStorm налаштовується сервер з іменем (PHP_IDE_CONFIG=serverName=laravel) і відповідністю шляхів, інакше точки зупинки не спрацьовують.
Налагодження консолі й тестів:
XDEBUG_TRIGGER=1 php artisan queue:work --once
XDEBUG_MODE=debug XDEBUG_TRIGGER=1 php artisan test --filter=OrderTest
Профілювання: xdebug.mode=profile записує файл на кожен запит; його відкривають у PhpStorm, KCachegrind чи Webgrind і бачать, де витрачено час. Профілювання сильно сповільнює виконання, тож абсолютні значення не відповідають продакшену - важливі пропорції. Для продакшену - вибіркові профайлери на кшталт Blackfire чи SPX.
Чому Xdebug не має бути завжди увімкненим: навіть у режимі debug без підключення він сповільнює PHP, а composer і тести стають помітно повільнішими. Зручно мати швидкий спосіб вмикати його лише на час налагодження.
Докладніше в документації: Laravel Sail: налагодження з Xdebug