Дослідження внутрішніх механізмів 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
:
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
Створіть наступний файл конфігурації:
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, де все пояснюється візуально.