Como funciona o Zend Engine
Acompanhe o PHP do código-fonte aos opcodes e à execução.
Como funciona o Zend Engine é uma aula grátis de PHP Academy no CoddyKit. Esta é a aula 1 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de PHP Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de PHP Academy inclui 4 aulas no total.
Por Dentro do Mecanismo
O PHP não é interpretado linha por linha a partir do código-fonte. O mecanismo Zend compila o seu script em uma representação intermediária chamada códigos de operação, que são então executados por uma máquina virtual. Compreender esse fluxo explica o desempenho, o OPcache e o JIT.
Esta lição percorre o caminho código-fonte → tokens → AST → códigos de operação → execução da VM.
O Fluxo
Cada requisição a um arquivo PHP passa por quatro fases:
- Análise léxica — texto do código-fonte → tokens (analisador baseado em re2c)
- Análise sintática — tokens → Árvore Sintática Abstrata (gramática Bison)
- Compilação — AST → matriz de códigos de operação (op_array)
- Execução — a VM Zend percorre os códigos de operação
Sem o OPcache, as três primeiras fases se repetem em cada requisição.
Tokenização
O analisador léxico transforma caracteres em tokens como T_VARIABLE, T_ECHO, T_STRING. O PHP expõe essa etapa por meio de token_get_all() / PhpToken, exatamente o que ferramentas como PHP-CS-Fixer utilizam.
<?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));
}
?>A AST
Os tokens são analisados em uma árvore de nós — um nó de atribuição cujos filhos são uma variável e uma expressão de operação binária. O PHP cria essa AST internamente (e o nikic/php-parser reconstrói uma equivalente no código do usuário para ferramentas de análise estática como o 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";
?>Códigos de Operação
O compilador emite um op_array: uma lista linear de códigos de operação. Cada código de operação tem um número de código de operação (por exemplo, ZEND_ADD, ZEND_ECHO, ZEND_ASSIGN) e até dois operandos e um resultado, cada um sendo uma variável compilada (CV), um temporário (TMP) ou uma constante.
<?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";
?>Inspecionando Códigos de Operação
É possível despejar os códigos de operação gerados com a extensão VLD ou com opcache.opt_debug_level do OPcache. Isso revela a propagação de constantes, a eliminação de código morto e como o fluxo de controle se transforma em códigos de operação 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 optVariáveis Compiladas (CVs)
Variáveis locais não são consultas a tabelas de dispersão no código compilado — o compilador atribui a cada uma uma posição de CV numerada. Acessar $x pela segunda vez corresponde a acessar um índice de matriz, não a pesquisar na tabela de símbolos. Esse é um dos principais motivos pelos quais as variáveis locais são rápidas.
<?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;
?>O Laço de Execução da VM
O executor (execute_ex) percorre o op_array. Cada código de operação corresponde a uma função de tratamento; o PHP pode implementar esse despacho como um switch gigantesco, saltos calculados ou um híbrido (o padrão, e a opção mais rápida nos compiladores compatíveis). O ponteiro opline avança; os códigos de operação JMP o movem para controlar o fluxo.
<?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";
?>Onde o OPcache se Encaixa
O OPcache armazena o op_array na memória compartilhada, ignorando a análise léxica, a análise sintática e a compilação nas requisições seguintes. Ele também executa uma etapa de otimização (propagação de constantes, remoção de código morto e fusão de códigos de operação). A VM ainda executa os códigos de operação armazenados em cache a cada requisição — é aí que o JIT ajuda posteriormente.
Sobrecarga das Chamadas de Função
As chamadas empilham um novo quadro de pilha da VM: os códigos de operação INIT_FCALL, SEND_VAL/SEND_VAR para cada argumento e, em seguida, DO_FCALL. Saber disso explica por que chamadas excessivas a funções minúsculas têm um custo mensurável e por que a expansão de funções e o JIT são importantes em laços intensivos.
<?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
?>Encerramento da Requisição
Após a execução, o PHP desmonta a requisição: as variáveis em execução são liberadas, a saída é descarregada e, em seguida, o mecanismo reinicializa sua arena do alocador. No modelo FPM sem compartilhamento, cada requisição começa limpa — por isso um erro fatal em uma requisição não pode corromper outra. Os op_arrays compartilhados do OPcache sobrevivem entre requisições; o estado do executor, não.
Verificação Rápida
O que exatamente o OPcache armazena para evitar a recompilação?
Resumo
O fluxo Zend é análise léxica → análise sintática → compilação → execução. O código-fonte se transforma em tokens, depois em uma AST e, por fim, em um op_array de códigos de operação executados pelo laço do executor da VM, com as variáveis locais armazenadas em posições de CV numeradas e as chamadas empilhando quadros. O OPcache armazena o op_array (além de executar uma etapa de otimização), de modo que apenas a execução se repete a cada requisição.
Perguntas Frequentes
A aula “Como funciona o Zend Engine” é grátis?
Sim — o texto completo de “Como funciona o Zend Engine” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de PHP Academy, atualize para CoddyKit PRO. O curso de PHP Academy inclui 4 aulas no total.
O que vou aprender em “Como funciona o Zend Engine”?
Acompanhe o PHP do código-fonte aos opcodes e à execução. Você pratica PHP Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.
Preciso ter experiência prévia para começar PHP Academy?
Nenhuma experiência prévia é necessária. PHP Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 1 de 4.
Quanto tempo leva a aula “Como funciona o Zend Engine”?
A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.
Posso escrever e executar código nesta aula de PHP Academy?
Sim. Cada aula de PHP Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.
Todas as aulas deste curso
- Como funciona o Zend Engine
- Gerenciamento de memória e coleta de lixo
- OPcache e compilação JIT
- Escrevendo uma extensão PHP básica em C