Table of Contents

Request for Comments: Loop+Else control structure

Introduction

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).

Syntax

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;

How does this work?

When is loop's "else" clause executed?

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
}

What about loops without a body?

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);

What is the precedence?

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";

Alternative syntax for control structures

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; ?>

How about elseif?

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.

Pros

Cons

Common Misconceptions

Alternative Proposals

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.

References

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)

Changelog

May 10, 2012 - RFC draft created