Итерируемые объекты и тип массива намекают?
У меня есть много функций, которые либо имеют намеки типа для массивов, либо используют is_array()
для проверки массивности переменной.
Теперь я начинаю использовать объекты, которые являются итерабельными. Они реализуют Iterator
или IteratorAggregate
. Будут ли они приняты в качестве массивов, если они пройдут через намеки типа или пройдут is_array()
?
Если мне нужно изменить свой код, существует ли общий тип is_iterable()
, или мне нужно сделать что-то вроде:
if ( is_array($var) OR $var instance_of Iterable OR $var instanceof IteratorAggregate ) { ... }
Какие еще итерируемые интерфейсы есть?
Ответы
Ответ 1
Я думаю, вы имеете в виду 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.
Ответ 2
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)
Ответ 3
Мне действительно нужно было добавить проверку для stdClass, поскольку экземпляры stdClass работают в циклах foreach, но stdClass не реализует Traversable:
function is_iterable($var) {
return (is_array($var) || $var instanceof Traversable || $var instanceof stdClass);
}
Ответ 4
Я использую простой (и, возможно, немного хакерский) способ проверить "итеративность".
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
Ответ 5
К сожалению, вы не сможете использовать подсказки типа для этого, и вам нужно будет сделать is_array($var) or $var instanceof ArrayAccess
вещи. Это известная проблема, но afaik она еще не решена. По крайней мере, он не работает с PHP 5.3.2, который я только что проверил.
Ответ 6
Вы можете использовать подсказку типа, если вы переключаетесь на использование итерационных объектов.
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;
}