Odkrywanie wewnętrznych mechanizmów Symfony
Od dłuższego czasu używamy Symfony do stworzenia potężnej aplikacji i większość kodu wykonywanego przez aplikację pochodzi nie od nas, ale z Symfony. Kilkaset linii kodu kontra tysiące linii kodu.
Lubię rozumieć, jak rzeczy działają od środka. Zawsze fascynowały mnie narzędzia, które pomagają zrozumieć, jak coś działa. Pierwsze użycie debuggera lub wykorzystanie po raz pierwszy funkcji ptrace
to dla mnie magiczne wspomnienia.
Chcesz lepiej zrozumieć, jak działa Symfony? Czas zgłębić mechanizmy, jakich Symfony używa, aby Twoja aplikacja robiła to, co robi. Zamiast opisywać z teoretycznej perspektywy, jak Symfony radzi sobie z żądaniem HTTP – co byłoby dość nudne – wykorzystamy narzędzie Blackfire, aby uzyskać pewne wizualne reprezentacje i skorzystać z nich do przedstawienia bardziej zaawansowanych problemów.
Zrozumienie wewnętrznych mechanizmów Symfony przy użyciu Blackfire
Już wiesz, że wszystkie żądania HTTP są obsługiwane przez jeden punkt wejścia: plik public/index.php
. Ale co dalej? Jak kontrolery są wywoływane?
Sprofilujmy produkcyjną, angielską wersję strony głównej przy użyciu Blackfire poprzez wtyczkę Blackfire do przeglądarki:
1
$ symfony remote:open
Albo bezpośrednio w wierszu poleceń:
1
$ blackfire curl `symfony cloud:env:url --pipe --primary`en/
Po przejściu do widoku "Oś czasu" w profilu, znajdziesz widok podobny do poniższego:
Najedź na kolorowe paski na osi czasu, aby uzyskać więcej informacji na temat każdego wywołania; poznasz lepiej działanie Symfony:
- Głównym punktem wejścia jest
public/index.php
; - Metoda
Kernel::handle()
obsługuje żądanie; - Wywołuje
HttpKernel
, który emituje pewne zdarzenia; - Pierwszym zdarzeniem jest
RequestEvent
; - Metoda
ControllerResolver::getController()
jest wywoływana aby określić, który kontroler powinien być wywoływany dla przychodzącego adresu URL; - Metoda
ControllerResolver::getArguments()
jest wywoływana aby określić, które argumenty mają być przekazane do kontrolera (wywoływany jest konwerter parametrów); - W następnie wywoływanej metodzie
ConferenceController::index()
wykonywana jest większość naszego kodu; - Metoda
ConferenceRepository::findAll()
pobiera wszystkie konferencje z bazy danych (zwróć uwagę na połączenie z bazą danych za pośrednictwemPDO::__construct()
); - Metoda
Twig\Environment::render()
renderuje szablon; - Zdarzenia
ResponseEvent
orazFinishRequestEvent
są wywoływane, ale wygląda na to, że żaden nasłuchiwacz zdarzeń (ang. event listener) nie jest zarejestrowany, ponieważ zdarzenia wykonują się naprawdę szybko.
Oś czasu to świetny sposób na zrozumienie, jak działa kod; jest to bardzo przydatne, kiedy otrzymujesz projekt rozwijany przez kogoś innego.
Teraz sprofiluj tę samą stronę z maszyny lokalnej w środowisku deweloperskim:
1
$ blackfire curl `symfony var:export SYMFONY_PROJECT_DEFAULT_ROUTE_URL`en/
Otwórz profil. Powinno nastąpić przekierowanie do widoku grafu połączeń, a ponieważ żądanie było naprawdę szybkie, oś czasu będzie prawie pusta:
Rozumiesz, co się dzieje? Pamięć podręczna HTTP (ang. HTTP cache) jest włączona i przez to profilujemy warstwę pamięci podręcznej Symfony. Ponieważ strona znajduje się w pamięci podręcznej, HttpCache\Store::restoreResponse()
otrzymuje odpowiedź HTTP z pamięci podręcznej i kontroler nigdy nie jest wywoływany.
Wyłącz warstwę pamięci podręcznej w public/index.php
tak jak w poprzednim etapie i spróbuj ponownie. Od razu widać, że profil wygląda zupełnie inaczej:
Główne różnice są następujące:
- Zdarzenie
TerminateEvent
, które nie było widoczne w środowisku produkcyjnym, zajmuje duży procent czasu wykonania; możemy dostrzec, że jest to zdarzenie odpowiedzialne za przechowywanie danych profilera Symfony zebranych podczas żądania; - W wywołaniu
ConferenceController::index()
, zwróć uwagę naSubRequestHandler::handle()
, metodę, która renderuje ESI (dlatego mamy dwa wywołaniaProfiler::saveProfile()
, jedno dla głównego żądania i jedno dla ESI).
Przejdź do osi czasu, aby dowiedzieć się więcej; przejdź do widoku grafu połączeń, aby zobaczyć różne reprezentacje tych samych danych.
Jak właśnie odkryliśmy, kod wykonywany w środowisku deweloperskim i produkcyjnym jest zupełnie inny. Środowisko programistyczne jest wolniejsze, ponieważ profiler Symfony próbuje zebrać wiele danych, aby ułatwić debugowanie. Dlatego też należy zawsze stosować profilowanie w środowisku produkcyjnym, nawet lokalnie.
W ramach ciekawych eksperymentów: sprofiluj stronę błędu, stronę /
(która jest przekierowaniem) lub zasób API. Każdy profil powie Ci trochę więcej o tym, jak działa Symfony, jakie klasy/metody są wywoływane, co ma większy koszt wykonania a co niższy.
Używanie dodatku Blackfire Debug
Domyślnie Blackfire usuwa wszystkie wywołania metod, które nie są wystarczająco znaczące, aby uniknąć ładowania zbyt dużej ilości danych i pokazywania dużych wykresów. W przypadku używania Blackfire jako narzędzia do debugowania, lepiej jest zachować wszystkie wywołania używając dodatku debug.
W wierszu poleceń, użyj flagi --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/
W środowisku produkcyjnym, zobaczysz na przykład ładowanie pliku o nazwie .env.local.php
:
Skąd to się wzięło? Platform.sh wykonuje pewne optymalizacje podczas wdrażania aplikacji Symfony, np. optymalizując autoloader Composer (--optimize-autoloader --apcu-autoloader --classmap-authoritative
) oraz zmienne środowiskowe zdefiniowane w pliku .env
(aby uniknąć przetwarzania pliku dla każdego żądania) poprzez wygenerowanie pliku .env.local.php
:
1
$ symfony run composer dump-env prod
Blackfire jest bardzo potężnym narzędziem, które pomaga zrozumieć ,w jaki sposób kod jest wykonywany przez PHP. Poprawa wydajności jest tylko jednym ze sposobów wykorzystania profilera.
Używanie debuggera krokowego z Xdebug
Osie czasu i grafy wywołań w Blackfire pozwalają pokazać, który plik/funkcja/metoda jest wykonywana przez interpreter PHP, celem większego zrozumienia kodu źródłowego projektu.
Kolejnym sposobem na podążanie za wykonywanym kodem jest debugger krokowy, jak np. Xdebug. Debugger krokowy pozwala na interaktywny spacer po kodzie PHP, żeby zweryfikować przepływ kodu oraz sprawdzić struktury danych. Jest to bardzo przydatne podczas debugowania niespodziewanych zachowań i zastępuje popularną technikę "var_dump()/exit()".
Na początek, zainstaluj rozszerzenie PHP xdebug
. Sprawdź czy jest zainstalowane za pomocą poniższej komendy:
1
$ symfony php -v
W odpowiedzi znajdziesz informacje o 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
Możesz także sprawdzić, czy Xdebug jest zainstalowany dla PHP-FPM, poprzez przejście do przeglądarki i kliknięcie w link "View phpinfo()", który pojawi się po najechaniu na logo Symfony w pasku debugowania:
Teraz, włącz tryb debug
w Xdebug:
Domyślnie, Xdebug wysyła dane do portu 9003 twojego lokalnego hosta.
Wyzwalanie Xdebug może zostać zrealizowane na wiele sposobów, jednak najłatwiejsze będzie korzystanie z Xdebug z twojego IDE. W tym rozdziale, użyjemy Visual Studio Code do zademonstrowania jak to działa. Zainstaluj PHP Debug rozszerzenie poprzez uruchomienie funkcji "Quick Open" (Ctrl+P
), następnie wklej poniższą komendę i wciśnij enter:
1
ext install felixfbecker.php-debug
Utwórz następujący plik konfiguracyjny:
Będąc w Visual Studio Code w twoim katalogu projektu, przejdź do debuggera i kliknij na zielony przycisk z napisem "Listen for Xdebug":
Jeśli przejdziesz do przeglądarki i odświeżysz stronę, IDE automatycznie się otworzy, sygnalizując, że sesja debugowania jest gotowa. Domyślnie, wszystko jest punktem przerwania (ang. breakpoint), więc wykonywanie kodu zatrzyma się na pierwszej instrukcji. Od tej pory to, w jaki sposób przeglądasz kod - poprzez analizę zmiennych, przechodzenie przez kolejne linijki kodu, czy wchodzenie i analizowanie kodu konkretnych funkcji, zależy wyłącznie od Ciebie.
Podczas debugowania, możesz odznaczyć punkt przerwania "Everything" i samemu dokładnie ustawić te punkty w twoim kodzie.
Jeśli debuggery krokowe są dla ciebie nowością, przeczytaj ten świetny poradnik dla Visual Studio Code, który wszystko wizualnie wyjaśnia.