This Gist covers example from production code that show how using do{...}while(false);
and make it much easier to write robust code with PHP.
Over the past several years I have been using a construct when writing PHP code that allows me to use guard clauses more effectively than I was every able to with early return.
I originally started using it to avoid issues with using XDEBUG and where many early return
s in a function required finding and setting breakpoints on them all to ensure the debugger would stop before that function existed.
However, since using it I found other much greater benefits that I had not anticipated. See section after the example.
That construct is as illustrated with the following example:
function example() {
do {
$value = DEFAULT;
if ( ! $foo ) {
break;
}
if ( ! $bar ) {
break;
}
if ( $baz ) {
break;
}
$value = do_real_work_here();
} while (false);
if ( is_null($value) ) {
do_common_stuff_here();
}
return $value;
}
I have found it to be extremely usable, even to the point when I try not to use it I find myself reverting to it.
The benefits I have discovered when using this pattern are:
- It allows for significant consistency for functions that need guard clauses.
- It is easy to document in a standards doc,
- It is easy to apply, and
- It is easy to code review.
- It naturally encourages the writing more guard clauses because it makes writing them is so easy; you rarely ever have to think about the logic.
- It makes it more obvious when a function should be refactored into multiple functions; whenever you feel the need to use multiple nested
do{...}while(false);
constructs you have identified a need to refactor your logic into two functions instead of just one. - It makes the refactoring extremely easy — with very little tedious recoding — unlike I had found when using early returns.
My "best practices" for using this pattern are as follows:
- Follow the Happy Path. Avoid nesting as much as possible,
- Simplify complex guard clauses expressions into multiple guard clauses, if possible,
- Make the nested block of code in your guard clause as short as possible, ideally just
break
- Don't use naked
if () break
as it disrupts the visual pattern of guard clauses - Don't nest or duplicate
do{...}while(false);
move to their own methods. - Always initialize the
return
value variable at the top of thedo {
- Never
return
early - Never
throw
- Always use
break
to exit early - If the last guard clause is an
if
/else
scenario, nest the shorter scenario in theif(...)
, especiallg if abreak
. - Always capture
throw
n errors and restructure usingbreak
. - "Handle" an error as soon as you recognize it, i.e. write to a log and then
break
. - Ideally never handle an error more than once.
- Always
return
the return value via its variable or a referencing expression on the last line. - Invert single AND logic into a
do{...}while(false)
block of multipleif(...)
statements, when it simplifies code. - Use these same best practices where applicable with
for*{...}
loops andcontinue
See UiElements.php
below for a perfect example of a method that really should be broken in two because of the need for two different do{...}while(false);
clauses.
However, there are several things I really dislike about this pattern:
- The need to always specify
while (false)
when it is really just visual noise for this use-case, and - Developers not using an IDE with a feature like Live Templates in PhpStorm may accidentally type
while (true)
and create an endless loop they will need to later correct. - It uses the
do{...} while(...)
forbreak
and not for loops, and probably 99% of developers will first think of a loop when they see it in your code unless they are familiar with this pattern.
If PHP allowed any of the following it would address at least the first two (2) if not all unfortunate aspects of this pattern:
- Allowing
do{...}
forbreak
s without requiringwhile(...)
, or - Allowing
try{...}
w/support for breaks without requiringcatch(){...}
, or - Adding some other construct like
block{...}
orguard{...}
or whatever that could supportbreak
and have appropriate semantics for this use-case.
Frankly this is my #1 request for PHP. If I could have this I would be (much) happy(er.)
The following are real-world examples from actual production code that include code after the while(false)
.
Note however that this pattern is equally useful for guard clauses when when the while(false);
immediately precedes the return
statment.
(Being code from a production system means the code is not always perfect code and has aspects that could certainly be improved, so please avoid criticizing this code for reason other than the pattern being highlighted by this Gist.)