foreach is a common use in PHP that is used as an array loop.Control statements .
Because of its convenience and ease of use, nature is also hidden in the back-end of a very complex concrete implementation method (transparent to the user)
Today, we'll come together to analyze and analyze how foreach implements the traversal of an array (object).
We know that PHP is a scripting language, that is to say, the user-written PHP code will eventually be interpreted by the PHP interpreter execution,
In particular, for PHP, all the user-written PHP code will be translated into PHP virtual machine ze virtual instructions (opcodes) to execute, regardless of the details, that is, we write any PHP script, will eventually be translated into a line of instructions, so according to the instructions, Executed by a function written by the corresponding C.
So what is foreach going to be translated into?
foreach ($arr as $key = + $val) { echo $key. ' = '. $val. "\ n";}
In the lexical analysis phase, FOREACH is identified as a token:t_foreach,
During the parsing phase, the rule will be:
Unticked_statement://Statement without bound ticks //have omitted | 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);} have omitted;
Careful analysis of this grammatical rule, we can find that for:
foreach ($arr as $key = + $val) {echo $key. ' = '. $val. " \ n ";}
will be analyzed as:
T_foreach ' (' variable t_as {zend_do_foreach_begin (' FOREACH ', ' (', $arr, ' as ', 1 tsrmls_cc);} Foreach_variable Foreach_optional_arg (t_double_arrow foreach_variable) ' {zend_do_foreach_cont (' foreach ', ' ( ' As ', $key, $val tsrmls_cc); }foreach_satement {zend_do_foreach_end (' foreach ', ' as ');}
Then, let's take a look at Foreach_statement:
It's actually a block of code that reflects our echo $key. ' = '. $val. " \ n ";
T_echo expr;
Obviously, the core of implementing foreach is the following 3 functions:
Zend_do_foreach_begin
Zend_do_foreach_cont
Zend_do_foreach_end
Among them, Zend_do_foreach_begin (code too long, direct write pseudo code) mainly did:
1. Record the current number of opline lines (for later jumps and records)
2. Reset the array (the internal pointer points to the first element)
3. Get temporary variables ($val)
4. Set the opcode fe_fetch of the Get variable, and the result is the temporary variable in the 3rd step
4. Record the number of rows that get the opcodes of a variable
And for Zend_do_foreach_cont:
1. According to the foreach_variable U. Ea.type to determine whether a reference
2. Adjust the Fe_fetch mode generated in zend_do_foreach_begin based on whether it is referenced
3. Initialize the loop based on the number of rows of opcodes that are recorded in the Zend_do_foreach_begin (mainly processing loops inside the loop: Do_begin_loop)
Last Zend_do_foreach_end:
1. Set ZEND_JMP opcodes According to the number of rows recorded in Zend_do_foreach_begin
2. Based on the current number of rows, set the next opline of the loop body to jump out of the loop
3. End loop (process loop inside: do_end_loop)
4. Clean up temporary variables
Of course, the Foreach_satement statement code is populated between Zend_do_foreach_cont and zend_do_foreach_end during the parsing phase.
In this way, the opcodes line of foreach is implemented.
For example, for the instance code we start with, the resulting opcodes is:
FileName:/home/huixinchen/foreach.phpfunction name: (null) Number of ops:17compiled VARs:!0 = $arr,! 1 = $key,!2 = $ Valline # OP FETCH ext return operands----------------------------------------------------------------- --------------2 0 send_val 1 1 send_val 2 Do_fcall 2 ' range ' 3 ASSIGN! 0, $3 4 fe_reset! 0,->14 5 Fe_fetch,->14 6 zend_op_data-7 ASSIGN !2, $8 ASSIGN! 1, ~ 4 9 CONCAT! 1, '-' CONCAT !2-CONCAT, '%0a ' ECHO 5 JMP->5 Switch_free 7 RETURN 1 16* Zend_handle_exception
We notice that Fe_fetch's op2 operand is 14, which is a opline after jmp, that is, after the last array element is fetched, Fe_fetch fails, it jumps to line 14th Opline, thus realizing the end of the loop.
While the operand of the opline of 15 rows is pointed to the Fe_fetch, which is the unconditional jump to the 5th row Opline, thus realizing the loop.
Appendix:
void Zend_do_foreach_begin (Znode *foreach_token, Znode *open_brackets_token, Znode *array, znode *as_token, int variable TSRMLS_DC) {Zend_op *opline; Zend_bool is_variable; Zend_bool Push_container = 0; Zend_op Dummy_opline; if (variable) {//is an anonymous array if (Zend_is_function_or_method_call (array)) {//Is the function return value is_variable = 0; } else {is_variable = 1; */* Use parentheses to record the number of opline lines for Fe_reset */open_brackets_token->u.opline_num = Get_next_op_number (CG (Active_op_array)); Zend_do_end_variable_parse (bp_var_w, 0 tsrmls_cc); Get array/object and zend_do_begin_variable_parse corresponding if (CG (Active_op_array)->last > 0 && CG (active_op_array)-&G T;OPCODES[CG (active_op_array)->last-1].opcode = = Zend_fetch_obj_w) {/* Only lock the container if we is fetching From a real container and not $this */if (CG (Active_op_array)->OPCODES[CG (Active_op_array)->last-1].op1.op_ty PE = = 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)); } foreach_token->u.opline_num = Get_next_op_number (CG (Active_op_array)); Record array Reset opline number Opline = Get_next_op (CG (Active_op_array) tsrmls_cc); Generates the RESET array opcode 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)); As_token-> u.opline_num = Get_next_op_number (CG (Active_op_array)); Record loop start point 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; Manipulated array opline->extended_value = 0; Set_unused (OPLINE->OP2); Opline = Get_next_op (CG (Active_op_array) tsrmls_cc); Opline->opcode = Zend_op_data; The secondary operand when using key, ignoring set_unused (OPLINE->OP1) when no key is included in the foreach; Set_unused (OPLINE->OP2); Set_unused (Opline->result);} void Zend_do_foreach_cont (Znode *foreach_token, const znode *open_brackets_token, const znode *as_token, Znode *value, Zn Ode *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]; Get Fe_fetch opline if (key->op_type! = is_unused) {znode *tmp;//interchange key and Val tmp = key; key = value; VAlue = tmp; Opline->extended_value |= Zend_fe_fetch_with_key; Indicates that you need to get both key and Val} if ((Key->op_type! = is_unused) && (Key->u.ea.type & Zend_parsed_reference_variab LE) {//key cannot be obtained by reference Zend_error (E_compile_error, "Key element cannot be a reference"); } if (Value->u.ea.type & zend_parsed_reference_variable) {//Gets the value Assign_by_ref = 1; if (! ( opline-1)->extended_value) {//based on the previous opline of Fe_fetch, which is to get the extended value of the array to determine if the array is an anonymous array zend_error (E_compile_error, "Can Not create references to elements of a temporary array expression "); } opline->extended_value |= Zend_fe_fetch_byref; Indicates that CG (active_op_array)->opcodes[foreach_token->u.opline_num].extended_value |= ZEND_FE_RESET_ by reference REFERENCE; Resets the original array} 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" to "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_erro R, "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);//Get Value (Reference) Zend_do_assign_ref (NULL, VA Lue, &value_node tsrmls_cc);//indicates that the type of value node is Is_var} else {zend_do_assign (&dummy, value, &value_node TSRMLS_CC); Get copy value 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));} void Zend_do_foreach_end (Znode *foreach_token, Znode *as_token tsrmls_dc) {zend_op *container_ptr; Zend_op *opline = Get_next_op (CG (Active_op_array) tsrmls_cc); Generate JMP opcode opline->opcode = zend_jmp; Opline->op1.u.opline_num = as_token->u.opline_num; Set up jmp to Fe_fetch Opline line 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)); Set Opline line CG (active_op_array)->opcodes[as_token->u.opline_num].op2.u.opline_num = Get_next_op_number for jumping out of loop (CG (Active_op_array));Ibid. do_end_loop (as_token->u.opline_num, 1 tsrmls_cc); Set Zend_stack_top (&CG (foreach_copy_stack), (void * *) &container_ptr) for loop nesting; Generate_free_foreach_copy (Container_ptr tsrmls_cc); Zend_stack_del_top (&CG (foreach_copy_stack)); DEC_BPC (CG (Active_op_array)); Set for PHP interactive mode}
It is also important to note that foreach is a value or a reference issue in use.
When iterating through an array in PHP, you can use either the for or Foreach,foreach syntax: foreach ($arr as $k = = $v). Iterate over the array, assign index to $k, and assign the value of the array to $v, so the assignment here is either a value or a reference. Let's look at the following example:
$arr = Array ( ' id ' = = 1, ' name ' = ' name1 '), array (' id ' = = 2, ' name ' = ' name2 '),); foreach ($arr As $obj) { $obj [' id '] = $obj [' id ']; $obj [' name '] = $obj [' name ']. '-modify ';} Print_r ($arr); The result of the output array ( [0] = = Array ( [id] = 1 [name] = + name1 ) [1] = = Array ( [id] = 2
[name] = name2 ))
Observations can be found that the $arr operation in the Foreach loop does not affect the $arr element, so the assignment here is a pass value instead of a reference. What if you need to modify the values of the elements in $arr? You can add a "&" symbol before the variable, for example:
foreach ($arr as & $obj) { $obj [' id '] = $obj [' id ']; $obj [' name '] = $obj [' name ']. '-modify ';}
Looking at another example, the array contains an object,
$arr = Array ( (object) (Array (' id ' = = 1, ' name ' = ' name1 ')), ( object) (Array (' id ' = = 2, ' name ' = ' = ') ' Name 2 ')), foreach ($arr as $obj) { $obj->name = $obj->name. '-modify '; }print_r ($arr); The result of the output array ( [0] = = StdClass Object ( [id] = 1 [name] = name1-modify ) [1] = = StdClass Object ( [id] = 2 [name] = name2-modify ))
At this point you can see that the object objects in the original array have been modified, so the assignment here is a reference instead of a pass value.
Combined with the above, the conclusion is: if the array is stored in the ordinary type of elements is the use of the value of the way, the object type element is used in the way of the address.