PHP Notice: Undefined variable: oops

Published on

Nov 8, 2011

how2tips

Nov 9, 2011 − Tired of having to modify a small, insignificant typo that break your whole code? Would you like to get this sweet security feeling that only compiled code provides? Let's do that with PHP CodeSniffer.

Tired of having to modify a small, insignificant typo that break your whole code?
Would you like to get this sweet security feeling that only compiled code provides?

Let's do that with PHP CodeSniffer.

Compiled code offers this kind of security, it will simply not compile and thow something like this (in gcc):

Imagine you have this complex code:

#include <stdio.h>

int main(int argc, const char *argv[])
{
    char* test = "Hello world!";
    puts (test);
    puts (oops);

    return 0;
}

Compile this code and gcc will return you something like that:

‘oops’ undeclared (first use in this function)

That's pretty cool. Of course, a compiler does a lot more.
But at least you have the certitude your code doesn't use undeclared variables.

That was C - now let's talk about PHP.

Back to PHP

PHP is a dynamic, compiled-at-runtime, step by step bytecode interpreted language.

Contrary to C, it won't throw errors when using undeclared variables, function, classes or methods.
It will only crash during the code interpretation, i.e at runtime.
As a result, you often see notices or fatal errors talking about "Notice: undefined variable oops" or "call to a member function on a non object".
What about discovering these problems before running the code?
Could be cool.

There would be different approaches to resolve this problem:

  • phc, an open source php compiler
  • HipHop, a php to C++ transformer
  • use and IDE that detects these kind of problems (I'm thinking to Eclipse PDT or PHPStorm), but god know how heavy they are
  • PHP CodeSniffer, a php (or css or js) tokenizer that detects coding standard violations.

We'll focus on the code sniffer.
It's surely not the strictest way of achieving this, but it's an easy way to go − and this accounts for something.

PHP CodeSniffer

So basically, you can see phpcs as a collection of sniffers that are listening on specific tokens.
As soon as it encounters a given token in a tokenized source file, phpcs will notify each sniffer about it.
These sniffers will search for coding standard violations (or whatever you want) in the token context, adding errors or warnings based on some criterias.

The idea here was to sniff every T_FUNCTION token and searching if one of the variable found in the function had been declared before having been used.
It's a bottom-top approach in the sense that it searches from the end of the function to the start of the function.

It registers each used variable and looks for a corresponding assignment between the first variable usage and the function's start.

Some edge cases have to be taken into account:

  • variables coming from the function arguments
  • closured variables
  • global variables (booooh)
  • foreach(array() as $element) assigns a $element variable without any assignment token
  • catch(Exception $e) assings a $e variable without any assignment token

There is still some work to handle all these types of cases (like foreach and catch), but it globally works pretty well!

The result of phpcs on this file:

<?php

function($test2) use($closured)
{
    substr($test2);
    $closured;
};

function($test2)
{
    substr($test2);
    $unclosured;
};

/**
 * Class documentation
 *
 * @author Florian Klein <florian.klein@free.fr>
 */
class Test
{
    static protected $test;
    /**
     * test
     *
     * @return void
     */
    public function test()
    {
        foreach (array() as $test2) {
            foreach (array() as $key => $test3) {
                strlen($key);
            }
        }

        $this->test;

        self::$test;
        $valid = 'test';
        $value = strlen($valid);
    }

    /**
     * test
     *
     * @return void
     */
    public function test2()
    {
        $variable = array();
        foreach ($variable as $key => $var) {
            $key;
        }
        $text = strlen($invalid);
        $invalid = 'test';

        $invalid;
        strlen($invalidToo);
    }

    /**
     * test3
     *
     * @param mixed $arg test
     *
     * @return void
     */
    public function test3($arg)
    {
        return strlen($arg);
    }
}

would be:

phpcs Test.php

--------------------------------------------------------------------------------
FOUND 3 ERROR(S) AFFECTING 3 LINE(S)
--------------------------------------------------------------------------------
 12 | ERROR | No assignation of a used variable "$unclosured"
 54 | ERROR | No assignation of a used variable "$invalid"
 58 | ERROR | No assignation of a used variable "$invalidToo"
--------------------------------------------------------------------------------

Now, how to use it:

Install phpcs

sudo pear install PHP_CodeSniffer-1.3.1

Install the specified coding standard

For the moment I put this in a symfony-coding-standard repository, to avoid complicated setup (and because we use Symfony!)

cd /path/to/pear/PHP/CodeSniffer/Standards
git clone git://github.com/docteurklein/Symfony2-coding-standard.git Symfony2
git fetch origin
git checkout -b var_assignment origin/var_assignment
phpcs --config-set default_standard Symfony2

Use it either in a CI environment or manually

phpcs path/to/my/file.php

Still, the best way to use it is to integrate it in your IDE (of course ViM).
But that will be the subject of another article.

Written by

KNP Labs
KNP Labs

Comments