Jak działa Zend Engine
Prześledź drogę PHP od kodu źródłowego przez opcode’y do wykonania
Jak działa Zend Engine to bezpłatna lekcja PHP Academy na CoddyKit. To lekcja 1 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej PHP Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs PHP Academy zawiera 4 lekcji w sumie.
Wewnątrz silnika
PHP nie interpretuje kodu źródłowego wiersz po wierszu. Zend Engine kompiluje skrypt do postaci pośredniej nazywanej kodami operacyjnymi, a następnie wykonuje je maszyna wirtualna. Zrozumienie tego potoku wyjaśnia kwestie wydajności, OPcache i JIT.
W tej lekcji omówiono drogę: kod źródłowy → tokeny → AST → kody operacyjne → wykonywanie przez maszynę wirtualną.
Potok przetwarzania
Każde żądanie pliku PHP przechodzi przez cztery fazy:
- Analiza leksykalna — tekst źródłowy → tokeny (skaner oparty na re2c)
- Analiza składniowa — tokeny → Abstract Syntax Tree (gramatyka Bison)
- Kompilacja — AST → tablica kodów operacyjnych (op_array)
- Wykonywanie — Zend VM przechodzi po kodach operacyjnych
Bez OPcache pierwsze trzy fazy są powtarzane przy każdym żądaniu.
Tokenizacja
Analizator leksykalny przekształca znaki w tokeny, takie jak T_VARIABLE, T_ECHO i T_STRING. PHP udostępnia ten etap za pośrednictwem token_get_all() / PhpToken — dokładnie z tego korzystają narzędzia takie jak PHP-CS-Fixer.
<?php
$src = '<?php $x = 1 + 2; echo $x;';
foreach (PhpToken::tokenize($src) as $tok) {
if ($tok->isIgnorable()) continue; // skip whitespace
printf("%-12s %s\n", $tok->getTokenName(), trim($tok->text));
}
?>AST
Tokeny są analizowane składniowo i przekształcane w drzewo węzłów — na przykład węzeł przypisania, którego elementami potomnymi są zmienna i wyrażenie operacji binarnej. PHP buduje to AST wewnętrznie, a nikic/php-parser odtwarza równoważną strukturę w kodzie użytkownika na potrzeby narzędzi do analizy statycznej, takich jak PHPStan.
<?php
// Conceptual AST for: $x = 1 + 2;
//
// AST_ASSIGN
// ├── AST_VAR ($x)
// └── AST_BINARY_OP (+)
// ├── 1
// └── 2
//
// At compile time PHP folds 1 + 2 into a literal 3
// (constant folding) before generating opcodes.
echo "AST drives opcode generation\n";
?>Kody operacyjne
Kompilator emituje op_array — płaską listę kodów operacyjnych. Każdy kod operacyjny ma numer kodu (np. ZEND_ADD, ZEND_ECHO, ZEND_ASSIGN) oraz maksymalnie dwa operandy i wynik, z których każdy jest zmienną skompilowaną (CV), wartością tymczasową (TMP) lub stałą.
<?php
// Opcodes for: $x = 1 + 2; echo $x;
//
// line op operands result
// --- ------------ ----------------- -------
// 1 ADD 1, 2 ~0
// 1 ASSIGN $x, ~0
// 1 ECHO $x
// 1 RETURN 1
//
// ~0 is a temporary; $x is a compiled variable (CV).
echo "op_array is what OPcache stores\n";
?>Inspekcja kodów operacyjnych
Wygenerowane kody operacyjne można wyświetlić za pomocą rozszerzenia VLD lub ustawienia opcache.opt_debug_level w OPcache. Pozwala to zobaczyć składanie stałych, eliminację martwego kodu oraz sposób, w jaki przepływ sterowania zostaje przekształcony w kody operacyjne JMP/JMPZ.
# Dump opcodes with VLD
php -d vld.active=1 -d vld.execute=0 script.php
# Or via OPcache optimizer debug (pre/post optimization)
php -d opcache.opt_debug_level=0x10000 script.php # before opt
php -d opcache.opt_debug_level=0x20000 script.php # after optZmienne skompilowane (CV)
Zmienne lokalne w skompilowanym kodzie nie wymagają wyszukiwania w tablicy haszującej — kompilator przypisuje każdej z nich numerowane gniazdo CV. Drugie odwołanie do $x jest indeksem tablicy, a nie wyszukiwaniem w tablicy symboli. To jeden z głównych powodów, dla których zmienne lokalne są szybkie.
<?php
// Each named local gets a fixed CV slot at compile time:
// $a -> CV0 $b -> CV1 $sum -> CV2
function add(int $a, int $b): int {
$sum = $a + $b; // ADD CV0, CV1 -> CV2
return $sum; // RETURN CV2
}
echo add(2, 3) . PHP_EOL;
?>Pętla wykonywania maszyny wirtualnej
Wykonawca (execute_ex) przechodzi po op_array. Każdy kod operacyjny odpowiada funkcji obsługi; PHP może realizować to rozsyłanie za pomocą ogromnej instrukcji switch, obliczanych skoków lub rozwiązania hybrydowego (domyślnego i najszybszego w kompilatorach, które je obsługują). Wskaźnik opline przesuwa się naprzód, a kody operacyjne JMP zmieniają jego położenie na potrzeby sterowania przepływem.
<?php
// if ($n > 0) echo 'pos'; compiles roughly to:
//
// IS_SMALLER 0, $n -> ~T
// JMPZ ~T, ->L1 ; if false, skip
// ECHO 'pos'
// L1:
// RETURN 1
//
// The VM follows opline; JMPZ rewrites it conditionally.
$n = 5;
if ($n > 0) echo "pos\n";
?>Rola OPcache
OPcache buforuje op_array w pamięci współdzielonej, pomijając analizę leksykalną, składniową i kompilację przy kolejnych żądaniach. Wykonuje również etap optymalizacji (składanie stałych, usuwanie martwego kodu, łączenie kodów operacyjnych). Maszyna wirtualna nadal wykonuje buforowane kody operacyjne przy każdym żądaniu — i właśnie w tym pomaga później JIT.
Koszt wywołań funkcji
Wywołania umieszczają na stosie maszyny wirtualnej nową ramkę: kody operacyjne INIT_FCALL, SEND_VAL/SEND_VAR dla każdego argumentu, a następnie DO_FCALL. Pozwala to zrozumieć, dlaczego nadmiar wielu krótkich wywołań funkcji ma mierzalny koszt oraz dlaczego rozwijanie funkcji (inlining) i JIT mają znaczenie w gorących pętlach.
<?php
// square($x) compiles to a call sequence:
// INIT_FCALL 'square'
// SEND_VAR $x
// DO_FCALL -> ~R
// ASSIGN $y, ~R
function square(int $x): int { return $x * $x; }
$total = 0;
for ($i = 1; $i <= 5; $i++) {
$total += square($i); // one call sequence per iteration
}
echo $total . PHP_EOL; // 1+4+9+16+25 = 55
?>Kończenie obsługi żądania
Po zakończeniu wykonywania PHP zamyka żądanie: zwalnia zmienne, opróżnia bufor wyjścia, a następnie resetuje arenę alokatora silnika. W modelu FPM typu shared-nothing każde żądanie rozpoczyna się w czystym środowisku — dlatego błąd krytyczny w jednym żądaniu nie może uszkodzić innego. Współdzielone przez OPcache op_arrays przetrwają między żądaniami, ale stan wykonawcy już nie.
Szybki test
Co dokładnie przechowuje OPcache, aby pominąć ponowną kompilację?
Podsumowanie
Potok Zend obejmuje analizę leksykalną → analizę składniową → kompilację → wykonywanie. Kod źródłowy staje się tokenami, następnie AST, a potem tablicą op_array zawierającą kody operacyjne, które wykonuje pętla wykonawcy maszyny wirtualnej. Zmienne lokalne są przechowywane w numerowanych gniazdach CV, a wywołania umieszczają na stosie ramki. OPcache buforuje op_array (wraz z etapem optymalizacji), dzięki czemu przy każdym żądaniu powtarza się tylko wykonywanie.
Często zadawane pytania
Czy lekcja „Jak działa Zend Engine” jest bezpłatna?
Tak — pełny tekst „Jak działa Zend Engine” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu PHP Academy, przejdź na CoddyKit PRO. Kurs PHP Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Jak działa Zend Engine”?
Prześledź drogę PHP od kodu źródłowego przez opcode’y do wykonania Ćwiczysz PHP Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć PHP Academy?
Nie wymagamy żadnego doświadczenia. PHP Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 1 z 4.
Ile czasu zajmuje lekcja „Jak działa Zend Engine”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji PHP Academy?
Tak. Każda lekcja PHP Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Jak działa Zend Engine
- Zarządzanie pamięcią i garbage collection
- OPcache i kompilacja JIT
- Pisanie podstawowego rozszerzenia PHP w C