将PHP可迭代对象转换为数组或可遍历对象

28

我很高兴PHP 7.1引入了可迭代伪类型

现在,当您只是循环遍历这种类型的参数时,这非常棒。但当您需要将其传递给仅接受arrayTraversable的PHP函数时,我不清楚该怎么做。例如,如果您想进行一个数组差异,并且您的iterableTraversable,那么您将获得一个array。相反,如果您调用一个需要 Iterator 的函数,如果iterablearray,则会出现错误。

是否有类似于iterable_to_array(而不是:iterator_to_array)和iterable_to_traversable的东西?

我正在寻找一种解决方案,可以避免我在函数中使用条件语句来处理此差异,并且不依赖于我定义自己的全局函数。

使用PHP 7.1

8个回答

16

我不确定这是否是您正在寻找的内容,但这是最简单的方法。

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

我不太确定它为什么有效。只是我发现你的问题很有趣,开始摆弄PHP。

完整示例:

<?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());

如果能够和函数array()一起使用会很好,但是由于它是语言结构,所以有点特殊。它也不会保留关联数组中的键。


1
这个方法可行是因为array_push可以接受可变数量的参数,并且你可以使用...将可迭代对象解包成参数列表。但对我来说不是一个可接受的解决方案,因为需要在作用域中引入一个额外的变量,然后对其进行改变。 - Jeroen De Dauw
@JeroenDeDauw 我知道...展开它,但在array()中不以同样的方式工作,所以有些奇怪。我同意这不是一个可接受的解决方案,事实上,考虑限制(不定义函数和使用条件语句),我认为在7.1中不可能,但这是我找到的最接近的方法。 - Edwin Rodríguez
2
如果生成器没有返回任何内容,则无法使用它,这时会出现错误 - 没有传递参数给 array_push,因为 ... 将解析为空。 - Oleg Abrazhaev
自从PHP 7.3以后,使用上述代码时不会抛出任何关于空迭代器的错误,array_push可以仅带有第一个参数的调用。 - dakujem

15

对于 PHP >= 7.4,这个功能可以很好地直接使用:

$array = [...$iterable];

请查看https://3v4l.org/L3JNH编辑: 仅适用于迭代器不包含字符串键的情况。

1
现在在 PHP 8.1 中支持字符串键:https://3v4l.org/UkdLD - BenMorel

7
可以这样做:
$array = $iterable instanceof \Traversable ? iterator_to_array($iterable) : (array)$iterable;

6
有类似于iterable_to_array和iterable_to_traversable的东西吗?
只需在项目中添加它们的某个位置,它们不占用太多空间并给你提供了你所需的确切API。
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;
}

5

从 PHP 8.2 开始,iterator_to_array()iterator_count() 函数将接受 iterable 而不是 Traversable。因此,当遇到数组时,它们将开始接受 array 并执行您期望它们执行的操作。

具体来说,以下等式成立:

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

并且

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

更多细节可以在相应的RFC中找到:PHP RFC:使iterator_*()系列接受所有可迭代对象

5

术语容易混淆

  • Traversable
    • Iterator(我将其视为具体类型,如用户定义的类A)
    • IteratorAggregate
  • iterable(这是一个伪类型,可以接受arraytraversable
  • array(这是一个具体类型,并且在需要 Iterator 类型的上下文中不能交换)
  • arrayIterator(可用于将数组转换为迭代器)

因此,如果函数A(iterable $a){},那么它接受参数为数组或traversable的实例(因为很明显这两个类都实现了Traversable。在我的测试中,传递ArrayIterator也可以正常工作)。

如果参数指定为 Iterator 类型,则传递数组会导致 TypeError。


2
对于“可迭代对象转数组”的情况,似乎没有单独的函数调用可用,你需要在代码中使用条件语句或定义自己的函数,例如这个:
function iterable_to_array( iterable $iterable ): array {
    if ( is_array( $iterable ) ) {
        return $iterable;
    }
    return iterator_to_array( $iterable );
}

对于“可迭代对象转换为迭代器”的情况,情况要复杂得多。可以使用ArrayIterator将数组轻松转换为TraversableIterator实例可以直接返回。这留下了不是IteratorTraversable实例。乍一看,似乎可以使用IteratorIterator,它接受一个Traversable。但是,当给它一个返回GeneratorIteratorAggregate时,该类存在缺陷并且无法正常工作。

解决此问题的方法太长而无法在此处发布,但我已创建了一个包含两个转换函数的小型库:

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

查看 https://github.com/wmde/iterable-functions


2

您可以将可迭代属性强制转换为数组 $array = (array) $iterable; - Fabien Salles

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接