In-depth analysis of the use of a foreach statement in PHP to control array loops _php tips

Source: Internet
Author: User
Tags anonymous php script

foreach is a very common control statement used in PHP as an array loop.
Because of its convenience and ease of use, nature is also hidden in the back end of a very complex concrete implementation (transparent to the user)
Today, we'll come together to analyze how foreach implements the traversal of arrays (objects).
We know that PHP is a scripting language, which means that the PHP code that the user writes will eventually be interpreted by the PHP interpreter,
Special, for PHP, all the user-written PHP code will be translated into PHP virtual machine ze virtual instruction (opcodes) to execute, regardless of the details, that is, we write any PHP script, will eventually be translated into a line of instructions, according to instructions, Executed by a function written by the corresponding C.

So what is a foreach to be translated into?

foreach ($arr as $key => $val) {
   echo $key. ' => '. $val. "\ n";
}

In the lexical analysis phase, FOREACH is recognized as a token:t_foreach,
In the parsing phase, the rules are:

 Unticked_statement:///There are no TICKS statements
   //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, & $, &$4, &$6, &$7 tsrmls_cc); }
    foreach_statement {zend_do_foreach_end (&$1, &$4 tsrmls_cc);}
   have omitted
;

With a careful analysis of this grammatical rule, we can see 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:

    1. Zend_do_foreach_begin
    2. Zend_do_foreach_cont
    3. 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 rows (for subsequent jumps to record)
2. Reset the array (speak the internal pointer to the first element)
3. Get temporary variables ($val)
4. Set the opcode fe_fetch that gets the variable, and the temporary variable that saves 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 Foreach_variable's U. Ea.type to determine if the reference
2. Adjust the Fe_fetch method generated in Zend_do_foreach_begin based on whether or not to reference
3. Initialize the loop based on the number of rows of the opcodes that are recorded in the Zend_do_foreach_begin (mainly dealing with 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. According to the current number of lines, set the loop body under a opline to jump out of the loop
3. End Loop (Process loop: 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 realized.
For example, for example code that starts with us, the resulting opcodes is:

FileName:/home/huixinchen/foreach.php function name: (NULL) Number of ops:17 compiled VARs:!0 = $arr,! 1 = $key,!2 = $val Line # OP fetch ext. operands----------------------------------------------------------- --------------------2 0 send_val 1 1 send_val 2 do_fcal   L 2 ' range ' 3 ASSIGN! 0, $3 4 fe_reset $                          !0,->14 5 fe_fetch $ $,->14 6 zend_op_data ~5 7 ASSIGN !2, $8 ASSIGN! 1, ~5 4 9 CONCAT ~7! 1, '                           -' CONCAT ~8 ~7,!2 CONCAT ~9-~8, '%0a ' ECHO                         ~9 5 JMP->5 switch_free $7 return  1 16* zend_handle_exception
 

We notice that the op2 operand of the Fe_fetch is 14, which is the jmp opline, which means that after the last array element is fetched, the fe_fetch of the 14th row is skipped, and the end of the loop is achieved.
and the 15 line opline OP1 operand is pointing to the fe_fetch, that is, unconditional jump to the 5th line 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 of 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); Gets the array/object and zend_do_begin_variable_parse corresponding 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 FETCH ing from a real container and not $this */if (CG (Active_op_array)->OPCODES[CG (Active_op_array)->last-1].op1.o P_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)); } 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);
  Generate 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));
  Recording cycle 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 subordinate operand when key is used, ignoring set_unused (OPLINE->OP1) when the foreach does not contain a key;
  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, Z
  Node *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]; Gets the Fe_fetch opline if (key->op_type!= is_unused) {znode *tmp;//exchange key and Val tmp = key;
    key = value;
 
    value = tmp; Opline->extended_value |= Zend_fe_fetch_with_key; Indicates that you need to obtain both key and Val} if ((Key->op_type!= is_unused) && (Key->u.ea.type & Zend_parsed_reference_var
  iable)) {//key cannot get zend_error by reference (E_compile_error, "Key element cannot be a reference");
    The IF (Value->u.ea.type & zend_parsed_reference_variable) {//is a reference to get the value assign_by_ref = 1; if (!) ( opline-1)->extended_value) {//According to Fe_fetch's previous opline that is, to get an array of extended values to determine whether an array is an anonymous array zend_error (E_compile_error, "C
    Annot create references to elements of a temporary array expression "); } opline->extended_value |= Zend_fe_fetch_byref; Indicates the CG (active_op_array)->opcodes[foreach_token->u.opline_num].extended_value |= ZEND_FE_RESET_ by reference REFERENCE;
    Reset 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 ' into ' read context ' */fetch->extended_value = 0;
      /* Reset zend_fe_reset_variable * (Fetch!= end) {--fetch; if (Fetch->opcode = = Zend_fetch_dim_w && Fetch->op2.op_type = is_unused) {zend_error (e_compile_err
      Or, "cannot use [] for reading"); } Fetch->opcode-= 3; /* Fetch_w-> fetch_r/* Prevent double Switch_free/zend_stack_top (&CG), (
    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, Value, &value_node tsrmls_cc)//indicates that the type of value node is Is_var} else {zend_do_assign (&dummy, value, &value_n Ode tsrmls_cc); Gets the copy value Zend_do_free (&amP;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 JMP to Fe_fetch opline row 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 the Opline line to jump out of the Loop CG (active_op_array)->opcodes[as_token->u.opline_num].op2.u.opline_num = Get_next_op_number (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

Also note whether foreach is a value or a reference issue in use.
A for or Foreach,foreach syntax can be used when traversing an array in PHP: foreach ($arr as $k => $v). Iterate over the array, assign the index to $k, and assign the value of the array to $v, so the assignment here is 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); Result array of output
(
  [0] => Array (
    [id] => 1
    [name] => name1
  )
  [1] => Array (
    [ ID] => 2
    [name] => name2
  )
)

Observations can be found that the $arr operation does not affect the $arr element in the Foreach loop, so the assignment here is to pass a value instead of a reference. What if you need to modify the value of the elements in the $arr? You can add a "&" symbol in front of the variable, for example:

foreach ($arr as & $obj) {
  $obj [' id '] = $obj [' id '];
  $obj [' name '] = $obj [' name ']. '-modify ';
}

To see another example, the array contains 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); Result Array of

output
(
  [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 rather than a pass value

To synthesize the above, the conclusion is: If the array contains the ordinary type of element is to use the value of the way, the object type of elements used in the way of the address.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.