Variable variables handling order: changes in PHP

2019-04-14 05:30发布

问题:

With the new PHP 7.0.0 out now, I'm a bit worried about the changes in evaluation order of the so-called 'variable variables'.

On this page, under 'Changes to variable handling', a table is displayed with examples of expressions with their handling order in PHP 5 and PHP 7. The four expressions listed are:

$$foo['bar']['baz']
$foo->$bar['baz']
$foo->$bar['baz']()
Foo::$bar['baz']()

Given the following string and array:

$qux = 'quux';
$foo = array('bar' => array('baz' => 'qux'));

the first expression in the table $$foo['bar']['baz'] is interpreted in PHP 5 as the value of a variable named as the value in $foo['bar']['baz'], thus the value of $qux, which is 'quux'.

However, in PHP 7, as I understand it, the same expression will be interpreted as a variable named as the value in $foo, thus I expect a PHP Notice for an 'array to string conversion', since $foo is an array.

The other examples in the table seem to be a variation of this same theme.

Of course I'm curious to why this is changed in PHP 7 (specifically, why is this change more important than being backwards compatible), however, that's not a suitable question for SO. My question is more practical:

What would be the recommended way of coping with this incompatibility?

Of course, adding curly braces to the offending expressions will help (${$foo['bar']['baz']}, $foo->{$bar['baz']}, $foo->{$bar['baz']}() and Foo::{$bar['baz']}()), but this is very cumbersome, going through tons of old code, searching for relatively few occurances...

Otherwise, are these four examples the only possible syntax variations? That is, can I create a RegExp and grep all offending code? What other variations might exist?

回答1:

Rasmus Lerdorf wrote a static analysis tool that can spot these so-called Uniform Variable Syntax issues, called Phan https://github.com/etsy/phan

Phan has the option -b, --backward-compatibility-checks to check for potential PHP 5 -> PHP 7 BC issues.



回答2:

https://wiki.php.net/rfc/uniform_variable_syntax

You don't really have a choice but to re-factor them by hand. Unless you can come up with a regular expression to find all use of variable variable syntax.

Regarding the reason "why". Uniform variable syntax allows us to use properties of data structures (like array indexes, and return values) in the same way we use "chaining" of object methods.

The change to the variable variable order of precedence was a casualty of this enhancement. Worth it in my opinion.



回答3:

Convert your code with sed to solve PHP7 Uniform Variable Syntax issues

You just need to find all instances of $$, ::$ and ->$ and add braces where needed:

find . -name "*.php"  -exec grep -l '\->\$' {} \;|while read f; do
  echo $f;  grep -H '\->\$' $f ; 
  # do some sed magic here to add braces
done

find . -name "*.php"  -exec grep -l '\$\$\w*\[' {} \;|while read f; do 
  echo $f;  grep -H '\$\$\w*\[' $f ;
  # do some sed magic here to add braces
done

find . -name "*.php"  -exec grep -l '::\$' {} \;|while read f; do 
  echo $f;  grep -H '::\$' $f ;
  # do some sed magic here to add braces
done

Maybe someone knows the right sed syntax, so I will add it here.

I already commented out the pointer instances & before objects with:

find . -name "*.php"  -exec grep -l new {} \;|while read f; do
  sed -i -e 's~=\s*\&\s*new~= /*\&*/ new~g' "$f">/tmp/a;
done

I added comments instead of just removing the &, to be able to solve errors, that might occur later.



回答4:

Step 1, Finding problem expression

It's too hard to be found by using grep and some magic regex, because it has lots of factors.

Phan https://github.com/etsy/phan can solve it, with the option -b, --backward-compatibility it check for potential PHP 5 -> PHP 7 BC issues. It may be little heavy because it looks for common issues.

If you want a tool that without configuration, you may try

PHP-Migration https://github.com/monque/PHP-Migration.

It will parse code twice, first as PHP 7, second PHP 5. Then compare nodes in AST result, if any difference found, means that different behavior will happen when it running between PHP 5/7, so that you can navigate to the line which reported by this tool and check the code manually.

$ cat demo.php
<?php

$$foo['bar']['baz'];
$foo->$bar['baz'];
$foo->$bar['baz']();
Foo::$bar['baz']();

$ php bin/phpmig demo.php

File: demo.php
--------------------------------------------------------------------------------
Found 4 spot(s), 4 identified
--------------------------------------------------------------------------------
    3 | WARNING    | * | 7.0.0 | Different behavior between PHP 5/7
    4 | WARNING    | * | 7.0.0 | Different behavior between PHP 5/7
    5 | WARNING    | * | 7.0.0 | Different behavior between PHP 5/7
    6 | WARNING    | * | 7.0.0 | Different behavior between PHP 5/7
--------------------------------------------------------------------------------

Step 2, Fix it

Now you have a list contains file and line number that you should fix.

1, Fix manually, test carefully recommended

2, Generate code by PHP-Parser PrettyPrinter

<?php

use PhpParser\ParserFactory;
use PhpParser\PrettyPrinter;

// Parse in PHP 5 mode
$parser = (new ParserFactory())->create(ParserFactory::ONLY_PHP5);
$printer = new PrettyPrinter\Standard();

$code = <<<'EOC'
<?php

$$foo['bar']['baz'];
$foo->$bar['baz'];
$foo->$bar['baz']();
Foo::$bar['baz']();
EOC;

$stmts = $parser->parse($code);
$code = $printer->prettyPrintFile($stmts);
echo $code."\n";