In PHP, what does iterator_to_array() do with keys by default, and when does it silently lose elements?
answer
- keys are kept unless told otherwise
- $preserve_keys defaults to true
- duplicate key: last value wins
- AppendIterator restarts keys at 0
- accepts plain arrays since 8.2
basics
~20 siterator_to_array() copies every element into an array and, because $preserve_keys defaults to true, uses the iterator's keys. When two elements share a key the later one overwrites the earlier; pass false to get a list of all values.
solid answer
~40 s`iterator_to_array(iterable $iterator, bool $preserve_keys = true): array` runs the iterator to the end and stores each `current()` under its `key()`. With the default `true`, repeated keys collide and only the last value survives, with no warning. The classic case is an `AppendIterator` over two `ArrayIterator`s: both restart at key `0`, so the result holds only the second list. Passing `false` ignores keys and returns a list of every value in order. Two more things matter: it **drives** the iterator, so a structure whose iteration removes elements, such as `SplPriorityQueue`, is empty afterwards; and it **materialises** everything at once, trading a lazy, constant-memory loop for a full array in memory. Since PHP 8.2 it also accepts a plain array.
code
php · 10 lines<?php
declare(strict_types=1);
$all = new AppendIterator();
$all->append(new ArrayIterator(['low', 'normal']));
$all->append(new ArrayIterator(['high', 'urgent']));
var_dump(iterator_count($all)); // int(4)
var_dump(iterator_to_array($all)); // [0 => 'high', 1 => 'urgent']
var_dump(iterator_to_array($all, false)); // ['low', 'normal', 'high', 'urgent']go deeper
Remember that the second argument, $preserve_keys, defaults to true, and that passing false gives a plain list of every value.
Explain how repeated keys overwrite earlier values, name AppendIterator as the classic source, and note that the call drives the iterator to its end.
Spot conversions that defeat a lazy iterator's memory profile or empty a destructive SPL structure, and keep large sources in a foreach instead.
Decide where a codebase returns arrays versus iterables, weighing caller convenience against memory and the hidden cost of materialising streams.
## The signature and the default In PHP 8.5 the function is declared as: ```php function iterator_to_array(iterable $iterator, bool $preserve_keys = true): array ``` It rewinds the iterator, then for each element reads `current()` and, when `$preserve_keys` is `true`, `key()`, and writes `$result[$key] = $value`. That default is the source of most surprises: an **array cannot hold two entries with the same key**, so a later element with a repeated key simply overwrites the earlier one. There is no warning and no error; the result is just shorter than the number of elements the iterator produced. With `$preserve_keys = false`, keys are ignored and every value is appended, so the result is a list numbered from `0` containing all elements in iteration order. ## Where duplicate keys come from Iterators are not arrays, so nothing stops them from yielding the same key twice: - **`AppendIterator`** chains several iterators; each inner `ArrayIterator` over a list starts again at key `0`. - A **hand-written `Iterator`** whose `key()` returns something repeating, such as a category name or a date. - **Iterators that walk nested data**, where different branches reuse the same local keys. In every one of these cases the default call quietly keeps only the last value per key. `iterator_count()` on the same iterator reports the full number, which is a quick way to spot the discrepancy. ## Key conversion rules When keys are preserved they are converted as array keys always are: | Key produced by the iterator | Stored as | |---|---| | `int`, `string` | unchanged | | `float` | truncated to `int` | | `bool` | `0` or `1` | | `null` | `""` (empty string) | | `array` or `object` | a warning is raised | This is another path to collisions: `1.2` and `1.7` both become `1`. ## It drives and it materialises 1. **It consumes the iteration.** The function calls `rewind()` and then walks to the end. For most iterators that is harmless, but some SPL structures remove elements as they are iterated: `SplPriorityQueue` and the heaps extract on each step, and an `SplQueue` switched to delete mode dequeues. After `iterator_to_array()` such a structure is empty. 2. **It builds the whole array.** A `foreach` over an iterator holds one element at a time; `iterator_to_array()` holds all of them. For an iterator over a large file, a database cursor or a long computed sequence, converting to an array throws away the reason the iterator existed and can exhaust `memory_limit`. 3. **It needs a rewindable source.** An iterator that cannot restart can be converted once; a second call either returns nothing or throws, depending on the class. The rule of thumb: loop over the iterator when you only need each element once, and convert to an array only when you truly need random access, sorting with the `sort` family, or a value to return from an API that promises an array. ## Version notes - **PHP 8.2** widened the first parameter to `iterable`, so `iterator_to_array($maybeArray)` works whether the caller passed an array or a `Traversable`. With an array and `$preserve_keys = false` it behaves like `array_values()`. - `iterator_count()` got the same widening and also drives the iterator to the end. ## A hand-written iterator with repeating keys Duplicate keys are easy to create by accident in your own classes. Suppose an `Iterator` over support tickets returns the ticket's **category** from `key()`, so that `foreach ($tickets as $category => $ticket)` reads naturally. Every category appears many times, and `iterator_to_array($tickets)` returns one ticket per category — the last one seen — with no hint that the rest were dropped. The loop was correct; only the conversion loses data. Either convert with `false` and keep the category inside each value, or group explicitly: ```php $byCategory = []; foreach ($tickets as $category => $ticket) { $byCategory[$category][] = $ticket; } ``` This is also the honest way to express intent: grouping is a decision, while `iterator_to_array()` with preserved keys silently decides "last one wins" for you. ## Checklist when converting - Do the keys mean something? Keep the default only if every key is unique. - Are the keys just positions? Pass `false`. - Is the source destructive or huge? Iterate it with `foreach` instead.
- Why is iterator_to_array() on an SplPriorityQueue a trap?Iterating an `SplPriorityQueue` extracts elements: each `next()` removes the top. `iterator_to_array()` walks it to the end, so you get the elements in priority order but the queue is empty afterwards. Iterate a `clone` of the queue if the original must keep its contents.
- When is converting an iterator to an array the wrong move?When the iterator exists to keep memory flat, such as a large file read line by line or a lazily computed sequence. Converting holds every element at once and can hit `memory_limit`; a `foreach` over the iterator processes one element at a time.
saying these in an interview costs you the question
- iterator_to_array() always returns a list numbered from 0
- Duplicate iterator keys raise a warning or an exception
- Converting an iterator leaves a destructive SPL structure untouched
- Converting to an array is free because iterators are lazy anyway
- iterator_to_array() rejects a plain array in PHP 8.5