Php web making error message when posting a comment

It often happens to me to handle data that can be either an array or a null variable and to feed some foreach with these data.

$values = get_values();

foreach ($values as $value){
  ...
}

When you feed a foreach with data that are not an array, you get a warning:

Warning: Invalid argument supplied for foreach() in [...]

Assuming it's not possible to refactor the get_values() function to always return an array (backward compatibility, not available source code, whatever other reason), I'm wondering which is the cleanest and most efficient way to avoid these warnings:

  • Casting $values to array
  • Initializing $values to array
  • Wrapping the foreach with an if
  • Other (please suggest)

Solution 1:

Personally I find this to be the most clean - not sure if it's the most efficient, mind!

if (is_array($values) || is_object($values))
{
    foreach ($values as $value)
    {
        ...
    }
}

The reason for my preference is it doesn't allocate an empty array when you've got nothing to begin with anyway.

Solution 2:

How about this one? lot cleaner and all in single line.

foreach ((array) $items as $item) {
 // ...
 }

Solution 3:

I usually use a construct similar to this:

/**
 * Determine if a variable is iterable. i.e. can be used to loop over.
 *
 * @return bool
 */
function is_iterable($var)
{
    return $var !== null 
        && (is_array($var) 
            || $var instanceof Traversable 
            || $var instanceof Iterator 
            || $var instanceof IteratorAggregate
            );
}

$values = get_values();

if (is_iterable($values))
{
    foreach ($values as $value)
    {
        // do stuff...
    }
}

Note that this particular version is not tested, its typed directly into SO from memory.

Edit: added Traversable check

Solution 4:

Please do not depend on casting as a solution, even though others are suggesting this as a valid option to prevent an error, it might cause another one.

Be aware: If you expect a specific form of array to be returned, this might fail you. More checks are required for that.

E.g. casting a boolean to an array (array)bool, will NOT result in an empty array, but an array with one element containing the boolean value as an int: [0=>0] or [0=>1].

I wrote a quick test to present this problem. (Here is a backup Test in case the first test url fails.)

Included are tests for: null, false, true, a class, an array and undefined.


Always test your input before using it in foreach. Suggestions:

  1. Quick type checking: $array = is_array($var) or is_object($var) ? $var : [] ;
  2. Type hinting arrays in methods before using a foreach and specifying return types
  3. Wrapping foreach within if
  4. Using try{}catch(){} blocks
  5. Designing proper code / testing before production releases
  6. To test an array against proper form you could use array_key_exists on a specific key, or test the depth of an array (when it is one !).
  7. Always extract your helper methods into the global namespace in a way to reduce duplicate code