How to check if a value is iterable by foreach in PHP?

9

In PHP, not only arrays are iterable, but also some specific objects. For example, objects that implement the interface Iterator or even IteratorAggregate . Another example is stdClass and ArrayObject , which iterate with foreach . Still, there are those functions that use yield internally. The yield causes the function to return a value that is iterated via foreach .

So, what is the safe way to check if a value is iterable with foreach via PHP, since there are so many variations?

For example, how could you make the following variables pass according to a specific test to check if it is iterable?

$a = new stdClass;
$b = new ArrayObject;
$c = new FileSystemIterator(__DIR__);
$d = array(1, 2, 3);

$e = new PDO(); // não iterável
$f = 'Não iterável'; // não iterável
    
asked by anonymous 11.01.2017 / 12:42

2 answers

10

In PHP, all array is iterable.

Any object can also be used in foreach , but behavior can not be desired because all public properties are used in the iteration. So you need to set the interface Iterator or IteratorAggregate or RecursiveIterator to determine the behavior of the class when the instance of the class is invoked in foreach .

An important information is that the Iterator or IteratorAggregate interfaces inherit another interface, called Traversable . It is used for PHP to internally detect if the class is iterable via foreach . So when you implement Iterator or IteratorAggregate , you indirectly implement Traversable . The RecursiveIterator in turn inherits Iterator , therefore it also indirectly inherits Traversable .

In short: Every iteration interface in PHP inherits Traversable . So every class that implements Iterator , is an instance of Traversable .

The classes mentioned in the post, ArrayObject and FileSystemIterator have implementations of Iterator . So that's why they're iterable.

See a test at ideone.com

The yield that was quoted in the question is used to return an instance of the Generator class. It also implements Iterator and therefore implicitly implements Traversable .

Now the case of stdClass , is the first example cited. Because it is an empty object, all properties defined in it are public. So this explains why it is iterate via foreach .

Based on the above information, I would say that the safest way to check if a value is iterable in PHP is:

is_array($valor) || $valor instanceof stdClass || $valor instanceof Traversable;

You can create a function to check this if you want:

function is_iterable($value)
{
    return is_array($value) || $value instanceof stdClass || $value instanceof Traversable;
}
    
11.01.2017 / 12:42
3
  

Note: This answer is only complimentary

As of PHP version 7.1.0, is_iterable is native , after testing it in this version I noticed that stdClass objects are not considered iterable, eg

<?php
$foo = new stdClass;
$foo->val = 1;
$foo->val2 = 2;

var_dump(is_iterable($foo));

It will return bool(false) , so I think an example with "backward compatibility" might look something like:

//Checa se já existe a função is_iterable
if (!function_exists('is_iterable'))
{
    //se não existir cria a função, por exemplo no PHP5.6 ou 7.0
    function is_iterable($value)
    {
        return is_array($value) || $value instanceof \Traversable;
    }
}

Another way to "check" in PHP 7.1 would be to use type declaration iterable , a sample code would be:

function minhafuncao(iterable $valor)
{
    //Faça algo aqui
}

minhafuncao([1, 2, 3]); //Usando

If you pass stdClass for example or another type that is not iterable a Exception TypeError if you do this:

minhafuncao(new stdClass);

Exception will bring something like:

PHP Fatal error:  Uncaught TypeError: Argument 1 passed to minhafuncao() must be
 iterable, object given, called in foo.php on line 7 and defined in foo.php:2
Stack trace:
#0 foo.php(7): minhafuncao(Object(stdClass))
#1 {main}
  thrown in foo.php on line 2

Fatal error: Uncaught TypeError: Argument 1 passed to minhafuncao() mustbe iterable, object given, called in foo.php on line 7 and defined in foo.php:2
Stack trace:
#0 foo.php(7): minhafuncao(Object(stdClass))
#1 {main}
  thrown in foo.php on line 2

What can be interesting if you want to create a more "restricted" (demanding) function

More details on this question What is the iterable type of PHP 7.1?

    
18.08.2017 / 22:30