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

在PHP的数组中,提供了一个强大的 语言结构 foreach;
有了它我们就可以很方便的处理数组.
先来看看PHP代码

$arr = array(1, 2, 3, 4);
foreach ($arr as $value) {
    echo $value;
}

很眼熟吗..那么 PHP内核中怎么做的呢.
经过之前的研究,我们可以很快的找到 Lex && Yacc的解析器
Zend/zend_language_parser.y

T_FOREACH '(' variable T_AS 
                { 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); }
                foreach_statement { zend_do_foreach_end(&$1, &$4 TSRMLS_CC); }
        |       T_FOREACH '(' expr_without_variable T_AS 
                { zend_do_foreach_begin(&$1, &$2, &$3, &$4, 0 TSRMLS_CC); }
                variable foreach_optional_arg ')' { zend_check_writable_variable(&$6); zend_do_foreach_cont(&$1, &$2, &$4, &$6, &$7 TSRMLS_CC); }
                foreach_statement { zend_do_foreach_end(&$1, &$4 TSRMLS_CC); }

为什么是两个?
因为 foreach可以传递key啊

foreach ($arr as $key=> $value) {
    echo $value;
}

我们就先分析下
第一个 T_FOREACH

T_FOREACH ‘(‘ variable T_AS { 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); }
foreach_statement { zend_do_foreach_end(&$1, &$4 TSRMLS_CC); }
这段代码其实是可以跟
foreach ($arr as $value) {
echo $value;
}
对应起来的.. 每个部分我们用 来组合一下
‘(‘ { zend_do_foreach_begin(&$1, &$2, &$3, &$4, 1 TSRMLS_CC); } ‘)’ { zend_do_foreach_cont(&$1, &$2, &$4, &$6, &$7 TSRMLS_CC); }
这样就能与上面的PHP代码对应起来,方便比较.
那我们好好看看这几个函数 zend_do_foreach_begin,zend_do_foreach_cont,zend_do_foreach_end
zend_do_foreach_begin
zend_do_foreach_begin(&$1, &$2, &$3, &$4, 1 TSRMLS_CC);
执行为:
zend_do_foreach_begin(T_FOREACH , ‘(‘, variable , T_AS , 1 TSRMLS_CC);
跟进去看代码,逐一分析

void zend_do_foreach_begin(znode *foreach_token, znode *open_brackets_token, znode *array, znode *as_token, int variable TSRMLS_DC) /* {{{ */
{
        zend_op *opline; //生成的op
        zend_bool is_variable;//循环的变量是否是一个普通变量
        zend_bool push_container = 0; 
        zend_op dummy_opline;

        if (variable) {
                if (zend_is_function_or_method_call(array)) {
                        is_variable = 0; 
                } else {
                        is_variable = 1; 
                }    
                /* save the location of FETCH_W instruction(s) */
                open_brackets_token->u.opline_num = get_next_op_number(CG(active_op_array));
                zend_do_end_variable_parse(array, BP_VAR_W, 0 TSRMLS_CC);
                if (CG(active_op_array)->last > 0 && 
                                CG(active_op_array)->opcodes[CG(active_op_array)->last-1].opcode == ZEND_FETCH_OBJ_W) {
                        /* Only lock the container if we are fetching from a real container and not $this */
                        if (CG(active_op_array)->opcodes[CG(active_op_array)->last-1].op1.op_type == IS_VAR) {
                                CG(active_op_array)->opcodes[CG(active_op_array)->last-1].extended_value |= ZEND_FETCH_ADD_LOCK;                                push_container = 1; 
                        }    
                }    
        } else {
                is_variable = 0; 
                open_brackets_token->u.opline_num = get_next_op_number(CG(active_op_array));
        }    

        /* save the location of FE_RESET */
        foreach_token->u.opline_num = get_next_op_number(CG(active_op_array));

        opline = get_next_op(CG(active_op_array) TSRMLS_CC);

        /* Preform array reset */
        opline->opcode = ZEND_FE_RESET;
        opline->result.op_type = IS_VAR;
        opline->result.u.var = get_temporary_variable(CG(active_op_array));
        opline->op1 = *array;
        SET_UNUSED(opline->op2);
        opline->extended_value = is_variable ? ZEND_FE_RESET_VARIABLE : 0;

        dummy_opline.result = opline->result;
        if (push_container) {
                dummy_opline.op1 = CG(active_op_array)->opcodes[CG(active_op_array)->last-2].op1;
        } else {
                znode tmp;

                tmp.op_type = IS_UNUSED;
                dummy_opline.op1 = tmp;
        }
        zend_stack_push(&CG(foreach_copy_stack), (void *) &dummy_opline, sizeof(zend_op));

        /* save the location of FE_FETCH */
        as_token->u.opline_num = get_next_op_number(CG(active_op_array));

        opline = get_next_op(CG(active_op_array) TSRMLS_CC);
        opline->opcode = ZEND_FE_FETCH;
        opline->result.op_type = IS_VAR;
        opline->result.u.var = get_temporary_variable(CG(active_op_array));
        opline->op1 = dummy_opline.result;
        opline->extended_value = 0;
        SET_UNUSED(opline->op2);

        opline = get_next_op(CG(active_op_array) TSRMLS_CC);
        opline->opcode = ZEND_OP_DATA;
        SET_UNUSED(opline->op1);
        SET_UNUSED(opline->op2);
        SET_UNUSED(opline->result);
}

第9-13行 用来检测传递过来的array是 类的方法还是一个函数?
第15行 保存当前代码执行的位置,方便以后跳转
第16行 把变量内容取出来压入堆栈
第30行 保存 foreach位置
35-40 生成 数组 RESET OP代码
42-51 将 生成的op代码 压入到 foreach_copy_stack 以供execute执行
54行 保存 as的位置
生成 Opcode代码…
zend_do_foreach_begin 的作用就是生成解析变量 的OP,并记录 opline的位置

未完……

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