22

Is there a way to stop an array_walk from inside the anonymous function ?

Here is some sample code (that works) to show what I mean, that checks if an array has only numeric values.

$valid = true;
array_walk($parent, function ($value) use (&$valid) {
    if (!is_numeric($value)) {
        $valid = false;
    }
});

return $valid ? 'Valid' : 'Invalid';

If I have a big enough array, and the first entry is invalid, the rest of the (redundant) checks are still done, so I would like to stop the execution.

Using break / continue doesn't work (error: Fatal error: Cannot break/continue 1 level in ...).

Note: I don't want to rewrite the code, I just want to know IF this is possible.

Vlad Preda
  • 9,780
  • 7
  • 36
  • 63
  • 3
    You *could* throw, then catch, an Exception. Of course, it's the wrong approach, but it's possible. – Anthony Sterling Jul 25 '13 at 08:43
  • Not possible directly, but where exactly do you draw the line for rewriting the code? (The `Exception` solution sounds like it'd work, but I would sooner use a plain `foreach` than do that). – Jon Jul 25 '13 at 08:46
  • @Jon: Well, I was curious if it was possible for functions like this. I wouldn't like to use `for` / `foreach`, that's it (mostly a theoretical question :) ). Anthony, you should post that as an answer. – Vlad Preda Jul 25 '13 at 08:48
  • 2
    That's it! I'm going to propose `(bool)array_product($array, 'is_numeric')` soon :) – Ja͢ck Jul 25 '13 at 09:19
  • @Jack: this works if the array has values different than 0 :) `if (array_product(array(1, 2, 'a')) == 0) { /* non numeric array code */ }` – Vlad Preda Jul 25 '13 at 10:54

2 Answers2

18

As stated, theoretically it's possible but I'd advise against it. Here's how to use an Exception to break out of the array_walk.

<?php
$isValid = false;

$array = range(1, 5);

try {
    array_walk($array, function($value) {
        $isAMagicNumber = 3 === $value;
        if ($isAMagicNumber) {
            throw new Exception;
        } 
    });
}catch(Exception $exception) {
    $isValid = true;
}

var_dump($isValid);

/*
    bool(true)
*/
Anthony Sterling
  • 2,451
  • 16
  • 10
  • 1
    This may be useful if this is a core check in your application, and you would throw an exception anyway. Doing something like `throw new InvalidInputException;`, then handling it properly. But in most cases, it's better not to do this :) Thanks for the answer! – Vlad Preda Jul 25 '13 at 15:14
12

You can put a static flag inside the anonymous function:

array_walk($ary, function($item) {
    static $done = false;
    if($done) {
        return;
    }

    // … your code

    if($myBreakCondition) {
        $done = true;
        return;
    }
});

This doesn’t actually stop the iteration, but all further cycles after the flag is set simply do nothing. Not very efficient, but it might work without any greater performance impact if the arrays iterated are not too large.

In your case, the code would be:

$valid = true;
array_walk($parent, function($value) use(&$valid) {
    static $done = false;
    if($done) {
        return;
    }

    if(!is_numeric($value)) {
        $valid = false;
        $done = true;
        return;
    }
});
return $valid ? 'Valid' : 'Invalid';

But actually it won’t be much difference if there was no “break” at all. Only the “false” would be assigned for every invalid value, which does not matter as the result would be still false. Maybe it would be even more efficient that my static variable cheat.

Personally in your case I would use array_filter instead:

$valid = count(array_filter($parent, 'is_numeric')) == count($parent);

or just

$valid = array_filter($parent, 'is_numeric')) == $parent;

If all values in the $parent array are numeric, they would be all present after the filtering. On the other hand, any non-numeric value in the array would affect the contents (decreasing the item count) in the filtered array and the comparison would yield false.

Glutexo
  • 547
  • 6
  • 13