5

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?

Marten Koetsier
  • 2,769
  • 2
  • 20
  • 33
  • 2
    Is it really that cumbersome? You just need to find all instances of `$$` and `->$` and add braces where needed. If you have more than a few instances that are more complex than `$$foo`, something's wrong with your code anyway. Consider it an opportunity to refactor. :) – elixenide Dec 04 '15 at 16:01
  • P.S. If you have good tests in place, then you don't even need to do that. Just see which tests fail under 5.x and pass under 7. Then fix your code. – elixenide Dec 04 '15 at 16:03
  • 1
    Thanks Ed Cottrell, do you think that `$$`, `->$` and `::$` are the only ones to have a look for? Which others do I miss? (I can handle the false positives, those are not too many). – Marten Koetsier Dec 04 '15 at 16:11
  • [Phan](https://github.com/etsy/phan) will supposedly identify UVS issues for you, together with various other potential migration hiccups – Mark Baker Dec 04 '15 at 16:32
  • 2
    @MartenKoetsier that should do it. Note that `::$` is going to give you a lot of false positives, relative to the others. Also, FYI, if you use PHPStorm (or want to try out a demo), PHPStorm 10 does a lot of automatic PHP 7 compatibility checks. (I have no affiliation; I just love the product.) – elixenide Dec 04 '15 at 16:37
  • So in the end, did you create regular expressions to solve this? – rubo77 Jul 22 '16 at 07:40
  • @rubo77 in the end, I found the instances using grep and updated the code manually. – Marten Koetsier Jul 22 '16 at 10:07

4 Answers4

5

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.

Andrea
  • 17,448
  • 4
  • 35
  • 62
  • 1
    Unfortunately, Phan requires PHP 7+, which I have not easily available (yet!). However, I have looked in their source for compatibility checks and found references only to `$$var[]` and `$foo->$bar['baz'];` (both in src/Phan/Analyze/BreadthFirstVisitor.php in function `visitDim`). These refer to tests 0047 and 0051. This leads me to expect that searches for `$$` and `->$` (@Ed Cottrell) will indeed do the trick! – Marten Koetsier Dec 04 '15 at 21:40
  • I have also searched for alternative means for detection, but with no positive result. Perhaps some light-weight parser becomes available in the future. In the mean time, your answer at least helped solving my issue, so for now, it's accepted. Thanks. – Marten Koetsier Dec 04 '15 at 21:49
0

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.

Flosculus
  • 6,660
  • 3
  • 15
  • 39
  • Thanks for that link. Reading about this helped me understand it better. The 'why' question is at least answered! In the end, I may indeed end up with refactoring by hand. The main issue with that was finding instances, which is probably sufficiently solved with the tests from Phan. – Marten Koetsier Dec 04 '15 at 21:45
0

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.

rubo77
  • 15,234
  • 23
  • 111
  • 195
0

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";
monque
  • 133
  • 1
  • 7