Skip to content

Дослідження внутрішніх механізмів Symfony

Ми вже досить довго використовуємо Symfony для розробки потужного застосунку, але більша частина коду, що виконується цим застосунком, походить від Symfony. Кілька сотень рядків коду в порівнянні з тисячами рядків коду.

Мені подобається розуміти, як все працює за лаштунками. І мене завжди захоплювали інструменти, які допомагають мені зрозуміти, як все працює. Перший раз, коли я використовував покроковий налагоджувач або вперше дослідив ptrace — це магічні спогади.

Хочете краще зрозуміти, як працює Symfony? Настав час розібратися в тому, як Symfony змушує ваш застосунок працювати. Замість того щоб описувати, як Symfony обробляє HTTP-запит з теоретичної точки зору, що було б досить нудно, ми збираємося використовувати Blackfire, щоб отримати певні візуальні уявлення і використовувати це для дослідження певних, більш просунутих, тем.

Опанування внутрішніх механізмів Symfony за допомогою Blackfire

Ви вже знаєте, що всі HTTP-запити обслуговуються однією точкою входу: файлом public/index.php. Але що відбувається далі? Як викликаються контролери?

Відпрофілюймо головну сторінку англійською мовою в продакшн використовуючи Blackfire, за допомогою розширення браузера Blackfire:

1
$ symfony remote:open

Або безпосередньо через командний рядок:

1
$ blackfire curl `symfony cloud:env:url --pipe --primary`en/

Перейдіть у режим перегляду "Timeline" результатів профілювання, ви маєте побачити щось схоже на наступне:

/

Наведіть курсор на кольорові смужки на часовій шкалі, щоб отримати додаткову інформацію про кожен виклик; ви дізнаєтеся багато нового про те, як працює Symfony:

  • Основною точкою входу є public/index.php;
  • Метод Kernel::handle() обробляє запит;
  • Він викликає HttpKernel, який оголошує кілька подій;
  • Першою подією є RequestEvent;
  • Викликається метод ControllerResolver::getController(), щоб визначити, який контролер слід викликати для вхідної URL-адреси;
  • Викликається метод ControllerResolver::getArguments(), щоб визначити, які аргументи передати контролеру (викликається перетворювач параметрів);
  • Викликається метод ConferenceController::index(), і більша частина нашого коду виконується цим викликом;
  • Метод ConferenceRepository::findAll() отримує всі конференції з бази даних (зверніть увагу на підключення до бази даних за допомогою PDO::__construct());
  • Метод Twig\Environment::render() відмальовує шаблон;
  • Оголошуються ResponseEvent і FinishRequestEvent, але це виглядає так, неначе насправді жодних слухачів не зареєстровано, оскільки вони, здається, дуже швидко виконуються.

Часова шкала — це чудовий спосіб зрозуміти, як працює певний код; що дуже корисно, коли ви отримуєте проект, що розроблений кимось іншим.

Тепер відпрофілюйте ту саму сторінку з локального комп'ютера в середовищі розробки:

1
$ blackfire curl `symfony var:export SYMFONY_PROJECT_DEFAULT_ROUTE_URL`en/

Відкрийте результати профілювання. Ви маєте бути перенаправлені в режим перегляду графа викликів, оскільки запит був достатньо швидким і часова шкала була б абсолютно порожньою:

/

Ви розумієте, що відбувається? HTTP-кеш увімкнено, і тому ми профілюємо шар HTTP-кешу Symfony. Оскільки сторінка знаходиться в кеші, HttpCache\Store::RestoResponse() отримує HTTP-відповідь з кешу, а контролер ніколи не викликається.

Вимкніть шар кешу в public/index.php, як ми робили це на попередньому кроці, і повторіть спробу. Ви можете відразу побачити, що результат профілювання виглядає зовсім по-іншому:

/

Основні відмінності полягають у наступному:

  • TerminateEvent, якої не було видно в продакшн, займає великий відсоток часу виконання; придивившись ближче, ви можете побачити, що ця подія відповідає за зберігання даних профілювальника Symfony, зібраних під час запиту;
  • Під викликом ConferenceController::index() зверніть увагу на метод SubRequestHandler::handle(), який відмальовує ESI (ось чому ми маємо два виклики Profiler::saveProfile() — один для основного запиту, і ще один для ESI).

Ознайомтеся з часовою шкалою, щоб дізнатися більше; перемкніться в режим перегляду графа викликів, щоб мати інше представлення тих самих даних.

Як ми тільки що з’ясували, код, що виконується в середовищі розробки й у продакшн — дуже відрізняється. Середовище розробки працює повільніше, оскільки профілювальник Symfony намагається зібрати багато даних, щоб полегшити складнощі налагодження. Ось чому вам завжди слід профілювати тільки у продакшн середовищі, навіть локально.

