Performing a set of actions upon *not* entering a loop is a very common programming necessity. This RFC proposes an optional “else” clause that can be used after while, for, and foreach loops to simplify handling of this particular scenario.
A secondary objective of this RFC is to bring closure to a number of requests in bug tracker (hopefully they can be closed after this, whatever the resolution).
Proposed syntax will look as follows:
// "foreach" example (display a list of names) foreach ($array as $x) { echo "Name: {$x->name}\n"; } else { echo "No records found!\n"; } // "for" example (unset a range of keys from array, then return it, or return null on error) for ($i = some_very_expensive_and_ugly_looking_calculation($array); $i >= 0; $i--) { unset($array[$i]); } else { return null; // horrific error! } return $array; // "while" example (return true if any $search matches have been removed from $array) while ($temp = array_search($search, $array)) { unset($array[$temp]); } else { unset($array[$search]); // just because we can return false; } return true;
And for comparison purposes, the following block (it's obviously longer and less elegant) contains the same code written without loop+else:
foreach ($array as $x) { echo "Name: {$x->name}\n"; } if (count($array) < 1) { echo "No records found!\n"; } if (some_very_expensive_and_ugly_looking_calculation($array) < 0) { // either that or clutter our scope with temporary variable return null; } for ($i = some_very_expensive_and_ugly_looking_calculation($array); $i >= 0; $i--) { unset($array[$i]); } return $array; $removed = false; // no choice... have to remember this in a variable while ($temp = array_search($search, $array)) { unset($array[$temp]); $removed = true; } if (!$removed) { unset($array[$search]); } return $removed;
Proposed “else” clause will be executed only if main loop body has *not* been entered at least once (i.e. loop condition was never satisfied). For this reason do-while loop cannot have this syntax (as it is guaranteed that its body will be entered at least once).
So in general, a loop+else construct is the equivalent of the following user code:
$loop_entered = false; loop ($condition) { // "loop" can be for, foreach, or while $loop_entered = true; } if (!$loop_entered) { // this is our "else" clause }
I suggest loops without a body should not be allowed to have an alternate “else” clause:
while($condition); // Can't add an else { } after this statement for ($i = 0; $i < 10; $i++); foreach ($array as $x);
In order to avoid BC breaks when single-statement blocks are not enclosed in {}'s, any ambiguity should always be resolved in favor of if's. This means that in the following sample “else” block belongs to the “if”, and not the “while”:
if ($hungry) while (more_food()) { eat(); else watch_tv();
However, when there is no conflict between a loop and an “if”, this should be resolved in favor of the “closest” loop, just like it is now with if's. This means that in the following sample “else” belongs to the “foreach” loop, and not the “while”:
while ($condition) foreach ($array as $x) $x->doWork(); else echo "No work was done!\n";
This should work as expected (while respecting the precedence order described above), e.g.:
<?php while ($x = $cursor->fetchObject()): ?> <p>Hi! My name is <?php echo $x->name; ?>!</p> <?php else: ?> <p>Nothing was fetched</p> <?php endwhile; ?>
Under this proposal, the following PHP code would be valid:
while ($condition) { ... } else if ($another_condition) { ... } else { ... }
Because of that, I believe it would make sense to allow loop+elseif syntax as well.
We could also do it the Python way, which means executing “else” upon the natural completion of the loop, and only skipping it on break, continue, or throw. I personally see this a lot less useful than this proposal.
Original discussions in bug tracker:
while {} else {} (by php at bellytime dot com)
Build in foreach else support (by kjarli at gmail dot com)
For... else construct (by jeroenvandenenden at gmail dot com)
May 10, 2012 - RFC draft created