rfc:foreach-non-scalar-keys

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
rfc:foreach-non-scalar-keys [2013/02/01 23:36] – [Introduction] Expanded introduction. levimrfc:foreach-non-scalar-keys [2017/09/22 13:28] (current) – external edit 127.0.0.1
Line 1: Line 1:
-====== Allow Non-Scalar Keys ======+====== Allow non-scalar keys in ''foreach'' ======
   * version 1.0   * version 1.0
   * Date: 2013-01-28   * Date: 2013-01-28
-  * Author: Levi Morrison <levim@php.net> +  * Authors: Levi Morrison <levim@php.net>, Nikita Popov <nikic@php.net> 
-  * Status: Under Discussion+  * Status: Implemented in PHP 5.5 ([[https://github.com/php/php-src/commit/fcc6611de9054327441786e52444b5f8eecdd525|commit]])
  
-===== Introduction =====+===== Current situation =====
  
-Implementing the [[http://php.net/manual/en/class.iterator.php|Iterator]] interface allows class to define the necessary things to use that class as an iterator The biggest use-case for implementing the Iterator interface is that it interacts with a foreach loop in a custom wayThere are currently limitations on what you can use as the ''key'' part of the iterator:+The ''Iterator::key'' function can currently return value of any type, but the handling code in ''foreach'' and several other places only allows integer and string keys to be usedThis limitation makes some use-cases unnecessarily complicatedFrom the SPL two examples are ''MultipleIterator'' and ''SplObjectStorage''
 + 
 +The ''MutlipleIterator'' allows you to traverse several ''Iterator''s at the same time. It'''::current'' method returns an array of values and the ''::key'' method returns an array of keys. But due to the ''foreach'' key type limitation the keys can not be directly fetched:
  
 <code php> <code php>
-<?php+$it = new MultipleIterator; 
 +$it->attachIterator($it1); 
 +$it->attachIterator($it2);
  
-// $key cannot be an object or array +// This is NOT possible 
-foreach($object as $key => $value) {+foreach ($it as $keys => $values) { 
 +    // ... 
 +}
  
 +// Instead you have to use this
 +foreach ($it as $values) {
 +    $keys = $it->keys();
 +    
 +    // ...
 } }
 </code> </code>
  
-If you implement the iterator interface and return an object or array for the key, you get this error''Warning: Illegal type returned from MapIterator::key() in ...''+''SplObjectStorage'' is a map/set implementation for object keys. Here the issue is circumvented by returning the keys as values and requiring a manual lookup on the values:
  
-===== Example Iterator ===== 
 <code php> <code php>
-<?php+// NOT possible 
 +foreach ($objectStore as $key => $value) { 
 +    // ... 
 +}
  
-class MapIterator implements Iterator +// Instead you have to use 
-    protected $vals = []; +foreach ($objectStore as $key) 
-    protected $keys = []; +    $value $objectStore[$key]; 
-    protected $index = 0;+     
 +    // ... 
 +
 +</code>
  
-    function __construct(array $keysarray $values+These are just two examples from core classes, but it obviously also applies in many other cases (and now that we have generatorsit will probably become an even larger issue).
-        $this->keys = $keys; +
-        $this->vals = $values; +
-    }+
  
-    function rewind() { +Another key issue is that you can't really work around this generically. If you want to write code that is also compatible with ''Iterator''s that return array/object keys, you can no longer use the ''foreach ($it as $k => $v)'' syntax. You are forced to use ''%%foreach ($it as $v) { $k = $it->key()... }%%'', but this will obviously only with with ''Iterator''s and not with aggregates, ''Traversable''s or normal arrays. In order to properly support all use cases you'd have to wrap everything in iterators (i.e. make extensive use of ''IteratorIterator'' and ''ArrayIterator''), which obviously is an option, but cumbersome to a degree that nobody does it. What this means is that iterators like ''MultipleIterator'' are to a large part excluded from use in iterator chaining/pipelines (which is probably the most important thing about using iterators).
-        $this->index = 0; +
-    }+
  
-    function valid() { +===== Suggested fix =====
-        return $this->index < count($this->keys) && $this->index >=0; +
-    }+
  
-    function key() +This RFC proposes to lift the restriction and allow values of arbitrary types to be used as keys (in particularly allowing also arrays and objects) in iterators. (Note: This proposal does not suggest allowing those key types in arrays. This is only about ''Iterator''s.)
-        return $this->keys[$this->index]; +
-    }+
  
-    function current() { +In order to remove this restriction the internal [[http://lxr.php.net/xref/PHP_TRUNK/Zend/zend_iterators.h#31|''zend_object_iterator_funcs'']API has to be changed:
-        return $this->vals[$this->index]+
-    }+
  
-    function next() { +<code c> 
-        $this->index+++// This entry: 
-    }+int (*get_current_key)(zend_object_iterator *iter, char **str_key, uint *str_key_len, ulong *int_key TSRMLS_DC); 
 +// Is replaced with this entry: 
 +void (*get_current_key)(zend_object_iterator *iter, zval *key TSRMLS_DC)
 +</code>
  
-}+The handler has to write into the passed ''zval*'' using one of the ''ZVAL_*'' macros.
  
-$requestA new StdClass; +===== iterator_to_array() =====
-$requestA->startLine 'GET / HTTP/1.1'; +
-$requestA->headers ['Host' => 'www.php.net'];+
  
-$responseA = new StdClass; +When using non-string/int keys ''iterator_to_array'' with the ''$preserve_keys'' option will behave in the same way as PHP would when it does normal array key assignments, i.e. its behavior would be the same as the following PHP snippet:
-$responseA->startLine = 'HTTP/1.1 200 OK'+
-$responseA->headers = [];+
  
-$requestB = new StdClass; +<code php> 
-$requestB->startLine 'GET /login HTTP/1.1'; +function iterator_to_array($iter) { 
-$requestB->headers = ['Host' => 'www.php.net'];+    foreach ($iter as $k => $v) { 
 +        $array[$k= $v; 
 +    } 
 +    return $array; 
 +
 +</code>
  
-$responseB = new StdClass; +For array and object keys this would give an ''Illegal offset type'' warningFor ''NULL'' the ''%%""%%'' key is used, doubles are truncated to the integral part, resources use their resource ID and issue a warning, booleans are cast to integers.
-$responseB->startLine = 'HTTP/1.1 302 Found'+
-$responseB->headers = ['Location=> 'http://www.php.net/account/login'];+
  
-$requests = [$requestA, $requestB]; +In order to support this a new function is added in ''Zend/zend_API.h'' (which more or less reimplements the internal inline function ''zend_fetch_dimension_address_inner''):
-$responses = [$responseA, $responseB];+
  
-$mapIterator = new MapIterator( +<code c
-    $requests, +/* The refcount of value is incremented by the function itself */ 
-    $responses +ZEND_API int array_set_zval_key(HashTable *ht, zval *key, zval *value);
-); +
- +
-foreach ($mapIterator as $request =$response) { +
-    var_dump($request); +
-    var_dump($response); +
-}+
 </code> </code>
  
-===== Proposal and Patch =====+===== Patch =====
  
-I propose that we lift the restriction that forces a scalar value. Instead we simply assign the key variable to whatever was returned from the iterator. The warning will also be removed. This also opens the possibility to use arrays as keys returned from an iterator, and as such I feel we should also add support for `list` in the keys.+The patch for this change can be found here: https://github.com/php/php-src/commit/fcc6611de9054327441786e52444b5f8eecdd525
  
-There is no patch at this time. I know Ekneuss was working on something but hasn't had time to finish. I also don't know how closely his patch matches up with this proposal, either.+The change itself is rather small, but there are quite a few extensions that require minor adjustments to use the new API.
  
-===== Changelog =====+===== Vote =====
  
-version 1.0: +Voting ends on March 6th. A 50% + majority is required. This RFC targets PHP 5.5.
-  * proposed+
  
 +<doodle title="Remove type-restrictions on foreach keys?" auth="nikic" voteType="single" closed="true">
 +   * Yes
 +   * No
 +</doodle>
rfc/foreach-non-scalar-keys.1359761791.txt.gz · Last modified: 2017/09/22 13:28 (external edit)