- URL: https://www.laruence.com/en/2008/08/24/377.html
- Please include attribution when republishing.
With some time on my hands, I went through the whole pipeline — PHP's lexical analysis, parsing, opcode generation and execution — to analyse in detail how the global keyword is implemented.
When you write this in a script:
<?php
$var = "laruence";
function sample(){
global $var;
}
?>
do you know how PHP finds the global variable from within the function scope?
In an earlier article (Understanding PHP Internals: Opcodes) I explained that PHP's execution goes through the following stages:
1. Scanning(Lexing): turn the PHP code into language fragments (Tokens) 2. Parsing: turn the Tokens into simple, meaningful expressions 3. Compilation: compile the expressions into Opcodes 4. Execution: execute the Opcodes one at a time, in order, thereby realizing the functionality of the PHP script.
So the first stage is naturally Scanning. During lexical analysis, our
global $var;
gets parsed as:
T_GLOBAL var;
Next comes the parsing stage:
T_GLOBAL var;
yacc matches this via the rule:
statement:
| T_GLOBAL global_var_list ';'
....
global_var_list:
global_var_list ',' global_var { zend_do_fetch_global_variable(&$3, NULL, ZEND_FETCH_GLOBAL_LOCK TSRMLS_CC); }
| global_var { zend_do_fetch_global_variable(&$1, NULL, ZEND_FETCH_GLOBAL_LOCK TSRMLS_CC); }
;
Here, zend_do_fetch_global_variable is the function that actually generates the opcode:
zend_op *opline;
......
opline->opcode = ZEND_FETCH_W;
opline->result.op_type = IS_VAR;
......
opline->op2.u.EA.type = ZEND_FETCH_GLOBAL_LOCK;
And the op_handler for ZEND_FETCH_W is:
ZEND_VM_HANDLER(83, ZEND_FETCH_W, CONST|TMP|VAR|CV, ANY)
{
ZEND_VM_DISPATCH_TO_HELPER_EX(zend_fetch_var_address_helper, type, BP_VAR_W);
}
Let's look at zend_fetch_var_adress_helper:
.....
target_symbol_table = zend_get_target_symbol_table(opline, EX(Ts), type, varname TSRMLS_CC);
/*
if (!target_symbol_table) {
ZEND_VM_NEXT_OPCODE();
}
*/
if (zend_hash_find(target_symbol_table, varname->value.str.val, varname->value.str.len+1, (void **) &retval) == FAILUR
E) {
switch (type) {
case BP_VAR_R:
case BP_VAR_UNSET:
zend_error(E_NOTICE,"Undefined variable: %s", Z_STRVAL_P(varname));
/* break missing intentionally */
case BP_VAR_IS:
retval = &EG(uninitialized_zval_ptr);
break;
case BP_VAR_RW:
zend_error(E_NOTICE,"Undefined variable: %s", Z_STRVAL_P(varname));
/* break missing intentionally */
case BP_VAR_W: {
zval *new_zval = &EG(uninitialized_zval);
new_zval->refcount++;
zend_hash_update(target_symbol_table, varname->value.str.val, varname->value.str.len+1, &new_zval, siz
eof(zval *), (void **) &retval);
}
break;
EMPTY_SWITCH_DEFAULT_CASE()
}
}
As we can see, the core is the zend_get_targer_symbol_table function:
static inline HashTable *zend_get_target_symbol_table(zend_op *opline, temp_variable *Ts, int type, zval *variable TSRMLS_DC)
{
switch (opline->op2.u.EA.type) {
case ZEND_FETCH_LOCAL:
return EG(active_symbol_table);
break;
case ZEND_FETCH_GLOBAL:
case ZEND_FETCH_GLOBAL_LOCK:
return &EG(symbol_table);
break;
case ZEND_FETCH_STATIC:
if (!EG(active_op_array)->static_variables) {
ALLOC_HASHTABLE(EG(active_op_array)->static_variables);
zend_hash_init(EG(active_op_array)->static_variables, 2, NULL, ZVAL_PTR_DTOR, 0);
}
return EG(active_op_array)->static_variables;
break;
EMPTY_SWITCH_DEFAULT_CASE()
}
return NULL;
}
Right, so the picture is clear: if you declare a variable global, Zend goes looking for it in the global symbol_table, and if it isn't found there, it allocates the corresponding variable in the global symbol_table.
That is the mechanism by which global variables are implemented.
Be First to Comment