January 20 Johannes Schmitt schmittjoh

Variable Existence Checks in PHP Analyzer

Checking whether a variable exists should be really simple, right? However, let me show you that it is not that simple if you want to make it accurate.

As always this is best explained by looking at some code.

Order is important

function someFunction() {
    $x = 1;
    echo $x;
}

Dead simple, you initialize a variable and then read from it. Obviously, if you reverse the order, i.e. you first read and then initialize it, this should be an error. Even if this is a bit of a contrived example, it shows that we need to take the execution flow of your code into account.

Pass-By Reference

This is still quite easy, however PHP also has another feature, pass-by-reference parameters. If you pass a variable by reference to a function (or method), it is automatically initialized in the originating scope:

function initializes(&$x) { }

var_dump($a); // Undefined variable: a

initializes($a);
var_dump($a); // NULL

So, we actually need to also know all the called functions and also all the methods to determine if a parameter is passed by reference or not. No worries, PHP Analyzer takes care of that as well, and uses the results from its type inference analysis.

The yes, no or maybe cases

While most of the no cases - i.e. a variable never exists - should be taken care of by enough tests, verifying that a variable always exists is a bit harder to do with tests. Let’s take a look at another example:

function someFunction($a) {
     switch ($a) {
         case 'foo':
             $x = 1;
             break;

         case 'bar':
             $x = 2;
             break;
     }

     // Is $x defined here?
     echo $x;
 }

This might look like valid code at first. After all, $x is defined in all the case statements, right? This is if you pass only “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function someFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function someFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function someFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // Add the missing case statement.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    

PHP Analyzer helps you find the cases where your tests are not complete yet to avoid suddenly popping up errors in production.

As always try it out, and we love to hear your feedback.

 

Have Feedback? Tweet to @scrutinizerci

If you experienced a bug or have any questions, please send them to [email protected].