404

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)
Geoffrey Hale
  • 10,597
  • 5
  • 44
  • 45
Roberto Aloi
  • 30,570
  • 21
  • 75
  • 112

20 Answers20

631

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.

vlasits
  • 2,215
  • 1
  • 15
  • 27
Andy Shellam
  • 15,403
  • 1
  • 27
  • 41
168

How about this one? It's a lot cleaner and all in single line.

foreach ((array) $items as $item) {
 // ...
 }
ytarabe
  • 3
  • 2
Ajith R Nair
  • 2,170
  • 1
  • 15
  • 10
  • 9
    This is the only thing that worked for me. For some reason, PHP didn't believe that the multidimensional array I built was actually an array of arrays. – Justin Jul 29 '15 at 20:21
  • 1
    Same here, this is a very nice fix for an array containing either arrays or null values. Simply add a test in the foreach loop to continue if the data is null. – Lizardx Jan 26 '16 at 00:37
  • 1
    Brilliant code, skipped the if, else for array and none-array value using $_POST with checkbox ! – Yann Chabot May 09 '16 at 19:27
  • 8
    NOTE: While beautiful looking and resolves the invalid foreach warning, this method will return an undefined variable warning if the variable is not set in any way. Use `isset()` or `is_array()` or both, entirely depending on your scenario etc. – James Jul 22 '16 at 13:26
  • I had exactly the same situation Justin had with multi-dimensional arrays, and was surprised PHP errored on this. I am coming from other languages usually, so this answer was perfect. Thanks! – Pysis Oct 24 '17 at 02:13
  • @Pysis you guys are telling tales. PHP will **always** iterate a multidimensional array. You surely won't be able to prove your words with a code example. While a much more plausible explanation is just **you** wrote some bad code, but blamed foreach for it. – Your Common Sense Aug 16 '23 at 19:21
50

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

