Revisited: foreachif()
A while ago I blogged about my desire for a foreachif() construct in PHP. A recent comment from a reader made me re-think the concept and consider ways in which it could be implemented elegantly, without the need for changes to PHP’s syntax.
Why implement it in PHP code rather than make a change to the syntax itself? Well, the reason is fairly simple; having too many syntaxes for the same thing can make code very difficult to read and understand unless the reader is a guru in that particular language. Anyone who has been forced to use Perl will know what I mean.
The above comment suggested the use of the eval() function. Whilst this would work, I don’t particularly like the idea for a number of reasons:
- If there is an error in the eval’d code, this will show up in a back-trace as occurring in eval’d code rather than pointing to the actual problem itself – messy.
- If like me, you are used to a nice, cushy IDE, you’ll know that almost all of your code completion / highlighting immediately goes out the window inside of a string.
- I try to avoid eval() as a rule of thumb. It just makes me feel dirty.
I thought I might try a similar solution however, using PHP 5.3’s new closures feature. Closures (A.K.A. anonymous functions) will most likely be familiar to anyone who has used JavaScript in any serious fashion, so I will not go into them here. If you are unfamiliar with the concept, please RTFM.
In order to make this work there are a few things we need:
- An array to iterate over
- A condition of some sort to evaluate on each loop
- A callback to execute if the condition evaluates to true
- Some measure of control over variable names passed to the callback would also be nice
The bottom line is that it is possible, but it’s nowhere near as nice, or elegant as I had hoped.
Method 1: using closures
Using nothing but the new closures syntax I was able to come up with the following reasonable-looking foreachif() function:
function foreachif(array $array, $condition, $callback) { if (!is_callable($condition)) throw new Exception('condition must be a valid callback'); if (!is_callable($callback)) throw new Exception('callback must be a valid callback'); foreach($array as $key => $value) { if ($condition($array, $value, $key)) { $callback($array, $value, $key); } } }
This seemed fine until the time came to actually use the thing. Reproducing one of the examples from the aforementioned article resulted in this mess:
$array = array(1,2,3,4); foreachif ($array, function($array, $value){ return $value % 2 == 0; }, function($array, $value) { echo $value.','; });
Not particularly readable huh? Downright horrible is more like it.
Method 2: mixed closures and eval’d strings
The above mess made me think perhaps I could just use eval() for the condition – perhaps this would help remove some clutter from the statement? Well, yes it does, but it also presents its own problems. Here is the new function:
function foreachif(array $array, $value_name, $condition, $callback) { if (!is_callable($callback)) throw new Exception('callback must be a valid callback'); foreach($array as $key => $$value_name) { eval("\$run = $condition;"); if ($run) { $callback($array, $$value_name, $key); } } }
In order to retain flexibility in naming the variable used to store the current iteration value, I was forced to pass the variable name as a string. I didn’t like this at all, and the situation would only get worse if you wanted to be able to choose the name of the array key inside the condition also.
For the sake of completeness, here is an example the utilises the above function:
$array = array(1,2,3,4); foreachif ($array, 'this_value', '$this_value % 2 == 0', function($array, $this_value) { echo $this_value.','; });
Then suddenly I realised I was forgetting a very, very important point; variable scope.
The big problem
What neither of the above implementations give you is access to any variables defined outside of the closure / eval’d code itself. Granted this could be worked around by grabbing every defined variable and re-defining them within the scope of the loop content, but this would almost certainly have major performance issues when used in any real-world situation.
At this point I basically abandoned the search for a function-based implementation and started considering PHP syntax alternatives, which turned out to be a much more elegant solution in the end…
Method 3: using existing PHP syntax in alternative ways
Starting with the ultra-obvious solution gives us the following:
$array = array(1,2,3,4); foreach ($array as $value) { if ($value % 2 == 0) { echo $value.','; }}
There is also PHP’s alternate syntax for these structures to consider, and I think the following actually comes out looking quite nice:
foreach ($array as $value): if ($value % 2 == 0): echo $value.','; endif; endforeach;
The main area in which I intended to use this structure was in native PHP templating. In line with this thinking, here is the above restated as I would present it in a template:
<?php foreach ($array as $value): if ($value % 2 == 0): ?> <?php echo $value ?>, <?php endif; endforeach; ?>
As you can see, these last three examples are actually quite readable (the last one as much so as native PHP templating can be). The big bonus of course is that they still allow access to all variables in their scope and have no significant performance impact (provided of course that your condition is not intensive on resources).
No Comments »
RSS feed for comments on this post. TrackBack URL