Итерируемые объекты и подсказки типа массива? - PullRequest
52 голосов
/ 27 августа 2010

У меня есть много функций, которые либо имеют хинтинг типа для массивов, либо используют is_array() для проверки массивности переменной.

Теперь я начинаю использовать объекты, которые можно повторять. Они реализуют Iterator или IteratorAggregate. Будут ли они приняты как массивы, если они пройдут через тип намеков или пройдут is_array()?

Если мне нужно изменить свой код, есть ли общий вид is_iterable(), или я должен сделать что-то вроде:

if ( is_array($var) OR $var instance_of Iterable OR $var instanceof IteratorAggregate ) { ... }

Какие еще итерируемые интерфейсы существуют?

Ответы [ 6 ]

75 голосов
/ 27 августа 2010

Я думаю, что вы имеете в виду instanceof Iterator, PHP не имеет интерфейса Iterable. Тем не менее, он имеет интерфейс Traversable. Iterator и IteratorAggregate оба расширяют Traversable (и AFAIK - единственные, кто это делает).

Но нет, объекты, реализующие Traversable, не пройдут проверку is_array(), равно как и встроенная функция is_iterable(). Вы можете использовать чек

function is_iterable($var) {
    return (is_array($var) || $var instanceof Traversable);
}

Для ясности, все php объекты могут быть повторены с foreach, но только некоторые из них реализуют Traversable. Поэтому представленная функция is_iterable не будет обнаруживать все, что может обработать foreach.

26 голосов
/ 04 сентября 2016

PHP 7.1.0 представила псевдотип iterable и функцию is_iterable() , специально разработанную для такой цели:

Этот […] предлагает новый iterable псевдотип. Этот тип аналогичен callable, принимая несколько типов вместо одного.

iterable принимает любой array или объект, реализующий Traversable. Оба эти типа повторяются с использованием foreach и могут использоваться с yield из генератора.

function foo(iterable $iterable) {
    foreach ($iterable as $value) {
        // ...
    }
}

Этот […] также добавляет функцию is_iterable(), которая возвращает логическое значение: true, если значение является итеративным и будет принято псевдотипом iterable, false для других значений.

var_dump(is_iterable([1, 2, 3])); // bool(true)
var_dump(is_iterable(new ArrayIterator([1, 2, 3]))); // bool(true)
var_dump(is_iterable((function () { yield 1; })())); // bool(true)
var_dump(is_iterable(1)); // bool(false)
var_dump(is_iterable(new stdClass())); // bool(false)
12 голосов
/ 13 сентября 2012

Мне фактически пришлось добавить проверку для stdClass, поскольку экземпляры stdClass работают в циклах foreach, но stdClass не реализует Traversable:

function is_iterable($var) {
    return (is_array($var) || $var instanceof Traversable || $var instanceof stdClass);
}
4 голосов
/ 05 декабря 2012

Я использую простой (и, возможно, немного хакерский) способ проверки на «итеративность».

function is_iterable($var) {
    set_error_handler(function ($errno, $errstr, $errfile, $errline, array $errcontext)
    {
        throw new \ErrorException($errstr, null, $errno, $errfile, $errline);
    });

    try {
        foreach ($var as $v) {
            break;
        }
    } catch (\ErrorException $e) {
        restore_error_handler();
        return false;
    }
    restore_error_handler();
    return true;
}

Когда вы пытаетесь зациклить не повторяемую переменную, PHP выдает предупреждение. Установив пользовательский обработчик ошибок до попытки итерации, вы можете преобразовать ошибку в исключение, что позволит вам использовать блок try / catch. После этого вы восстанавливаете предыдущий обработчик ошибок, чтобы не прерывать выполнение программы.

Вот небольшой тестовый пример (протестирован в PHP 5.3.15):

class Foo {
    public $a = 'one';
    public $b = 'two';
}

$foo = new Foo();
$bar = array('d','e','f');
$baz = 'string';
$bazinga = 1;
$boo = new StdClass();    

var_dump(is_iterable($foo)); //boolean true
var_dump(is_iterable($bar)); //boolean true
var_dump(is_iterable($baz)); //boolean false
var_dump(is_iterable($bazinga)); //bolean false
var_dump(is_iterable($boo)); //bolean true
1 голос
/ 27 августа 2010

К сожалению, вы не сможете использовать подсказки типа для этого, и вам придется сделать is_array($var) or $var instanceof ArrayAccess вещи. Это известная проблема, но на самом деле она до сих пор не решена. По крайней мере, он не работает с PHP 5.3.2, который я только что протестировал.

0 голосов
/ 12 мая 2011

Вы МОЖЕТЕ использовать подсказки типа, если переключаетесь на использование итерируемых объектов.

protected function doSomethingWithIterableObject(Iterator $iterableObject) {}

или

protected function doSomethingWithIterableObject(Traversable $iterableObject) {}

Однако это нельзя использовать для одновременного приема итерируемых объектов и массивов. Если вы действительно хотите это сделать, попробуйте создать функцию-оболочку примерно так:

// generic function (use name of original function) for old code
// (new code may call the appropriate function directly)
public function doSomethingIterable($iterable)
{
    if (is_array($iterable)) {
        return $this->doSomethingIterableWithArray($iterable);
    }
    if ($iterable instanceof Traversable) {
        return $this->doSomethingIterableWithObject($iterable);
    }
    return null;
}
public function doSomethingIterableWithArray(array $iterable)
{
    return $this->myIterableFunction($iterable);
}
public function doSomethingIterableWithObject(Iterator $iterable)
{
    return $this->myIterableFunction($iterable);
}
protected function myIterableFunction($iterable)
{
    // no type checking here
    $result = null;
    foreach ($iterable as $item)
    {
        // do stuff
    }
    return $result;
}
...