Kris
  • 40,604
  • 9
  • 72
  • 101
  • 2
    You may be able to remove the other two classes, @Kris. They both extend **[Traversable](http://www.php.net/manual/en/class.traversable.php)** now and seem to have been born that way in 5.0.0. Though I'm feeling a tiny doubt as to whether instanceof always applied to extends. – Bob Stein Mar 08 '14 at 10:44
  • 1
    @BobStein-VisiBone: yes (except they are interfaces, not classes) However; I put Traversable in before those, neither Iterator nor IteratorAggregate would ever need verifying (this way they won't slow down execution). I left them in to keep the answer as close as possible to the original answer I gave and to keep it obvious/readable. – Kris Mar 14 '14 at 06:51
  • 2
    I think it'd be fair to add `is_object($var)` re. http://php.net/manual/en/language.oop5.iterations.php – Mark Fox Jul 23 '14 at 22:53
  • 1
    @MarkFox: Feel free, however I intentionally left it out; I've never seen a use for it that wasn't better served by implementing `Iterator` or `IteratorAggregate`, but that's of course just my opinion and therefor subjective (I never use public fields). – Kris Jul 28 '14 at 07:34
  • 1
    @kris `stdClass` doesn't implement these interfaces, therefore I would be in favour for `is_object` as well. https://bugs.php.net/bug.php?id=61312 – William George Mar 02 '15 at 18:33
  • @WilliamGeorge, You could if you wanted to traverse public fields but I actively choose not to view a single objects' properties as a list or collection. there is no guarantee that all properties are homogenous. in other words, I consider traversing an objects properties to be either a very bad idea or too specialized a case. (would prefer using reflection in either case) – Kris Mar 03 '15 at 09:07
  • 1
    @hakre: your edit completely changes the behaviour and meaning of my answer and negates its entire purpose. Your "edit" is better suited to be an answer on its own. – Kris May 25 '15 at 13:52
  • 1
    I'd replace the $var !== null by !empty($var), just one reason: the empty string "" counts as traversable but will raise the Invalid argument exception. – Alex Jul 10 '15 at 10:11
20

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
Community
  • 1
  • 1
AARTT
  • 503
  • 5
  • 8
14

Try this:

//Force array
$dataArr = is_array($dataArr) ? $dataArr : array($dataArr);
foreach ($dataArr as $val) {
  echo $val;
}

;)

Gigoland
  • 1,287
  • 13
  • 10
  • 1
    This won't work well with associative arrays.. The is_array method is overall better... and easier... – AO_ Jul 14 '14 at 13:49
8

All answers provided above are essentially just error suppression.

Your PHP is telling you that you are trying to use a variable of incorrect type, and there is possibly an error. but all answers provided are just brushing this message off.

Your best bet is to initialize every variable before use. And to make return types strict and explicit. You must ask yourself, WHY get_values() is returning anything than array? Why it cannot be made to return just an empty array, if no data found? Surely it can be.

Your Common Sense
  • 156,878
  • 40
  • 214
  • 345
  • Casting is an option - if you initialise an array using `$array = (array)null;` you get an empty array. Of course it's a waste of memory allocation ;-) – Andy Shellam Apr 13 '10 at 14:13
  • 3
    +1: read from a sentimental point of view, I don't care if the language can do without, variables _MUST_ be declared and unreliable results _MUST_ be checked. It's required to keep the developer(s) sane and the error logs short. – Kris Mar 27 '13 at 09:23
  • 1
    This is *far and away* the best answer to this question. If you're trying to iterate something then you clearly expect it to be iterable. Casting it or checking if it is iterable is a waste of time as all it does is mask the fact that the variable doesn't contain what you think it does, which means the *actual* problem is somewhere else in your code. – Nick Feb 22 '21 at 04:47
6
foreach ($arr ?: [] as $elem) {
    // Do something
}

This doesen't check if it is an array, but skips the loop if the variable is null or an empty array.

Update from PHP 7.0 you should use the null coalescing operator:

foreach ($arr ?? [] as $elem) {
    // Do something
}

This would solve the warning mentioned in the comment (here a handy table that compares ?: and ?? outputs).

T30
  • 11,422
  • 7
  • 53
  • 57
  • This produces a PHP warning "Cannot use a scalar value as an array" when $arr is invalid. You can create an empty array beforehand and then refer to that instead of []. But that can sometimes produce a separate problem when using $elem as a reference like "&$elem" so I wouldn't recommend either method now. – Russell G Jul 07 '20 at 14:44
6
$values = get_values();

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

Problem is always null and Casting is in fact the cleaning solution.

boctulus
  • 404
  • 9
  • 15
5

If you're using php7 and you want to handle only undefined errors this is the cleanest IMHO

$array = [1,2,3,4];
foreach ( $array ?? [] as $item ) {
  echo $item;
}
Edwin Rodríguez
  • 1,229
  • 10
  • 19
5

As of PHP >= 7.1.0 use is_iterable

https://www.php.net/manual/en/function.is-iterable.php

if (is_iterable($value)) {
    foreach ($value as $v) {
        ...
    }
}
TechDingo
  • 176
  • 2
  • 11
3

Warning invalid argument supplied for foreach() display tweets. go to /wp-content/plugins/display-tweets-php. Then insert this code on line number 591, It will run perfectly.

if (is_array($tweets)) {
    foreach ($tweets as $tweet) 
    {
        ...
    }
}
Nik
  • 2,885
  • 2
  • 25
  • 25
3

More concise extension of @Kris's code

function secure_iterable($var)
{
    return is_iterable($var) ? $var : array();
}

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

especially for using inside template code

<?php foreach (secure_iterable($values) as $value): ?>
    ...
<?php endforeach; ?>
Community
  • 1
  • 1
HongKilDong
  • 1,276
  • 3
  • 16
  • 23
2

There seems also to be a relation to the environment:

I had that "invalid argument supplied foreach()" error only in the dev environment, but not in prod (I am working on the server, not localhost).

Despite the error a var_dump indicated that the array was well there (in both cases app and dev).

The if (is_array($array)) around the foreach ($array as $subarray) solved the problem.

Sorry, that I cannot explain the cause, but as it took me a while to figure a solution I thought of better sharing this as an observation.

araldh
  • 51
  • 6
2

Exceptional case for this notice occurs if you set array to null inside foreach loop

if (is_array($values))
{
    foreach ($values as $value)
    {
        $values = null;//WARNING!!!
    }
}
Farid Movsumov
  • 12,350
  • 8
  • 71
  • 97
2

This warning is happening because the array that you want use is empty, you can use of below condition:

if ($your_array != false){
   foreach ($your_array as $value){
         echo $value['your_value_name'];
    }
}
Mohamad Shabihi
  • 121
  • 1
  • 4
1

I'll use a combination of empty, isset and is_array as

$array = ['dog', 'cat', 'lion'];

if (!empty($array) && isset($array) && is_array($array) {
    //loop
    foreach ($array as $values) {
        echo $values; 
    }
}
Nik
  • 2,885
  • 2
  • 25
  • 25
Rotimi
  • 4,783
  • 4
  • 18
  • 27
1

Use is_array function, when you will pass array to foreach loop.

if (is_array($your_variable)) {
  foreach ($your_variable as $item) {
   //your code
}
}
Billu
  • 2,733
  • 26
  • 47
1

How about this solution:

$type = gettype($your_iteratable);
$types = array(
    'array',
    'object'
);

if (in_array($type, $types)) {
    // foreach code comes here
}
Julian
  • 4,396
  • 5
  • 39
  • 51
1

What about defining an empty array as fallback if get_value() is empty?
I can't think of a shortest way.

$values = get_values() ?: [];

foreach ($values as $value){
  ...
}
Quentin Veron
  • 3,079
  • 1
  • 14
  • 32
-2
<?php
 if (isset($_POST['checkbox'])) {
    $checkbox = $_POST['checkbox'];
    foreach ($checkbox as $key) {
       echo $key . '<br>';
    }
}

?>
    <input type="checkbox" name="checkbox[]" value="red">red <br>
    <input type="checkbox" name="checkbox[]" value="green">green <br>
as_bold_as_love
  • 216
  • 5
  • 11
  • 3
    -1, If `$yourArray = 1;` it will try to iterate, and you will get the error. `empty()` is not a suitable test. – Brad Koch Jul 11 '13 at 20:10
  • @BradKoch is absolutely right. is_array() is the only reliable way of testing whether $yourArray is an array. See other answers for details on why is_array() is not sufficient--foreach can handle iterators as well. – cgeisel Jul 17 '13 at 16:29