Кілька цікавих експериментів: відпрофілюйте сторінку помилки, відпрофілюйте сторінку / (яка є перенаправленням) або ресурс API. Кожен результат профілювання розповість вам трохи більше про те, як працює Symfony, які класи/методи викликаються, що виконується повільно, а що — швидко.

Використання налагоджувального додатка Blackfire

За замовчуванням Blackfire видаляє всі виклики методів, які не є достатньо значущими, щоб уникнути значних навантажень і об'ємних графіків. При використанні Blackfire, у ролі інструменту налагодження, краще зберігати всі виклики. Це забезпечується налагоджувальним додатком.

У командному рядку використовуйте прапорець --debug:

1
2
$ blackfire --debug curl `symfony var:export SYMFONY_PROJECT_DEFAULT_ROUTE_URL`en/
$ blackfire --debug curl `symfony cloud:env:url --pipe --primary`en/

У продакшн ви побачите, наприклад, завантаження файлу з назвою .env.local.php:

/

Звідки він береться? Platform.sh виконує деякі оптимізації під час розгортання застосунку Symfony, як-от оптимізацію автозавантажувача Composer (--optimize-autoloader --apcu-autoloader --classmap-authoritative). Він також оптимізує змінні середовища, що визначені у файлі .env (щоб уникнути розбору файлу для кожного запиту), генеруючи файл .env.local.php:

1
$ symfony run composer dump-env prod

Blackfire — це дуже потужний інструмент, який допомагає зрозуміти, як виконується код на PHP. Поліпшення продуктивності є тільки одним зі способів використання профілювальника.

Використання покрокового налагоджувача за допомогою Xdebug

Часові шкали і графи викликів Blackfire дозволяють розробникам візуалізувати, які файли/функції/методи виконуються механізмом PHP, щоб краще зрозуміти кодову базу проекту.

Іншим способом стежити за виконанням коду є використання покрокового налагоджувача, як-от Xdebug. Покроковий налагоджувач дозволяє розробникам інтерактивно проходити по коду проекту PHP для налагодження потоку управління і вивчення структур даних. Це дуже корисно для налагодження неочікуваної поведінки, і замінює поширений метод налагодження "var_dump()/exit()".

По-перше, встановіть розширення PHP xdebug. Переконайтеся, що воно встановлене, виконавши наступну команду:

1
$ symfony php -v

Ви маєте побачити Xdebug виводі:

1
2
3
4
5
6
PHP 8.0.1 (cli) (built: Jan 13 2021 08:22:35) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.0.1, Copyright (c) Zend Technologies
    with Zend OPcache v8.0.1, Copyright (c), by Zend Technologies
    with Xdebug v3.0.2, Copyright (c) 2002-2021, by Derick Rethans
    with blackfire v1.49.0~linux-x64-non_zts80, https://blackfire.io, by Blackfire

Ви також можете перевірити чи увімкнено Xdebug для PHP-FPM, зайшовши в браузер і натиснувши на посилання "View phpinfo()" наводячи курсор на логотип Symfony на панелі інструментів веб-налагодження:

/

Тепер увімкніть режим Xdebug debug:

php.ini
1
2
3
[xdebug]
xdebug.mode=debug
xdebug.start_with_request=yes

За замовчуванням Xdebug відправляє дані на порт 9003 локального хоста.

Ініціювання Xdebug може бути здійснене різними способами, але найпростішим є використання Xdebug із вашої IDE. У цьому розділі ми будемо використовувати Visual Studio Code, щоб продемонструвати, як це працює. Встановіть розширення PHP Debug, запустивши функцію "Quick Open" (Ctrl+P), вставте наступну команду й натисніть клавішу Enter:

1
ext install felixfbecker.php-debug

Створіть наступний файл конфігурації:

.vscode/launch.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Listen for XDebug",
            "type": "php",
            "request": "launch",
            "port": 9003
        },
        {
            "name": "Launch currently open script",
            "type": "php",
            "request": "launch",
            "program": "${file}",
            "cwd": "${fileDirname}",
            "port": 9003
        }
    ]
}

З Visual Studio Code, перебуваючи в каталозі вашого проекту, перейдіть до налагоджувача й натисніть зелену кнопку відтворення з написом "Listen for Xdebug":

Якщо ви перейдете у браузер і оновите сторінку, IDE має автоматично взяти фокус на себе, тобто сеанс налагодження готовий. За замовчуванням все є точкою зупинки, тому виконання зупиняється на першій інструкції. Потім ви маєте перевірити поточні змінні, перейти через код, увійти в код, ...

Під час налагодження ви можете зняти прапорець "Everything" і явно встановити точки зупинки у своєму коді.

Якщо ви новачок у покроковому налагодженні, прочитайте навчальний посібник із Visual Studio Code, де все пояснюється візуально.

This work, including the code samples, is licensed under a Creative Commons BY-NC-SA 4.0 license.
TOC
    Version