rfc:php7_foreach
Differences
This shows you the differences between two versions of the page.
Next revision | Previous revision | ||
rfc:php7_foreach [2015/01/29 16:16] – created dmitry | rfc:php7_foreach [2017/09/22 13:28] (current) – external edit 127.0.0.1 | ||
---|---|---|---|
Line 3: | Line 3: | ||
* Date: 2015-01-29 | * Date: 2015-01-29 | ||
* Author: Dmitry Stogov, dmitry@zend.com | * Author: Dmitry Stogov, dmitry@zend.com | ||
- | * Status: | + | * Status: |
* First Published at: http:// | * First Published at: http:// | ||
Line 14: | Line 14: | ||
Result of current() is undefined | Result of current() is undefined | ||
< | < | ||
- | $ php -r '$a = [1,2,3]; foreach($a as $v) { echo $v . " - " . current($a) . " | + | $ php -r '$a = [1,2,3]; foreach($a as $v) {echo $v . " - " . current($a) . " |
1 - 2 | 1 - 2 | ||
2 - 2 | 2 - 2 | ||
3 - 2 | 3 - 2 | ||
- | $ php -r '$a = [1,2,3]; $b = $a; foreach($a as $v) { echo $v . " - " . current($a) . " | + | $ php -r '$a = [1,2,3]; $b = $a; foreach($a as $v) {echo $v . " - " . current($a) . " |
1 - 1 | 1 - 1 | ||
2 - 1 | 2 - 1 | ||
Line 27: | Line 27: | ||
unset() may exclude an element from iteration or not | unset() may exclude an element from iteration or not | ||
< | < | ||
- | $ php -r '$a = [1,2,3]; foreach($a as $v) { echo " | + | $ php -r '$a = [1,2,3]; foreach($a as $v) {echo " |
1 | 1 | ||
2 | 2 | ||
3 | 3 | ||
- | $ php -r '$a = [1,2,3]; $b = &$a; foreach($a as $v) { echo " | + | $ php -r '$a = [1,2,3]; $b = &$a; foreach($a as $v) {echo " |
1 | 1 | ||
3 | 3 | ||
Line 55: | Line 55: | ||
Iteration over **iterator objects** (by value and by reference) is kept the same. | Iteration over **iterator objects** (by value and by reference) is kept the same. | ||
- | ==== foreach() by value over array ==== | + | ==== foreach() by value over arrays |
**foreach** by value over array will never use or modify internal array pointer. It also won't duplicate array, it'll lock it instead (incrementing reference counter). This will lead to copy-on-write on attempt of array modification inside the loop. As result, we will always iterate over elements of array originally passed to **foreach**, | **foreach** by value over array will never use or modify internal array pointer. It also won't duplicate array, it'll lock it instead (incrementing reference counter). This will lead to copy-on-write on attempt of array modification inside the loop. As result, we will always iterate over elements of array originally passed to **foreach**, | ||
Line 61: | Line 61: | ||
The value of internal pointer is unaffected | The value of internal pointer is unaffected | ||
< | < | ||
- | $ sapi/cli/php -r '$a = [1,2,3]; foreach($a as $v) { echo $v . " - " . current($a) . " | + | $ php -r '$a = [1,2,3]; foreach($a as $v) {echo $v . " - " . current($a) . " |
1 - 1 | 1 - 1 | ||
2 - 1 | 2 - 1 | ||
Line 69: | Line 69: | ||
Modifications of the original array are ignored | Modifications of the original array are ignored | ||
< | < | ||
- | $ sapi/cli/php -r '$a = [1,2,3]; $b = &$a; foreach($a as $v) { echo " | + | $ php -r '$a = [1,2,3]; $b = &$a; foreach($a as $v) {echo " |
1 | 1 | ||
2 | 2 | ||
Line 75: | Line 75: | ||
</ | </ | ||
- | ==== foreach() by value over plain object | + | ==== foreach() by reference |
- | TODO | + | In most cases it repeats the PHP5 behavior. |
- | ==== foreach() by reference over array ==== | + | **foreach** by reference over array modifys internal pointer on each iteration. Exactly as it was implemented in PHP5, it sets the internal pointer to the following element. |
- | TODO | + | < |
+ | $ php -r '$a = [1,2,3]; foreach($a as &$v) {echo $v . " - " . current($a) . " | ||
+ | 1 - 2 | ||
+ | 2 - 3 | ||
+ | 3 - | ||
+ | </ | ||
+ | |||
+ | Modification of internal array pointer through next() and family doesn' | ||
+ | |||
+ | < | ||
+ | $ php -r '$a = [1,2,3,4]; foreach($a as &$v) {echo "$v - "; next($a); var_dump(current($a)); | ||
+ | 1 - int(3) | ||
+ | 2 - int(4) | ||
+ | 3 - bool(false) | ||
+ | 4 - bool(false) | ||
+ | </ | ||
+ | |||
+ | Deletion of the next element referred by **foreach** pointer leads to skipping it (in the same way as as in PHP5). | ||
+ | |||
+ | < | ||
+ | $ sapi/ | ||
+ | 1 | ||
+ | 3 | ||
+ | </ | ||
+ | |||
+ | Adding new elements after the current **foreach** pointer adds them to iteration (the same as in PHP5) | ||
+ | |||
+ | < | ||
+ | $ php -r '$a = [1,2]; foreach($a as &$v) {echo " | ||
+ | 1 | ||
+ | 2 | ||
+ | 3 | ||
+ | </ | ||
+ | |||
+ | Adding new elements after the current **foreach** pointer when we are already at the end adds them to iteration as well (**this didn't work in PHP5**) | ||
+ | |||
+ | < | ||
+ | $ php -r '$a = [1]; foreach($a as &$v) {echo " | ||
+ | 1 | ||
+ | 2 | ||
+ | </ | ||
+ | |||
+ | Replacing iterated array with another array lead to continue iteration over the new array starting from its internal pointer (the same as in PHP5) | ||
+ | |||
+ | < | ||
+ | $ php -r '$a = [1,2]; $b = [3,4]; next($b); foreach($a as &$v) {echo " | ||
+ | 1 | ||
+ | 4 | ||
+ | </ | ||
+ | |||
+ | In case we have several **forech** by reference statements over the same array each of them works according to the rules above, independently from the others. (**It didn't work in PHP5**) | ||
+ | |||
+ | < | ||
+ | <?php | ||
+ | $a = [0, 1, 2, 3]; | ||
+ | foreach ($a as &$x) { | ||
+ | foreach ($a as &$y) { | ||
+ | echo "$x - $y\n"; | ||
+ | if ($x == 0 && $y == 1) { | ||
+ | unset($a[1]); | ||
+ | unset($a[2]); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | < | ||
+ | $ php test.php | ||
+ | 0 - 0 | ||
+ | 0 - 1 | ||
+ | 0 - 3 | ||
+ | 3 - 0 | ||
+ | 3 - 3 | ||
+ | </ | ||
+ | |||
+ | Modification of array, iterated through foreach by reference, using internal functions like array_pop(), | ||
+ | |||
+ | < | ||
+ | $ php -r ' | ||
+ | 1 | ||
+ | 2 | ||
+ | </ | ||
+ | |||
+ | ==== foreach() by value over plain objects ==== | ||
+ | |||
+ | It beahves in the same way as **foreach by reference over array**, but using object value instead of reference. As result the object can be modified, but can't be replaced. | ||
- | ==== foreach() by reference over plain object | + | ==== foreach() by reference over plain objects |
- | TODO | + | It beahves in the same way as **foreach by reference over array**. |
==== Implementation Details ==== | ==== Implementation Details ==== | ||
- | TODO | + | The existing FE_RESET/ |
+ | |||
+ | Iteration by value over array doesn' | ||
+ | |||
+ | Iteration by reference or by value over plain object implemented using special **HashTableIterator** structures. | ||
+ | |||
+ | < | ||
+ | typedef struct _HashTableIterator { | ||
+ | HashTable | ||
+ | HashPosition | ||
+ | } HashTableIterator; | ||
+ | </ | ||
+ | |||
+ | On entrance into **foreach** loop FE_RESET_R/ | ||
+ | |||
+ | Iterators are actually allocated in a buffer - EG(ht_iterators), | ||
+ | |||
+ | < | ||
+ | struct _zend_executor_globals { | ||
+ | ... | ||
+ | uint32_t | ||
+ | uint32_t | ||
+ | HashTableIterator *ht_iterators; | ||
+ | HashTableIterator | ||
+ | ... | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Creation, deletion and accessing iterators position is implemented through special API. | ||
+ | |||
+ | < | ||
+ | ZEND_API uint32_t | ||
+ | ZEND_API HashPosition zend_hash_iterator_pos(uint32_t idx, HashTable *ht); | ||
+ | ZEND_API void | ||
+ | </ | ||
+ | |||
+ | Indirect modification of iterators positions implemented through zend_hash_iterators_update(). It's called when HashTable modification may affects iterator position. For example when element referred by iterator is inserted, or when iterator is set at the end of the array and new element is inserted. | ||
+ | |||
+ | < | ||
+ | ZEND_API void | ||
+ | </ | ||
+ | |||
+ | Foe more details see zend_hash_iterators_*() functions implementation in zend_hash.c | ||
===== Backward Incompatible Changes ===== | ===== Backward Incompatible Changes ===== | ||
- | Some rare cases where the **foreach** statement behavior was undefined may be changed. The implementation changes few such PHPT tests. | + | Some rare cases where the **foreach** statement behavior was undefined may be changed. The implementation changes few such PHPT tests. |
+ | |||
+ | * Zend/ | ||
+ | * Zend/ | ||
+ | * tests/ | ||
+ | * tests/ | ||
+ | * tests/ | ||
+ | * tests/ | ||
+ | * tests/ | ||
+ | * tests/ | ||
+ | * tests/ | ||
+ | * tests/ | ||
+ | |||
+ | ===== Additional Behavoir Change ===== | ||
+ | |||
+ | With new implementation it's quite easy to stop using internal array/ | ||
+ | It means that reset/ | ||
+ | This would change the output of few examples above. | ||
+ | |||
+ | **foreach** (even foreach by reference) won't affect internal array pointer | ||
+ | |||
+ | < | ||
+ | $ php -r '$a = [1,2,3]; foreach($a as &$v) {echo $v . " - " . current($a) . " | ||
+ | 1 - 1 | ||
+ | 2 - 1 | ||
+ | 3 - 1 | ||
+ | </ | ||
+ | |||
+ | Modification of internal array pointer through next() and family doesn' | ||
+ | |||
+ | < | ||
+ | $ php -r '$a = [1,2,3,4]; foreach($a as &$v) {echo "$v - "; next($a); var_dump(current($a)); | ||
+ | 1 - int(2) | ||
+ | 2 - int(3) | ||
+ | 3 - int(4) | ||
+ | 4 - bool(false) | ||
+ | </ | ||
===== Proposed PHP Version(s) ===== | ===== Proposed PHP Version(s) ===== | ||
Line 106: | Line 268: | ||
===== Open Issues ===== | ===== Open Issues ===== | ||
- | * complete RFC | + | * implementation optimization |
- | * complete | + | |
- | * performance | + | |
===== Future Scope ===== | ===== Future Scope ===== | ||
Line 115: | Line 275: | ||
===== Proposed Voting Choices ===== | ===== Proposed Voting Choices ===== | ||
The vote is a straight Yes/No vote, that requires a 2/3 majority | The vote is a straight Yes/No vote, that requires a 2/3 majority | ||
+ | |||
+ | <doodle title=" | ||
+ | * Yes | ||
+ | * No | ||
+ | </ | ||
+ | |||
+ | The second (Yes/No 50%+1) question is - if we should stop modifying internal array/ | ||
+ | |||
+ | <doodle title=" | ||
+ | * Yes | ||
+ | * No | ||
+ | </ | ||
+ | |||
+ | The vote will end on February 12. | ||
===== Patches and Tests ===== | ===== Patches and Tests ===== | ||
Line 120: | Line 294: | ||
Pull request for master branch: [[https:// | Pull request for master branch: [[https:// | ||
- | It doesn' | + | The implementation of additional idea is trivial [[https:// |
===== Implementation ===== | ===== Implementation ===== | ||
- | After the project is implemented, this section should contain | + | The RFC implemented |
- | - the version(s) it was merged to | + | |
- | - a link to the git commit(s) | + | [[http:// |
- | - a link to the PHP manual entry for the feature | + | |
- | ===== References ===== | + | [[http:// |
- | Links to external references, discussions or RFCs | + | |
rfc/php7_foreach.1422548207.txt.gz · Last modified: 2017/09/22 13:28 (external edit)