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

Link to this comparison view

rfc:foreach-non-scalar-keys [2013/01/29 06:54]
levim [Proposal and Patch]
rfc:foreach-non-scalar-keys [2013/03/12 17:53] (current)
nikic update with recent changes
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 ​=====
-Currently if you have a class that implements [[http://​php.net/​manual/​en/​class.iterator.php|Iterator]] in such a way that ''​key'' ​returns ​non-scalaryou get a warning when using it in foreach ​loop. The value of the key is cast to an integer if it is not a scalar.+The ''​Iterator::key'' ​function can currently return ​value of any typebut the handling code in ''​foreach''​ and several other places only allows integer and string keys to be usedThis limitation makes some use-cases unnecessarily complicated. From the SPL two examples are ''​MultipleIterator''​ and ''​SplObjectStorage''​. 
 +The ''​MutlipleIterator''​ allows you to traverse several ''​Iterator''​s at the same time. It's ''::​current''​ method returns an array of values and the ''::​key''​ method returns ​an array of keysBut due to the ''​foreach''​ key type limitation the keys can not be directly fetched:
 <code php> <code php>
-<?php+$it = new MultipleIterator;​ 
-class MapIterator implements Iterator { +// This is NOT possible 
-    ​protected ​$vals = []; +foreach ($it as $keys => $values) { 
-    protected ​$keys = []; +    ​// ... 
-    ​protected $index = 0;+}
-    function __construct(array $keys, array $values) { +// Instead you have to use this 
-        $this->keys = $keys; +foreach ​($it as $values) { 
-        $this->vals = $values+    $keys = $it->keys()
-    }+    ​ 
 +    // ... 
-    function rewind() { +''​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:
-        $this->​index = 0; +
-    }+
-    function valid() { +<code php> 
-        return ​$this->index < count($this->​keys&& $this->​index >=0; +// NOT possible 
-    }+foreach ​($objectStore as $key => $value{ 
 +    ​// ... 
-    function key() { +// Instead you have to use 
-        ​return ​$this->​keys[$this->​index]; +foreach ​($objectStore as $key) { 
-    }+    $value = $objectStore[$key]; 
 +    ​ 
 +    // ... 
-    function current() +These are just two examples from core classes, but it obviously also applies in many other cases (and now that we have generators, it will probably become an even larger issue).
-        return $this->​vals[$this->​index];​ +
-    }+
-    function next() { +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++; +
-    ​}+
-}+===== Suggested fix =====
-$requestA = new StdClass; +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.)
-$requestA->​startLine = 'GET / HTTP/1.1'+
-$requestA->​headers = ['Host' ​=> 'www.php.net'​];​+
-$responseA = new StdClass; +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:
-$responseA->​startLine = 'HTTP/1.1 200 OK'+
-$responseA->​headers = [];+
-$requestB = new StdClass+<code c> 
-$requestB->​startLine = '​GET ​/login HTTP/1.1'+// This entry: 
-$requestB->headers = ['​Host'​ => '​www.php.net'​];​+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)
-$responseB = new StdClass; +The handler has to write into the passed ​''​zval*'' ​using one of the ''​ZVAL_*''​ macros.
-$responseB->​startLine = 'HTTP/1.1 302 Found'+
-$responseB->​headers = ['Location' ​=> 'http://​www.php.net/​account/​login'];+
-$requests ​[$requestA, $requestB];​ +===== iterator_to_array() =====
-$responses ​[$responseA,​ $responseB];​+
-$mapIterator = new MapIterator( +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 assignmentsi.e. its behavior would be the same as the following PHP snippet:
-    $requests, +
-    $responses +
-foreach ($mapIterator ​as $request ​=> $response) { +<code php> 
-    ​var_dump($request)+function iterator_to_array($iter) { 
-    ​var_dump($response);+    ​foreach ($iter as $=> $v) { 
 +        $array[$k] = $v
 +    ​
 +    return ​$array;
 } }
 </​code>​ </​code>​
-Yields a warning ​''​Warning: ​Illegal type returned from MapIterator::​key() in ...''​ +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. 
-and ''​var_dump($request);'' ​returns ​''​int(0)''​.+ 
 +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''​): 
 +<code c> 
 +/* The refcount of value is incremented by the function itself */ 
 +ZEND_API ​int array_set_zval_key(HashTable *ht, zval *key, zval *value)
-===== 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
rfc/foreach-non-scalar-keys.1359438851.txt.gz · Last modified: 2013/01/29 06:54 by levim