Do You PHP はてブロ

Do You PHPはてなからはてブロに移動しました

PHP 7 Compatibility Checker(php7cc)を触ってみた

珍しくPHPネタを連投してます;-)
β2がリリースされてだいぶ実体が見えてきたPHP7ですが、PHP5.xから多くの改良、機能の追加・非推奨化・廃止、また仕様変更が含まれています。既存のコードがPHP7で動作するかどうかを

で、既存のコードがPHP7で動作するかどうかをチェックするツールがGitHubで公開されていましたので、ちょっと触ってみました。


php7cc is a command line tool designed to make migration from PHP 5.3-5.6 to PHP 7 easier. It searches for potentially troublesome statements in existing code and generates reports containing file names, line numbers and short problem descriptions. It does not automatically fix code to work with the new PHP version.

Introductionにもあるとおり、あくまでlintのような"コードチェッカー"であり、コードを修正してくれるツールではありません。また、PHP7はまだ開発中なのですべての変更点を洗い出せるわけではないことに注意です。

環境

  • CentOS6.4
  • PHP5.6.11

インストール

README.mdのInstallationに書かれているとおり、composer経由でインストールします。

$ PATH=/usr/local/lib/php56/bin:$PATH php ./composer.phar create-project sstalle/php7cc php7cc --stability=dev
        :
Writing lock file
Generating autoload files
Do you want to remove the existing VCS (.git, .svn..) history? [Y,n]? n
$ 

インストールの最後にVCSの履歴を削除するかどうかを聞かれます。ここで"no"としておけば、今後git pullで更新できるようになるようです。

チェック対象のPHPスクリプト

PHP7.0.0β2に含まれているUPGRADINGファイルに記載されているコードを使ってチェック用のスクリプト(UPGRADING.php)を作ってみました。

<?php
/**
 * @see https://raw.githubusercontent.com/php/php-src/php-7.0.0beta2/UPGRADING
 */

/**
 * Indirect variable, property and method references are now interpreted with
 * left-to-right semantics. Some examples:
 */
$$foo['bar']['baz']; // interpreted as ($$foo)['bar']['baz']
$foo->$bar['baz'];   // interpreted as ($foo->$bar)['baz']
$foo->$bar['baz'](); // interpreted as ($foo->$bar)['baz']()
Foo::$bar['baz']();  // interpreted as (Foo::$bar)['baz']()


/**
 * The global keyword now only accepts simple variables. Instead of
 */
global $$foo->bar;


/**
 * Parentheses around variables or function calls no longer have any influence
 * on behavior. For example the following code, where the result of a function
 * call is passed to a by-reference function
 */
function getArray() { return [1, 2, 3]; }

$last = array_pop(getArray());
// Strict Standards: Only variables should be passed by reference
$last = array_pop((getArray()));
// Strict Standards: Only variables should be passed by reference


/**
 * Array elements or object properties that are automatically created during
 * by-reference assignments will now result in a different order.
 */
$array = [];
$array["a"] =& $array["b"];
$array["b"] = 1;
var_dump($array);
    :

全体はGistにありますので参照してみてください。

実行してみる

インストール後に生成されるphp7ccディレクトリ直下にbinディレクトリがあり、その中にあるphp7cc.phpファイルを実行します。引数はPHPスクリプトを含むパスもしくはファイル名を指定します。パスを指定した場合は、第2引数に拡張子(カンマ区切りで複数指定可能)できます。

$ PATH=/usr/local/lib/php56/bin:$PATH php ./php7cc/bin/php7cc.php ./UPGRADING.php

File: ./UPGRADING.php
Line 7. Indirect variable, property or method access: /**
 * Indirect variable, property and method references are now interpreted with
 * left-to-right semantics. Some examples:
 */
${$foo['bar']['baz']};
Line 8. Indirect variable, property or method access: // interpreted as ($$foo)['bar']['baz']
$foo->{$bar['baz']};
Line 9. Indirect variable, property or method access: // interpreted as ($foo->$bar)['baz']
$foo->{$bar['baz']}();
Line 10. Indirect variable, property or method access: // interpreted as ($foo->$bar)['baz']()
Foo::$bar['baz']();
Line 16. Complex variable without curly braces in global keyword: // interpreted as (Foo::$bar)['baz']()
/**
 * The global keyword now only accepts simple variables. Instead of
 */
global ${$foo->bar};
Line 37. Possible array element creation during by-reference assignment: $array['a'] =& $array['b'];
Line 53. Empty list assignment: /**
 * Empty list() assignments are no longer allowed. As such all of the following
 * are invalid:
 */
list();
Line 54. Empty list assignment: list(, , );
Line 55. Empty list assignment: list();
Line 99. Possible adding to array on the last iteration of a by-reference foreach loop: $array[1] = 1;
Line 109. Duplicate function parameter name "unused": public function foo($a, $b, $unused, $unused)
{
}
Line 122. Function argument(s) returned by "func_get_arg" might have been modified: func_get_arg(0);
Line 142. Invalid octal literal 0781: 7;
Line 172. String containing number in hexadecimal notation: '0x123';
Line 173. String containing number in hexadecimal notation: '0x123';
Line 174. String containing number in hexadecimal notation: '0xe';
Line 174. String containing number in hexadecimal notation: '0x1';
Line 176. String containing number in hexadecimal notation: '0x1';
Line 184. String containing number in hexadecimal notation: '0xffff';
Line 197. Unicode codepoint escaping "\u{xyz}" in a string: '\\u{xyz}';

Checked 1 file(s) in 0.048255 second(s)
$ 

用意したPHPスクリプトのすべてではないですが、結構いい感じにチェックできているんじゃないでしょうか?

ちょっとだけ深堀り

PHPスクリプトの解析にはPHP Parserが使われています。php7cc側では、PhpParser\NodeVisitorAbstractクラスを継承したAbstractVisitorクラスが用意されており、それを継承した各Visitorクラスで「何をチェックするか」が実装されています。
たとえば、func_get_arg/func_get_args関数の戻り値の変更に対するチェッカー(php7cc/src/NodeVisitor/FuncGetArgsVisitor.php)は次のような感じです。

<?php

namespace Sstalle\php7cc\NodeVisitor;

use PhpParser\Node;
use Sstalle\php7cc\Helper\NodeHelper;

class FuncGetArgsVisitor extends AbstractVisitor
{

    public function enterNode(Node $node)
    {
        if (!NodeHelper::isFunctionCallByStaticName($node)) {
            return;
        }

        /** @var Node\Expr\FuncCall $node */
        $functionName = $node->name->toString();
        if ($functionName == 'func_get_arg' || $functionName == 'func_get_args') {
            $this->addContextMessage(
                sprintf('Function argument(s) returned by "%s" might have been modified', $functionName),
                $node
            );
        }
    }
}

まとめ

今年中には正式リリースされる感じのPHP7ですが、こういうツールを上手く使って楽に移行をしたいですね(希望的観測)。。。(汗)
というか、サードパーティ製の拡張モジュールが対応してくれるのか不安だなぁ。。。