27

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

Jeroen De Dauw
  • 10,321
  • 15
  • 56
  • 79

8 Answers8

15

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.

Edwin Rodríguez
  • 1,229
  • 10
  • 19
  • 1
    This works because array_push takes a variable amount of arguments and you unpack the iterable with ... to a list of arguments. Not an acceptable solution for me due to the need to introduce an extra variable in the scope and then mutating it. – Jeroen De Dauw Jun 16 '17 at 14:13
  • @JeroenDeDauw I know that ... unfolds it but in array() doesn't work in the same way, so there is something weird about it. I agree that it is not a acceptable solution, in fact, I think that is not possible in 7.1 but is the closest that I found considering the limitations (not defining functions and using conditionals) – Edwin Rodríguez Jun 16 '17 at 17:47
  • 2
    will not work for the generator, if the generator returns nothing. then an error appears - no arguments passed to `array_push`, since `...` will resolve to nothing. – Oleg Abrazhaev Oct 21 '19 at 08:08
  • Since PHP 7.3 no error will be thrown for empty iterables using the above code, `array_push` can be called with only the first argument. – dakujem Mar 08 '21 at 09:55
15

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

$array = [...$iterable];

See https://3v4l.org/L3JNH

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

BenMorel
  • 34,448
  • 50
  • 182
  • 322
Michael Petri
  • 171
  • 1
  • 3
7

Can be done like this:

$array = $iterable instanceof \Traversable ? iterator_to_array($iterable) : (array)$iterable;
alexkart
  • 71
  • 1
  • 1
6

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;
}
Sara
  • 719
  • 5
  • 8
5

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,690
  • 5
  • 38
  • 40
Nero
  • 1,555
  • 1
  • 13
  • 28
4

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.

TimWolla
  • 31,849
  • 8
  • 63
  • 96
2

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.

sevavietl
  • 3,762
  • 1
  • 14
  • 21
2

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

Jeroen De Dauw
  • 10,321
  • 15
  • 56
  • 79