原:PHP内核源码分析之foreach 二
作者 斯人 | 发布于 2012 年 3 月 26 日
PHP PHP内核

如果您没有看第一节,请务必先看 PHP内核源码分析之foreach 一
foreach的变量可以是引用,也可以是普通变量..
例如

一.
foreach ($arr as $v){

}
二.
foreach($arr as &$v){

}

继续看内核源码

{ zend_do_foreach_begin(&$1, &$2, &$3, &$4, 1 TSRMLS_CC); }
                foreach_variable foreach_optional_arg ')' 
{ zend_do_foreach_cont(&$1, &$2, &$4, &$6, &$7 TSRMLS_CC); }

为了实现这个功能,
PHP 的 foreach_variable 定义了两个规则

foreach_variable:
                variable                { zend_check_writable_variable(&$1); $$ = $1; }
        |       '&' variable            { zend_check_writable_variable(&$2); $$ = $2;  $$.u.EA.type |= ZEND_PARSED_REFERENCE_VARIABLE; }
;

多了一个 $$.u.EA.type |= ZEND_PARSED_REFERENCE_VARIABLE;
意思为 以引用的方式返回该值.
zend_check_writable_variable 用来检测 变量是否是函数或者某个对象的方法,
再来看看zend_do_foreach_cont

void zend_do_foreach_cont(znode *foreach_token, const znode *open_brackets_token, const znode *as_token, znode *value, znode *key TSRMLS_DC) /* {{{ */
{
        zend_op *opline;
        znode dummy, value_node;        
        zend_bool assign_by_ref=0; 

        opline = &CG(active_op_array)->opcodes[as_token->u.opline_num];        
        if (key->op_type != IS_UNUSED) {
                znode *tmp;

                /* switch between the key and value... */                tmp = key; 
                key = value;
                value = tmp; 

                /* Mark extended_value in case both key and value are being used */
                opline->extended_value |= ZEND_FE_FETCH_WITH_KEY;
        }    

        if ((key->op_type != IS_UNUSED) && (key->u.EA.type & ZEND_PARSED_REFERENCE_VARIABLE)) {
                zend_error(E_COMPILE_ERROR, "Key element cannot be a reference");
        }    

        if (value->u.EA.type & ZEND_PARSED_REFERENCE_VARIABLE) {
                assign_by_ref = 1; 
                if (!(opline-1)->extended_value) {
                }
                /* Mark extended_value for assign-by-reference */
                opline->extended_value |= ZEND_FE_FETCH_BYREF;
                CG(active_op_array)->opcodes[foreach_token->u.opline_num].extended_value |= ZEND_FE_RESET_REFERENCE;
        } else {
                zend_op *foreach_copy;
                zend_op *fetch = &CG(active_op_array)->opcodes[foreach_token->u.opline_num];
                zend_op *end = &CG(active_op_array)->opcodes[open_brackets_token->u.opline_num];

                /* Change "write context" into "read context" */
                fetch->extended_value = 0;  /* reset ZEND_FE_RESET_VARIABLE */
                while (fetch != end) {
                        --fetch;
                        if (fetch->opcode == ZEND_FETCH_DIM_W && fetch->op2.op_type == IS_UNUSED) {
                                zend_error(E_COMPILE_ERROR, "Cannot use [] for reading");
                        }
                        fetch->opcode -= 3; /* FETCH_W -> FETCH_R */
                }
                /* prevent double SWITCH_FREE */
                zend_stack_top(&CG(foreach_copy_stack), (void **) &foreach_copy);
                foreach_copy->op1.op_type = IS_UNUSED;
        }

        value_node = opline->result;

        if (assign_by_ref) {
                zend_do_end_variable_parse(value, BP_VAR_W, 0 TSRMLS_CC);
                /* Mark FE_FETCH as IS_VAR as it holds the data directly as a value */
                zend_do_assign_ref(NULL, value, &value_node TSRMLS_CC);
        } else {
                zend_do_assign(&dummy, value, &value_node TSRMLS_CC);
                zend_do_free(&dummy TSRMLS_CC);
        }

        if (key->op_type != IS_UNUSED) {
                znode key_node;

                opline = &CG(active_op_array)->opcodes[as_token->u.opline_num+1];
                opline->result.op_type = IS_TMP_VAR;
                opline->result.u.EA.type = 0;
                opline->result.u.opline_num = get_temporary_variable(CG(active_op_array));
                key_node = opline->result;

                zend_do_assign(&dummy, key, &key_node TSRMLS_CC);
                zend_do_free(&dummy TSRMLS_CC);
        }

        do_begin_loop(TSRMLS_C);
        INC_BPC(CG(active_op_array));
}

8-11行 交换 key和value的值
19-21行 key不能作为引用的方式获取
23-29行 val以引用的方式获取
32行 获取foreach执行的op代码
33行 获取 括号的op代码
51-54 获取引用值
54-58 复制值
60-71 获取key的值
do_begin_loop 初始化 foreach 循环.

void zend_do_foreach_end(const znode *foreach_token, const znode *as_token TSRMLS_DC) /* {{{ */
{        zend_op *container_ptr;
        zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);

        opline->opcode = ZEND_JMP;
        opline->op1.u.opline_num = as_token->u.opline_num;
        SET_UNUSED(opline->op1);
        SET_UNUSED(opline->op2);

        CG(active_op_array)->opcodes[foreach_token->u.opline_num].op2.u.opline_num = get_next_op_number(CG(active_op_array)); /* FE_RESET */
        CG(active_op_array)->opcodes[as_token->u.opline_num].op2.u.opline_num = get_next_op_number(CG(active_op_array)); /* FE_FETCH */

        do_end_loop(as_token->u.opline_num, 1 TSRMLS_CC);

        zend_stack_top(&CG(foreach_copy_stack), (void **) &container_ptr);
        generate_free_foreach_copy(container_ptr TSRMLS_CC);
        zend_stack_del_top(&CG(foreach_copy_stack));

        DEC_BPC(CG(active_op_array));
}

生成一个跳转的opcode,
设置 op为ZEND_JMP
当前op将要跳转的位置
设置op跳出的位置
结束循环
一些清理操作…

foreach_satement就是 我们foreach的中间代码了 它会解析成 zend_do_extended_info这个函数来处理中间代码
这个函数之前的文章有提到过..这里就不说了

原文出处:http://www.imsiren.com/archives/487