Ensuring a Well-Structured Switch Command in PHP

In PHP, the switch statement is a powerful tool for controlling program flow, especially when dealing with multiple conditional branches. However, achieving a clean and efficient switch has its own snags. This post explores essential tips and practices for checking the quality of a ‘switch’ command to ensure code quality and optimize its performance.

Missing Default Entry

The first aspect to consider is whether the switch statement includes a default case. A default acts as a fallback option, ensuring that the code executes when none of the defined case match.

default should always be present. In fact, the command match, which is an alternative version of switch, throws an exception when it cannot match any of the case, and the default is missing.

<?php
switch($a) {
    case 0:
      echo 'A';
      break;
      
    case 1:
      echo 'A';
      break;
}

?>

default serve different purposes :

  • Handle all other cases as one. This is when a finite list of cases is available, and anything outside that list can be handled in a single manner.
  • Raise an error if ever is it reached. In this situation, the context preceding the switch ensures that a finite list of cases is handled. Then, whenever a case outside the legit ones reach the command, an exception should be raised.
<?php
switch($a) {
    case 0:
      echo 'A';
      break;
      
    case 1:
      echo 'A';
      break;
      
    default:
      throw new MyException('This should be zero or one');
}

?>

Always ensure that switch has a default.

Duplicate Cases

It’s rewarded to examine a switch statement for duplicate cases. PHP only process the first case that it encounters, ignoring the others. This leads to dead code, with some of the branches in the switch to be ignored and never used.

<?php
switch($a) {
    case 'gif':
        // doSomething() and break
      
    case 'jpeg':
    case 'jpg':
        // doSomething() and break

    case 'png':
        // doSomething() and break

    case 'gif':
        // doSomething() and break
      
    default:
      throw new MyException('This should be an image format');
}

?>

Duplicates happen often in very long list of cases.

They also happen when the actual value is hidden by constant names, or also, by PHP’s type juggling. Indeed, switch uses a relaxed comparison style, and some literals may actually be the same.

<?php

// $a in [null, false, '', 0]
switch($a) {
    // Most of the cases ends here, except 0
    // because it is the first encountered
    case '':
        print 'Empty string';break;

    // 0 goes here
    case 0:
        print 'Zero';break;
      
    case false:
        print 'false';break;

    case null:
        print 'null';break;
      
    default:
      throw new MyException('This should be empty');
}

?>

Carefully review the cases values and remove the duplicates.

Duplicate Code Blocks

Another common issue is the repetition of code blocks in different case branches. This redundancy can bloat your switch and make it challenging to update in the future. Consider refactoring your code to eliminate duplication and merge the cases.

<?php

switch($a) {
    case 'john':
        $b = 0; break;

    case 'henry':
        $b = 10; break;

    // duplicate of case 'john'
    case 'julia':
        $b = 0; break;

    case 'jeroen':
        $b = 30; break;
      
    default:
      throw new MyException('This is an unexpected name');
}

?>

Usage of Enumeration Cases

PHP has built-in support for enumerations. One of the important advantages of enumerations is their finite nature. They represent a full collection of options, and no other option shall exist. This makes it easy to check if a switch is covering all the possible situations.

<?php

enum formats {
    case GIF;
    case JPG;
    case PNG;
}

switch($a) {
    case formats::GIF:
        // doSomething() and break;

    case formats::PNG:
        // doSomething() and break;
        
    // We are missing one case : JPG!

    // Unknown cases triggers an exception lazily (only if reached)
    case formats::TIFF:
        // doSomething() and break;

    default:
      throw new MyException('Unknown image format');
}

?>

Optimization with Simple Switch

PHP 7.2 introduced an optimisation for switch.

Until then, PHP would review each cases one after the other, and stop as soon as it finds a matching value. With PHP 7.2 and later, PHP sets up a lookup table for the case values, when they are simple literals. Then, PHP jumps immediately to the right case, and bypass all other non-matching cases.

<?php

switch($a) {
    case 1:
        // doSomething() and break;

    case 2:
        // doSomething() and break;
        
    default:
      throw new MyException('Unknown image format');
}

?>

Simple switch happens when all the cases are simple values, that can be directly compared to a incoming variable.

If the switch has a mix of expressions and simple values, it is recommended to separate the simple literals in a first switch, and put the others in a second switch.

<?php

switch($a) {
    case 1:
        // doSomething() and break;

    case 2:
        // doSomething() and break;
        
    default:
        switch($a) {
            case $b + 2:
                // doSomething() and break;

        case strtolower($a):
            // doSomething() and break;
        
        default:
            throw new MyException('Unknown case');
    }
}

?>

It might also be more convenient to process them in a different manner, with a previous condition, for example.

Cunning little switch

In conclusion, a well-structured switch statement in PHP can greatly enhance your code’s clarity, maintainability, and performance. By checking for missing default entries, eliminating duplicate cases and code blocks, leveraging enumeration-like structures, and optimizing complex switches, you can ensure that your switch statements remain an efficient and effective part of any PHP codebase.

Happy PHP code auditing