Hướng dẫn php traversable vs iterable

I'm quite happy that PHP 7.1 introduced the iterable pseudo-type.

Now while this is great when just looping over a parameter of this type, it is unclear to me what to do when you need to pass it to PHP functions that accept just an array or just a Traversable. For instance, if you want to do an array_diff, and your iterable is a Traversable, you will get an array. Conversely, if you call a function that takes an Iterator, you will get an error if the iterable is an array.

Is there something like iterable_to_array (NOT: iterator_to_array) and iterable_to_traversable?

I'm looking for a solution that avoids conditionals in my functions just to take care of this difference, and that does not depend on me defining my own global functions.

Using PHP 7.1

asked Jun 16, 2017 at 11:18

Hướng dẫn php traversable vs iterable

Jeroen De DauwJeroen De Dauw

9,65114 gold badges50 silver badges76 bronze badges

Not sure this is what are you searching for but this is the shortest way to do it.

$array = [];
array_push ($array, ...$iterable);

I'm not very sure why it works. Just I found your question interesting and I start fiddling with PHP

Full example:

<?php

function some_array(): iterable {
    return [1, 2, 3];
}

function some_generator(): iterable {
    yield 1;
    yield 2;
    yield 3;
}

function foo(iterable $iterable) {
    $array = [];
    array_push ($array, ...$iterable);
    var_dump($array);

}

foo(some_array());
foo(some_generator());

It would be nice if works with function array(), but because it is a language construct is a bit special. It also doesn't preserve keys in assoc arrays.

answered Jun 16, 2017 at 12:03

4

For php >= 7.4 this works pretty well out of the box:

$array = array(...$iterable);

See https://3v4l.org/L3JNH

Edit: Works only as long the iterable doesn't contain string keys

answered Feb 18, 2021 at 11:50

Hướng dẫn php traversable vs iterable

1

Can be done like this:

$array = $iterable instanceof \Traversable ? iterator_to_array($iterable) : (array)$iterable;

answered Aug 8, 2019 at 8:51

alexkartalexkart

711 silver badge1 bronze badge

0

Is there something like iterable_to_array and iterable_to_traversable

Just add these to your project somewhere, they don't take up a lot of space and give you the exact APIs you asked for.

function iterable_to_array(iterable $it): array {
    if (is_array($it)) return $it;
    $ret = [];
    array_push($ret, ...$it);
    return $ret;
}

function iterable_to_traversable(iterable $it): Traversable {
    yield from $it;
}

answered Jun 16, 2017 at 15:52

SaraSara

7015 silver badges8 bronze badges

Terms are easy to mix

  • Traversable
    • Iterator (I see this as a concrete type, like user-defined class A)
    • IteratorAggregate
  • iterable (this is a pseudo-type, array or traversable are accepted)
  • array (This is a concrete type, and it's not exchangeable with Iterator in context of that a Iterator type is required)
  • arrayIterator (can be used to convert array to iterator)

So, that's why if function A(iterable $a){}, then it accepts parameter of either array or an instanceof traversable (Iterator, IteratorAggregate are both accepted because it's obvious these two classes implement Traversable. In my test, passing ArrayIterator also works ).

In case Iterator type is specified for parameter, passing in an array will cause TypeError.

jchook

6,1944 gold badges34 silver badges39 bronze badges

answered Oct 3, 2018 at 8:31

NeroNero

1,48511 silver badges26 bronze badges

You can use iterator_to_array converting your variable to Traversable first:

$array = iterator_to_array((function() use ($iterable) {yield from $iterable;})());

Conversion method is taken from the comment under this question.

Here is working demo.

answered Jun 18, 2017 at 7:12

Hướng dẫn php traversable vs iterable

sevavietlsevavietl

3,6921 gold badge13 silver badges21 bronze badges

1

For the "iterable to array" case it seems there is no single function call you can make and that you'll either need to use a conditional in your code or define your own function like this one:

function iterable_to_array( iterable $iterable ): array {
    if ( is_array( $iterable ) ) {
        return $iterable;
    }
    return iterator_to_array( $iterable );
}

For the "iterable to Iterator" case things are much more complicated. Arrays can be easily translated into a Traversable using ArrayIterator. Iterator instances can just be returned as they are. That leaves Traversable instances that are not Iterator. On first glance it looks like you can use IteratorIterator, which takes a Traversable. However that class is bugged and does not work properly when giving it an IteratorAggregate that returns a Generator.

The solution to this problem is too long to post here though I have created a mini-library that contains both conversion functions:

  • function iterable_to_iterator( iterable $iterable ): Iterator
  • function iterable_to_array( iterable $iterable ): array

See https://github.com/wmde/iterable-functions

answered Oct 4, 2018 at 10:17

Hướng dẫn php traversable vs iterable

Jeroen De DauwJeroen De Dauw

9,65114 gold badges50 silver badges76 bronze badges

Starting with PHP 8.2 the iterator_to_array() and iterator_count() functions will accept iterable instead of Traversable. Thus they will start to accept arrays and do what you would expect them to do when encountering an array.

Specifically the following equalities hold:

iterator_to_array($array, true) == $array
iterator_to_array($array, false) == array_values($array)

and

iterator_count($array) == count($array)

More details can be found in the corresponding RFC: PHP RFC: Make the iterator_*() family accept all iterables.

answered Aug 25 at 20:32

TimWollaTimWolla

30.9k8 gold badges64 silver badges94 bronze badges