Is there an elegant way to structure consecutive short-circuited assignments?
https://softwareengineering.stackexchange.com/questions/411885
-
12-03-2021 - |
Question
The following code works and is clear, but it's also verbose. I suspect that there's a way to make it more terse, so that it could be skimmed quickly and it'd be more obvious what's happening.
// The functions return either `false`, or a valid URL.
$redirect = site_redirects( $domain, $_SERVER['REQUEST_URI'] );
if ( ! $redirect ) {
$redirect = get_city_slash_year_url( $domain, $_SERVER['REQUEST_URI'] );
}
if ( ! $redirect ) {
$redirect = unsubdomactories_redirects( $domain, $_SERVER['REQUEST_URI'] );
}
if ( ! $redirect ) {
$redirect = get_canonical_year_url( $domain, $path );
}
if ( $redirect ) {
header( 'Location: ' . $redirect );
}
As an example, here's something I tried that didn't work:
$redirect =
site_redirects( $domain, $_SERVER['REQUEST_URI'] ) ||
get_city_slash_year_url( $domain, $_SERVER['REQUEST_URI'] ) ||
unsubdomactories_redirects( $domain, $_SERVER['REQUEST_URI'] ) ||
get_canonical_year_url( $domain, $path )
;
...but that just returns true
or false
, instead of the first truthy return value.
I know it'll vary somewhat from language to language, but I'm curious if there's a common pattern, or at least something common to C-based languages.
Solution
You can use the Ternary Operator:
$redirect = site_redirects( $domain, $_SERVER['REQUEST_URI'] ) ?:
get_city_slash_year_url( $domain, $_SERVER['REQUEST_URI'] ) ?:
unsubdomactories_redirects( $domain, $_SERVER['REQUEST_URI'] ) ?:
get_canonical_year_url( $domain, $path ) ?:
false ;
if ($redirect) header( 'Location: ' . $redirect );
It is "missing" the expression between ?
and :
for the alternate version of the Ternary operator and behaves as follows:
Expression
expr1 ?: expr3
returnsexpr1
ifexpr1
evaluates toTRUE
, andexpr3
otherwise.
If you're worried about its "unexpected behavior", it has already been tested out 10 years ago:
// I couldn't find much info on stacking the new ternary operator, so I ran some tests:
<?php
echo 0 ?: 1 ?: 2 ?: 3; //1
echo 1 ?: 0 ?: 3 ?: 2; //1
echo 2 ?: 1 ?: 0 ?: 3; //2
echo 3 ?: 2 ?: 1 ?: 0; //3
echo 0 ?: 1 ?: 2 ?: 3; //1
echo 0 ?: 0 ?: 2 ?: 3; //2
echo 0 ?: 0 ?: 0 ?: 3; //3
?>
// It works just as expected, returning the first non-false value within a group of expressions.
I noticed that it has no test for an all false
condition, so a quick test using this code confirms its behavior:
<?php
echo 0 ?: 0 ?: 0 ?: 0; // returns 0 as required
?>
Since you're working with strings, just note that false
, "0"
, and ""
all evaluates to a false
. It will follow the logic of empty()
.
OTHER TIPS
I'm not familiar with PHP but if I were doing this in e.g. Python I would create a function like this
def get_first_truthy(*conditions):
for condition in conditions:
if condition:
return condition
return None
If lazy evaluation is important, you change this to accept a list of methods or lambdas.
Here's an example of how calls to that would look (again, in Python):
redirect = get_first_truthy(
site_redirects(domain, _SERVER['REQUEST_URI'] ),
get_city_slash_year_url(domain, _SERVER['REQUEST_URI']),
unsubdomactories_redirects(domain, _SERVER['REQUEST_URI']),
get_canonical_year_url(domain, path)
)
if redirect:
header(f'Location: {redirect}')
For the lazy eval solution, I'm really unsure what is possible in PHP but here's what that could look like in Python:
def get_first_truthy(*conditions):
for condition in conditions:
result = condition()
if result:
return result
return None
And the usage:
def lazy(func, domain, param):
def do_eval():
return func(domain, param)
return do_eval
redirect = get_first_truthy(
lazy(site_redirects, domain, _SERVER['REQUEST_URI']),
lazy(get_city_slash_year_url, domain, _SERVER['REQUEST_URI']),
lazy(unsubdomactories_redirects, domain, _SERVER['REQUEST_URI']),
lazy(get_canonical_year_url, domain, path)
)
if redirect:
header(f'Location: {redirect}')
If you wanted something more generic you could do this:
def lazy(func, *params):
def do_eval():
return func(*params)
return do_eval
Again, I'm not sure this helps you in PHP but maybe you could use anonymous functions to get something similar.
I'm unfamiliar with php; however the logic looks like a long boolean OR expression. Maybe something like:
if ( ($redirect = site_redirects( $domain, $_SERVER['REQUEST_URI'] ))
|| ($redirect = get_city_slash_year_url( $domain, $_SERVER['REQUEST_URI'] ))
|| ($redirect = unsubdomactories_redirects( $domain, $_SERVER['REQUEST_URI'] ))
|| ($redirect = get_canonical_year_url( $domain, $path ))
) {
header( 'Location: ' . $redirect );
}