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

Asked

Viewed 298 times

9

In PHP, not just arrays are eternal, but also some specific objects. For example, objects that implement the interface Iterator or IteratorAggregate. Another example is stdClass and ArrayObject, iterate with foreach. Still, there is the case of those functions that use yield internally. The yield makes the function can return a value that is eternal via foreach.

Therefore, what is the safe way to verify that a value is eternal with foreach via PHP, since there are so many variations?

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

$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

2 answers

10


In PHP, all array is everlasting.

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

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

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

The classes cited in the post, ArrayObject and FileSystemIterator have the implementations of Iterator. So that’s why they’re eternal.

See a test on ideone.

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

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

Based on the information obtained above, I would say that the safest way to check whether a value is PHP-grade 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;
}

3

Note: This answer is only complementary

As of version 7.1.0 of PHP there is a function is_iterable is native, after testing it in this version I noticed that objects stdClass are not considered to be eternal, for example:

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

var_dump(is_iterable($foo));

Will return bool(false), then I believe that an example with "backward compatibility" could be 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" on PHP 7.1 would be using the type declaration iterable, an example code would be:

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

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

Pass by stdClass for example or another type that is not eternal will occur 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" function (demanding)

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

  • is_iterable((array) new stdClass) give what, boss?

  • 1

    @Wallacemaxters bool(true) due to cast, but I believe that they do not consider stdClass as iterable because it only works in some situations, I believe with functions like next(); and current() does not work

  • 1

    In fact any object casted for array is iterable. A cast of Object for array takes its properties and transforms into an associative array. https://3v4l.org/Ts0S8

  • @gmsantos goes beyond "objects": https://ideone.com/UlAh3b

  • Yes, array is iterable... anything that is converted to array is iterable :) http://php.net/manual/en/language.types.array.php#language.types.array.casting

  • 1

    @gmsantos think that’s exactly what I wanted to expose :) as said in the first comment to Wallace, "due to the cast"

Show 1 more comment

Browser other questions tagged

You are not signed in. Login or sign up in order to post.