In PHP, regular local variables are allocated on the zend_execute_data structure. Each time a zend_op_array is executed, a new zend_execute_data instance is created. Local variables are allocated at the start of execution and freed when execution ends—this defines thier lifetime.
Static variables are an exception: they persist beyond function execution. When control exits a function, static variables retain their values and remain available during subsequent calls.
A static variable is declared using the static keyword:
function counter() {
static $value = 10;
$value++;
echo $value, "\n";
}
counter(); // outputs 11
counter(); // outputs 12
Storage Mechanism
Unlike regular locals, static variables are not stored in zend_execute_data. Instead, they reside in a hash table attached to the zend_op_array as static_variables. This ensures persistence across multiple invocations of the same function.
Initialization occurs once, during the compile phase, not at runtime. In the example above, $value = 10 is evaluated and inserted into static_variables when the script is compiled. Subsequent calls do not reinitialize the value, which explains the incrementing output.
Because initialization happens at compile time, the initial value must be a constant expression. Assigning a non-constant, such as another variable (static $x = $y;), results in a compile error.
Access and Compilation
During compilation, a static declraation is parsed into a ZEND_AST_STATIC AST node. The compiler processes this via zend_compile_static_var(), which evaluates any initializer into a zval and delegates to zend_compile_static_var_common().
This function ensures the static_variables hash table exists in the current zend_op_array, then inserts the variable using its name as the key:
if (!CG(active_op_array)->static_variables) {
ALLOC_HASHTABLE(CG(active_op_array)->static_variables);
zend_hash_init(CG(active_op_array)->static_variables, 8, NULL, ZVAL_PTR_DTOR, 0);
}
zend_hash_update(
CG(active_op_array)->static_variables,
Z_STR(var_node.u.constant),
value
);
After insertion, two opcodes are emitted:
ZEND_FETCH_Wwithextended_value = ZEND_FETCH_STATIC: This creates anIS_INDIRECTzval pointing to the entry instatic_variables.ZEND_ASSIGN_REF: This binds the local variable (e.g.,$value) as a reference to that indirect zval.
Thus, inside the function, $value is not the static variable itself—it is a local symbol that references the persistent entry in static_variables. Conceptually, the statement static $value = 10; translates at runtime to:
$value =& $op_array->static_variables['value'];
This indirection enables the familiar syntax while maintaining separation between transient local storage and persistent static state.