Closure support in PHP_CodeSniffer
31 JanQuite a few sniffs included with PHP_CodeSniffer check various aspects of function declarations, from commenting to spacing and everything in between. But the rules for anonymous functions (closures) tend to be different. They are used inside function calls or assigned to variables mid-function. It doesn't make sense to enforce the same commenting and spacing rules in these contexts. Doing so would probably make the code harder to read, which is not the goal of a coding standard.
PHP's tokenizer does not have a token specifically for closures, so for this code:
<?php
function myFunc($foo)
{
$callback = function ($bar) use ($foo) {
$bar += $foo;
};
}//end myFunc()
?>
You end up with a token stack like this:
*** START SCOPE MAP ***
Start scope map at 1: T_FUNCTION => function
Process token 2 []: T_WHITESPACE =>
Process token 3 []: T_STRING => myFunc
Process token 4 []: T_OPEN_PARENTHESIS => (
* skipping parenthesis *
Process token 7 []: T_WHITESPACE => \n
Process token 8 []: T_OPEN_CURLY_BRACKET => {
=> Found scope opener for 1 (T_FUNCTION)
...
Process token 11 [opener:8;]: T_VARIABLE => $callback
Process token 12 [opener:8;]: T_WHITESPACE =>
Process token 13 [opener:8;]: T_EQUAL => =
Process token 14 [opener:8;]: T_WHITESPACE =>
Process token 15 [opener:8;]: T_FUNCTION => function
* token is an opening condition *
* searching for opener *
...
Process token 27 []: T_OPEN_CURLY_BRACKET => {
=> Found scope opener for 15 (T_FUNCTION)
...
Process token 38 [opener:27;]: T_CLOSE_CURLY_BRACKET => }
=> Found scope closer for 15 (T_FUNCTION)
...
Process token 42 [opener:8;]: T_CLOSE_CURLY_BRACKET => }
=> Found scope closer for 1 (T_FUNCTION)
*** END SCOPE MAP ***
Notice that there are two T_FUNCTION tokens in there (tokens 1 and 15). The second is actually a closure, but sniffs can't tell the difference between them.
Instead of forcing sniff developers to check each function to see if it has a name, PHP_CodeSniffer, from version 1.3.0RC3 (not yet released) will have a new token to listen for: T_CLOSURE.
After the main token processing, the PHP tokenizer built into PHP_CodeSniffer does some additional work. You'll now see this at the end of PHP_CodeSniffer's verbose output:
*** START ADDITIONAL PHP PROCESSING ***
* token 15 on line 4 changed from T_FUNCTION to T_CLOSURE
*** END ADDITIONAL PHP PROCESSING ***
This means that all existing sniffs that listen for the T_FUNCTION token will now ignore closures by default. For all sniffs included with PHP_CodeSniffer, this is the correct behaviour. But if you have been making assumptions in your sniffs that closures also appear as T_FUNCTION tokens, you'll need to make a change to support 1.3.0. Just make sure you register both T_FUNCTION and T_CLOSURE tokens in your sniff, or when searching for next and previous tokens, make sure you pass an array with T_FUNCTION and T_CLOSURE instead of a single token.
This change also means you can start writing targeted sniffs to enforce rules for closures. Coding standards tend to be fairly relaxed on how they can be used and how they should be defined, leading to a variety of different styles. If you are already enforcing a specific style in your standard, and you are using closures, it might be a good time to sit down and think about what standards you want to put around them.
If you can't wait for the next release, you can play around with this new feature by installing PHP_CodeSniffer from the SVN source. Note that this will replace your existing PHP_CodeSniffer install:
pear uninstall PHP_CodeSniffer
svn co http://svn.php.net/repository/pear/packages/PHP_CodeSniffer/trunk PHP_CodeSniffer
cd PHP_CodeSniffer
pear install -f package.xml
Stay up to date on all PHP_CodeSniffer changes, including new features and releases, by subscribing to the RSS feed or following me on Twitter.