Come funziona Zend Engine
Segua PHP dal codice sorgente agli opcode fino all'esecuzione
Come funziona Zend Engine è una lezione PHP Academy gratuita su CoddyKit. Questa è la lezione 1 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento PHP Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso PHP Academy include 4 lezioni in totale.
All'interno del motore
PHP non viene interpretato riga per riga a partire dal codice sorgente. Lo Zend Engine compila lo script in una rappresentazione intermedia chiamata opcode, che viene poi eseguita da una macchina virtuale. Comprendere questa pipeline aiuta a capire le prestazioni, OPcache e JIT.
Questa lezione illustra il percorso sorgente → token → AST → opcode → esecuzione della VM.
La pipeline
Ogni richiesta a un file PHP attraversa quattro fasi:
- Analisi lessicale — testo sorgente → token (scanner basato su re2c)
- Analisi sintattica — token → albero sintattico astratto (grammatica Bison)
- Compilazione — AST → array di opcode (op_array)
- Esecuzione — la VM Zend attraversa gli opcode
Senza OPcache, le prime tre fasi si ripetono a ogni richiesta.
Tokenizzazione
Il lexer trasforma i caratteri in token come T_VARIABLE, T_ECHO, T_STRING. PHP espone questa fase tramite token_get_all() / PhpToken, proprio ciò che utilizzano strumenti come 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));
}
?>L'AST
I token vengono analizzati in un albero di nodi: un nodo di assegnazione i cui figli sono una variabile e un'espressione binaria. PHP costruisce internamente questo AST, mentre nikic/php-parser ne ricostruisce uno equivalente in userland per strumenti di analisi statica come 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";
?>Opcode
Il compilatore emette un op_array: un elenco lineare di opcode. Ogni opcode ha un numero di opcode, ad esempio ZEND_ADD, ZEND_ECHO, ZEND_ASSIGN, e fino a due operandi più un risultato, ciascuno dei quali è una variabile compilata (CV), un temporaneo (TMP) o una costante.
<?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";
?>Ispezione degli opcode
Può visualizzare gli opcode generati con l'estensione VLD o con opcache.opt_debug_level di OPcache. Questo rivela il constant folding, l'eliminazione del codice morto e il modo in cui il flusso di controllo diventa una sequenza di opcode 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 optVariabili compilate (CV)
Nel codice compilato, le variabili locali non richiedono ricerche in una tabella hash: il compilatore assegna a ciascuna uno slot CV numerato. Il secondo accesso a $x è un indice di array, non una ricerca nella tabella dei simboli. Questo è uno dei motivi principali per cui le variabili locali sono veloci.
<?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;
?>Il ciclo di esecuzione della VM
L'esecutore (execute_ex) attraversa l'op_array. Ogni opcode corrisponde a una funzione handler; PHP può realizzare questo dispatch con un enorme switch, con computed goto oppure con un approccio ibrido, quello predefinito e più veloce nei compilatori che lo supportano. Il puntatore opline avanza; gli opcode JMP lo spostano per gestire il flusso di controllo.
<?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";
?>Dove si colloca OPcache
OPcache memorizza l'op_array nella memoria condivisa, evitando l'analisi lessicale, l'analisi sintattica e la compilazione nelle richieste successive. Esegue inoltre un passaggio di ottimizzazione (constant folding, eliminazione del codice morto, fusione degli opcode). La VM esegue comunque gli opcode memorizzati nella cache a ogni richiesta: è qui che JIT può aiutare in seguito.
Overhead delle chiamate di funzione
Le chiamate inseriscono un nuovo frame nello stack della VM: gli opcode INIT_FCALL, SEND_VAL/SEND_VAR per ogni argomento, quindi DO_FCALL. Comprendere questo meccanismo spiega perché un numero eccessivo di chiamate a funzioni molto piccole abbia un costo misurabile e perché l'inlining/JIT sia importante nei loop caldi.
<?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
?>Chiusura della richiesta
Al termine dell'esecuzione, PHP smantella la richiesta: libera le variabili in uso, svuota l'output, quindi reimposta l'arena dell'allocatore. In un modello FPM shared-nothing ogni richiesta riparte da zero: per questo un errore irreversibile in una richiesta non può danneggiarne un'altra. Gli op_array condivisi di OPcache sopravvivono tra le richieste; lo stato dell'esecutore no.
Verifica rapida
Che cosa memorizza esattamente OPcache per evitare la ricompilazione?
Riepilogo
La pipeline Zend è analisi lessicale → analisi sintattica → compilazione → esecuzione. Il sorgente diventa token, poi un AST e infine un op_array di opcode, eseguito dal ciclo dell'esecutore della VM; le variabili locali vengono memorizzate in slot CV numerati e le chiamate inseriscono frame nello stack. OPcache memorizza l'op_array, insieme a un passaggio di ottimizzazione, così a ogni richiesta si ripete solo l'esecuzione.
Domande Frequenti
La lezione «Come funziona Zend Engine» è gratuita?
Sì — il testo completo di «Come funziona Zend Engine» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso PHP Academy, passa a CoddyKit PRO. Il corso PHP Academy include 4 lezioni in totale.
Cosa imparerò in «Come funziona Zend Engine»?
Segua PHP dal codice sorgente agli opcode fino all'esecuzione Eserciti PHP Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.
Ho bisogno di esperienza per iniziare PHP Academy?
Non è richiesta alcuna esperienza precedente. PHP Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 1 di 4.
Quanto tempo richiede la lezione «Come funziona Zend Engine»?
La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.
Posso scrivere ed eseguire codice in questa lezione PHP Academy?
Sì. Ogni lezione PHP Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.
Tutte le lezioni di questo corso
- Come funziona Zend Engine
- Gestione della memoria e garbage collection
- OPcache e compilazione JIT
- Scrivere un'estensione PHP di base in C