0

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.

0
3

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 returns expr1 if expr1 evaluates to TRUE, and expr3 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().

1
  • That's exactly what I was looking for, thanks!
    – Ian Dunn
    Jul 2 '20 at 16:26
2

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.

8
  • Thanks, I considered that, but I think in my case it'd make it worse, since the caller would be messy, and the extra code from the unintuitive abstraction would require the dev to spend more time reading in order to understand what's happening. I updated the example to show how I'm using the value.
    – Ian Dunn
    Jun 24 '20 at 15:49
  • 1
    I added an example call to make the discussion concrete. What is messy about this? It's a lot less noisy than your current approach. I don't really know what this would look like in PHP so that might be the disconnect.
    – JimmyJames
    Jun 24 '20 at 17:31
  • I was referring to the case where you'd pass in the names of functions and their params. Passing in the function return values would result in unnecessary database queries.
    – Ian Dunn
    Jun 25 '20 at 13:46
  • I added a lazy evaluation version. It still seems much cleaner than what your base approach looks like.
    – JimmyJames
    Jun 25 '20 at 15:58
  • 1
    @IanDunn Well, my answer is pretty concise and doesn't need any weird abstractions.
    – Nelson
    Jul 2 '20 at 2:45
2

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 );
      }
1
  • Ah, yeah, that works, but it needs to use || instead of &&, so that it short-circuits after the first truthy value. I submitted an edit.
    – Ian Dunn
    Jul 2 '20 at 0:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.