Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Питання на співбесіді: Налагодження

Питання з реальних співбесід з відповідями: 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() для логів замість виводу на екран.

Докладніше в документації: Хелпери (dump/dd)

Коли 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]).

Докладніше в документації: Перегляд логів через Pail

Telescope - інструмент дебагу/спостереження. Збирає й показує у дашборді: запити, винятки, SQL-запити (з часом і дублями), завдання черг, листи, нотифікації, кеш, події, HTTP-клієнт.

php artisan telescope:install
  • Незамінний для пошуку N+1 (бачиш усі запити сторінки), повільних місць, помилок у чергах.
  • Дані зберігаються в БД; у проді обмежують доступ через gate viewTelescope та вмикають sampling, бо обсяг записів великий.

На відміну від Horizon (керування чергами), Telescope - про діагностику всього застосунку. У продакшені часто доповнюють зовнішнім APM (Sentry, Datadog).

Докладніше в документації: Laravel Telescope

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