Итерируемые объекты и тип массива намекают?

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

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

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

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

Какие еще итерационные интерфейсы есть?

Solutions Collecting From Web of "Итерируемые объекты и тип массива намекают?"

Я думаю, вы имеете в виду экземпляр 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.

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) 

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

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

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

 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 

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

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

 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; }