How does PHP 'foreach' actually work?

前端 未结 7 1418
温柔的废话
温柔的废话 2020-11-21 06:12

Let me prefix this by saying that I know what foreach is, does and how to use it. This question concerns how it works under the bonnet, and I don\'t want any an

7条回答
  •  旧时难觅i
    2020-11-21 06:29

    Great question, because many developers, even experienced ones, are confused by the way PHP handles arrays in foreach loops. In the standard foreach loop, PHP makes a copy of the array that is used in the loop. The copy is discarded immediately after the loop finishes. This is transparent in the operation of a simple foreach loop. For example:

    $set = array("apple", "banana", "coconut");
    foreach ( $set AS $item ) {
        echo "{$item}\n";
    }
    

    This outputs:

    apple
    banana
    coconut
    

    So the copy is created but the developer doesn't notice, because the original array isn’t referenced within the loop or after the loop finishes. However, when you attempt to modify the items in a loop, you find that they are unmodified when you finish:

    $set = array("apple", "banana", "coconut");
    foreach ( $set AS $item ) {
        $item = strrev ($item);
    }
    
    print_r($set);
    

    This outputs:

    Array
    (
        [0] => apple
        [1] => banana
        [2] => coconut
    )
    

    Any changes from the original can't be notices, actually there are no changes from the original, even though you clearly assigned a value to $item. This is because you are operating on $item as it appears in the copy of $set being worked on. You can override this by grabbing $item by reference, like so:

    $set = array("apple", "banana", "coconut");
    foreach ( $set AS &$item ) {
        $item = strrev($item);
    }
    print_r($set);
    

    This outputs:

    Array
    (
        [0] => elppa
        [1] => ananab
        [2] => tunococ
    )
    

    So it is evident and observable, when $item is operated on by-reference, the changes made to $item are made to the members of the original $set. Using $item by reference also prevents PHP from creating the array copy. To test this, first we’ll show a quick script demonstrating the copy:

    $set = array("apple", "banana", "coconut");
    foreach ( $set AS $item ) {
        $set[] = ucfirst($item);
    }
    print_r($set);
    

    This outputs:

    Array
    (
        [0] => apple
        [1] => banana
        [2] => coconut
        [3] => Apple
        [4] => Banana
        [5] => Coconut
    )
    

    As it is shown in the example, PHP copied $set and used it to loop over, but when $set was used inside the loop, PHP added the variables to the original array, not the copied array. Basically, PHP is only using the copied array for the execution of the loop and the assignment of $item. Because of this, the loop above only executes 3 times, and each time it appends another value to the end of the original $set, leaving the original $set with 6 elements, but never entering an infinite loop.

    However, what if we had used $item by reference, as I mentioned before? A single character added to the above test:

    $set = array("apple", "banana", "coconut");
    foreach ( $set AS &$item ) {
        $set[] = ucfirst($item);
    }
    print_r($set);
    

    Results in an infinite loop. Note this actually is an infinite loop, you’ll have to either kill the script yourself or wait for your OS to run out of memory. I added the following line to my script so PHP would run out of memory very quickly, I suggest you do the same if you’re going to be running these infinite loop tests:

    ini_set("memory_limit","1M");
    

    So in this previous example with the infinite loop, we see the reason why PHP was written to create a copy of the array to loop over. When a copy is created and used only by the structure of the loop construct itself, the array stays static throughout the execution of the loop, so you’ll never run into issues.

提交回复
热议问题