PHP, итерируемый в массив или Traversable

Я очень рад, что в PHP 7.1 появился итерируемый псевдотип.

Теперь, хотя это здорово, когда просто перебираешь параметр этого типа, мне неясно, что делать, когда вам нужно передать его функциям PHP, которые принимают только array или только Traversable. Например, если вы хотите сделать array_diff, а ваш iterable — это Traversable, вы получите array. И наоборот, если вы вызываете функцию, которая принимает итератор, вы получите ошибку, если iterable является array.

Есть что-то вроде iterable_to_array (НЕ: iterator_to_array) и iterable_to_traversable?

Я ищу решение, которое избегает условных выражений в моих функциях только для того, чтобы позаботиться об этой разнице, и это не зависит от того, определяю ли я свои собственные глобальные функции.

Использование PHP 7.1


person Jeroen De Dauw    schedule 16.06.2017    source источник


Ответы (7)


Не уверен, что это то, что вы ищете, но это самый короткий способ сделать это.

$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(), но поскольку это языковая конструкция, она немного особенная. Он также не сохраняет ключи в массивах assoc.

person Edwin Rodríguez    schedule 16.06.2017
comment
Это работает, потому что array_push принимает переменное количество аргументов, и вы распаковываете итерируемый объект с помощью ... в список аргументов. Неприемлемое решение для меня из-за необходимости ввести в область видимости дополнительную переменную, а затем изменить ее. - person Jeroen De Dauw; 16.06.2017
comment
@JeroenDeDauw Я знаю, что ... разворачивает его, но в array () работает по-другому, так что в этом есть что-то странное. Я согласен, что это неприемлемое решение, на самом деле, я думаю, что это невозможно в 7.1, но это самое близкое, что я нашел, учитывая ограничения (без определения функций и использования условий) - person Edwin Rodríguez; 16.06.2017
comment
не будет работать для генератора, если генератор ничего не возвращает. затем появляется ошибка - никакие аргументы не передаются в array_push, так как ... ничего не решит. - person Oleg Abrazhaev; 21.10.2019
comment
Поскольку в PHP 7.3 при использовании приведенного выше кода для пустых итерируемых объектов не будет выдаваться ошибка, array_push можно вызывать только с первым аргументом. - person dakujem; 08.03.2021

Есть ли что-то вроде 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;
}
person Sara    schedule 16.06.2017

Можно сделать так:

$array = $iterable instanceof \Traversable ? iterator_to_array($iterable) : (array)$iterable;
person alexkart    schedule 08.08.2019

Условия легко смешивать

  • Traversable
    • Iterator (I see this as a concrete type, like user-defined class A)
    • Итераторагрегат
  • итерируемый (это псевдотип, допустимы массив или обходной)
  • массив (это конкретный тип, и его нельзя заменить с помощью Iterator в контексте того, что требуется тип Iterator)
  • arrayIterator (может использоваться для преобразования массива в итератор)

Итак, вот почему, если функция A(iterable $a){}, то она принимает параметр либо массива, либо экземпляра traversable (и Iterator, и IteratorAggregate принимаются, потому что очевидно, что эти два класса реализуют Traversable. В моем тесте передача ArrayIterator также работает).

Если для параметра указан тип Iterator, передача массива вызовет TypeError.

person Nero    schedule 03.10.2018

Вы можете использовать iterator_to_array для преобразования вашей переменной в Traversable сначала:

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

Метод преобразования взят из комментария под этот вопрос.

Вот рабочая демонстрация.

person sevavietl    schedule 18.06.2017
comment
Вы можете просто передать свое итерируемое свойство в массив $array = (array) $iterable; - person Fabien Salles; 31.10.2019

Для случая «итерируемого в массив» кажется, что нет единственного вызова функции, который вы можете сделать, и что вам нужно либо использовать условное выражение в своем коде, либо определить свою собственную функцию, подобную этой:

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

В случае «итерируемого в итератор» все намного сложнее. Массивы можно легко преобразовать в Traversable с помощью ArrayIterator. Iterator экземпляров можно просто вернуть как есть. Остается Traversable экземпляров, которые не являются Iterator. На первый взгляд кажется, что вы можете использовать IteratorIterator, который принимает Traversable. Однако этот класс содержит ошибки и не работает должным образом, если ему присвоено значение IteratorAggregate, которое возвращает значение Generator.

Решение этой проблемы слишком длинное, чтобы публиковать его здесь, хотя я создал мини-библиотеку, содержащую обе функции преобразования:

  • функция iterable_to_iterator(iterable $iterable): Итератор
  • функция iterable_to_array(iterable $iterable): массив

См. https://github.com/wmde/iterable-functions.

person Jeroen De Dauw    schedule 04.10.2018

Для php ›= 7.4 это работает довольно хорошо из коробки:

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

См. https://3v4l.org/L3JNH.

Изменить: работает только до тех пор, пока итерируемый объект не содержит строковых ключей.

person Michael Petri    schedule 18.02.2021