Press "Enter" to skip to content

PHP Internals — the global Keyword

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

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.