62

I have a lot of functions that either have type hinting for arrays or use is_array() to check the array-ness of a variable.

Now I'm starting to use objects that are iterable. They implement Iterator or IteratorAggregate. Will these be accepted as arrays if they pass through type hinting, or undergo is_array()?

If I have to modify my code, is there a generic sort of is_iterable(), or must I do something like:

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

What other iterable interfaces are out there?

BenMorel
  • 34,448
  • 50
  • 182
  • 322
user151841
  • 17,377
  • 29
  • 109
  • 171

6 Answers6

77

I think you mean instanceof Iterator, PHP doesn't have an Iterable interface. It does have a Traversable interface though. Iterator and IteratorAggregate both extend Traversable (and AFAIK they are the only ones to do so).

But no, objects implementing Traversable won't pass the is_array() check, nor there is a built-in is_iterable() function. A check you could use is

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

To be clear, all php objects can be iterated with foreach, but only some of them implement Traversable. The presented is_iterable function will therefore not detect all things that foreach can handle.

goat
  • 31,486
  • 7
  • 73
  • 96
NullUserException
  • 83,810
  • 28
  • 209
  • 234
  • 26
    `Iterator` and `IteratorAggregate` don't implement `Traversable`. They are interfaces and as such have no implementation. They *extend* `Traversable`. Other than that, +1 – Artefacto Aug 27 '10 at 14:49
  • I can not see what I am doing wrong, but it doesn't seem to work for classes which are fine with foreach: http://codepad.org/hi373LYg – MSpreij Jun 20 '13 at 15:52
  • 7
    `foreach` will work with classes which aren't instances of those interfaces. It just iterates through all properties of the instance. If you want custom behavior you'll have to implement `Iterator` – NullUserException Jun 20 '13 at 17:09
  • 6
    PHP has the pseudo type iterable now (7.1). :-D – Vinicius Dias Dec 02 '16 at 19:27
  • 6
    @NullUserException as PHP7.1 implements the `is_iterable` function already, you should add and `if function_exists(...)` to your example code – Philipp Jun 02 '17 at 21:37
39

PHP 7.1.0 has introduced the iterable pseudo-type and the is_iterable() function, which is specially designed for such a purpose:

This […] proposes a new iterable pseudo-type. This type is analogous to callable, accepting multiple types instead of one single type.

iterable accepts any array or object implementing Traversable. Both of these types are iterable using foreach and can be used with yield from within a generator.

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

This […] also adds a function is_iterable() that returns a boolean: true if a value is iterable and will be accepted by the iterable pseudo-type, false for other values.

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)
Blackhole
  • 20,129
  • 7
  • 70
  • 68
  • seems odd that it should be false for stdClass as you can iterate over them with foreach same as an array. I often use arrays and basic objects interchangeably with functions that iterate over things and that's the main scenario I'd want to check. Pretty much never use ArrayItterators or Traversables. – Brad May 19 '20 at 07:28
12

I actually had to add a check for stdClass, as instances of stdClass do work in foreach loops, but stdClass does not implement Traversable:

function is_iterable($var) {
    return (is_array($var) || $var instanceof Traversable || $var instanceof stdClass);
}
Isaac
  • 1,505
  • 15
  • 8
  • 10
    All objects can be `foreach`'d, but doing so is often unintentional. – Brilliand Apr 10 '13 at 18:14
  • 2
    Not all objects are an `instanceof stdClass`, but as @Brilliand pointed out, all objects can be `foreach`'d. In many cases you would be checking to see if you actually want to `foreach` it, as in when `is_array($var)` returns true or when `$var instanceof Traversable` returns true. If you actually do want to run the `foreach` on anything that can be `foreach`'d (nothing wrong with that as long as you realize what you are doing and why), then you would be better off replacing the last part of the code with `is_object($var)` – still_dreaming_1 Feb 11 '15 at 04:00
  • The next question is why are you using stdClass? Hardly any benefits, many disadvantages. – CommandZ Apr 28 '15 at 16:14
  • 2
    Update: from PHP7.1 a native [```is_iterable()```](http://php.net/manual/en/function.is-iterable.php) function is available. – Kamafeather Oct 18 '17 at 18:02
  • 1
    furthermore, is_iterable(new stdClass) returns false. I think you should mark your answer as deprecated somehow – Pierre-Antoine Guillaume Aug 27 '19 at 07:18
4

I use a simple (and maybe a little hackish) way to test for "iterability".

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

When you try to loop a non iterable variable, PHP throws a warning. By setting a custom error handler prior the attempt to iterate, you can transform an error into an exception thus enabling you to use a try/catch block. Afterwards you restore the previous error handler to not disrupt the program flow.

Here's a small test case (tested in 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
Tivie
  • 18,864
  • 5
  • 58
  • 77
  • 2
    I ran a slightly modified version on 3v4l.org which works on PHP 5.0+: http://3v4l.org/ITNF9. Even stdClass passes on all of them. – Phillip Whelan Jun 27 '13 at 04:13
  • Do you know if there's something that your method catches, that [this answer](http://stackoverflow.com/a/12395684/249538) doesn't? – goat Dec 21 '13 at 21:31
  • 2
    @rambocoder any object that doesn't "extend" StdClass nor explicitly implement Traversable. For instance, some PECL libraries, such as PHP Imuttable Extension, use C constructs rather than PHP Objects. Those objects are iterable, but the function above does not recognise them as such, since they aren't based on PHP Object Construct at C level. – Tivie Dec 23 '13 at 17:58
  • There is a new [```is_iterable()```](http://php.net/manual/en/function.is-iterable.php) function since PHP7. Take a look to it :) – Kamafeather Oct 15 '17 at 00:39
  • @Kamafeather that function fails for Classes that don't implement Iterable or for Generic Objects (StdCLass) – Tivie Oct 18 '17 at 16:21
  • @Tivie, with *fails* do you mean that throws an exception? Because to me ```is_iterable()``` returns ```false``` (as expected) on ```stdClass``` (either as object or class-name) and with classes not implementing ```Traversable```. Although in the above comment I meant _PHP7.1_; my fault sorry. – Kamafeather Oct 18 '17 at 17:55
  • @Kamafeather I mean it returns false. StdClass and other classes are "iterablish", in the sense that you can loop through their properties. This is specially useful when dealing with json_decode (and you don't want to convert the obejct to an array), or when dealing with some PECL extensions. – Tivie Oct 18 '17 at 23:49
  • Mmmh, I see your point; can be confusing. But as long as the new pseudo-type ```iterable``` exists, one is supposed to give precedence to the meaning of this latter. – Kamafeather Oct 19 '17 at 09:06
1

Unfortunately you won't be able to use type hints for this and will have to do the is_array($var) or $var instanceof ArrayAccess stuff. This is a known issue but afaik it is still not resolved. At least it doesn't work with PHP 5.3.2 which I just tested.

Raoul Duke
  • 4,241
  • 2
  • 23
  • 18
  • [`ArrayAccess`](http://php.net/manual/en/class.arrayaccess.php) is only about direct access by "array key" syntax, it has nothing to do with iterability. – Gras Double Mar 04 '16 at 15:58
0

You CAN use type hinting if you switch to using iterable objects.

protected function doSomethingWithIterableObject(Iterator $iterableObject) {}

or

protected function doSomethingWithIterableObject(Traversable $iterableObject) {}

However, this can not be used to accept iterable objects and arrays at the same time. If you really want to do that could try building a wrapper function something like this:

// 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;
}
Jon Gilbert
  • 866
  • 8
  • 5