LoginSignup
9

More than 5 years have passed since last update.

モノタロウさんがすごいなと思ったのでphp-astを試してみた

Posted at

背景・概要

20万行超のコードベースをテストせずにリファクタリングリリースした話 を読んで、面白いなあ、これPHPだとどうなってるんだろう・・・と思って調べてみました。

php-ast というのがあるらしい

PHP AST 徹底解説 のような発表があったので、とりあえずそれを使ってみることにした。

インストール

$ git clone https://github.com/nikic/php-ast
$ cd php-ast
$ phpize
Configuring for:
PHP Api Version:         20170718
Zend Module Api No:      20170718
Zend Extension Api No:   320170718

ここで自分の環境(phpbrewでインストールしたての7.2.11, CentOS7.5)では下記の設定がされておらずエラーになったのでしておいた。

export LANGUAGE="ja_JP:ja"
export LC_ALL="ja_JP.UTF-8"
export LANG="ja_JP.UTF-8"

あとはいつもどおりに...(phpbrewでのインストールなのでインストール先は一旦.phpbrew以下に)

$ ./configure
(省略)
$ make
(省略)
$ sudo make install
Installing shared extensions:     /home/vagrant/.phpbrew/php/php-7.2.11/lib/php/extensions/no-debug-non-zts-20170718/

php.ini ファイルを探して

$ php --ini
Configuration File (php.ini) Path: /home/vagrant/.phpbrew/php/php-7.2.11/etc
Loaded Configuration File:         /home/vagrant/.phpbrew/php/php-7.2.11/etc/php.ini
Scan for additional .ini files in: /home/vagrant/.phpbrew/php/php-7.2.11/var/db
Additional .ini files parsed:      (none)

ast.so を追加。

php.ini
;extension=soap
;extension=sockets
;extension=sqlite3
;extension=tidy
;extension=xmlrpc
;extension=xsl
extension=ast.so

;;;;;;;;;;;;;;;;;;;
; Module Settings ;
;;;;;;;;;;;;;;;;;;;

とりあえず試す

test.php

<?php

$ast = ast\parse_code('<?php echo "ok"; ?>', $version=50);
var_dump($ast);
object(ast\Node)#1 (4) {
  ["kind"]=>
  int(132)
  ["flags"]=>
  int(0)
  ["lineno"]=>
  int(1)
  ["children"]=>
  array(1) {
    [0]=>
    object(ast\Node)#2 (4) {
      ["kind"]=>
      int(282)
      ["flags"]=>
      int(0)
      ["lineno"]=>
      int(1)
      ["children"]=>
      array(1) {
        ["expr"]=>
        string(2) "ok"
      }
    }
  }
}

クラスファイルに対して適用してみる+ast_dumpを試す

ast_dump を試す。

まずはサンプルにあるように

<?php
// clone したときのフォルダがあればそれを使う
require_once 'php-ast/util.php';

$ast = ast\parse_code('<?php echo "ok"; ?>', $version=50);
echo ast_dump($ast) . PHP_EOL;

こんな結果に。この結果をmd5とかしておけばいいのですね。

AST_STMT_LIST
    0: AST_ECHO
        expr: "ok"

クラスファイルに対して適用

http://php.net/manual/ja/language.oop5.basic.php のクラスをやってみよう。

SimpleClass.php
<?php
class SimpleClass
{
    // プロパティの宣言
    public $var = 'a default value';

    // メソッドの宣言
    public function displayVar() {
        echo $this->var;
    }
}
?>

こちらのプログラムに流してみる

<?php
require_once 'php-ast/util.php';

$file = $argv[1];
$ast = ast\parse_file($file, $version=50);
echo ast_dump($ast) . PHP_EOL;
AST_STMT_LIST
    0: AST_CLASS
        flags: 0
        name: "SimpleClass"
        docComment: null
        extends: null
        implements: null
        stmts: AST_STMT_LIST
            0: AST_PROP_DECL
                flags: MODIFIER_PUBLIC (256)
                0: AST_PROP_ELEM
                    name: "var"
                    default: "a default value"
                    docComment: null
            1: AST_METHOD
                flags: MODIFIER_PUBLIC (256)
                name: "displayVar"
                docComment: null
                params: AST_PARAM_LIST
                uses: null
                stmts: AST_STMT_LIST
                    0: AST_ECHO
                        expr: AST_PROP
                            expr: AST_VAR
                                name: "this"
                            prop: "var"
                returnType: null
                __declId: 0
        __declId: 1

クラスに変更を加えて確認してみる

  • クラスにPHPDocのパーツを追加
  • 行コメントの削除
  • {} の位置を変更
  • returnを追加
  • 引数を追加
SimpleClass.php
<?php
/**
 * SimpleClass
 */
class SimpleClass {
    public $var = 'a default value';

    public function displayVar(int $n) {
        echo $this->var;
        return;
    }
}

結果はこのように。

AST_STMT_LIST
    0: AST_CLASS
        flags: 0
        name: "SimpleClass"
        docComment: "/**
         * SimpleClass
         */"
        extends: null
        implements: null
        stmts: AST_STMT_LIST
            0: AST_PROP_DECL
                flags: MODIFIER_PUBLIC (256)
                0: AST_PROP_ELEM
                    name: "var"
                    default: "a default value"
                    docComment: null
            1: AST_METHOD
                flags: MODIFIER_PUBLIC (256)
                name: "displayVar"
                docComment: null
                params: AST_PARAM_LIST
                uses: null
                stmts: AST_STMT_LIST
                    0: AST_ECHO
                        expr: AST_PROP
                            expr: AST_VAR
                                name: "this"
                            prop: "var"
                    1: AST_RETURN
                        expr: null
                returnType: null
                __declId: 0
        __declId: 1
  • doCcomment が null だったのが内容が追加された
  • AST_PARAM, AST_RETURN のあたりが変更になった

ことがわかります。

docComment以外のコメント、動作に変更のないような修正は、元の記事のように同一とみなして良いといえそうです。

まとめ

ast_check.php
<?php
require_once 'php-ast/util.php';

$file = $argv[1];
$tmp = array_shift($argv);

$files = $argv;
foreach ($files as $file) {
    $ast = ast\parse_file($file, $version=50);
    printf("%s: %s\n", md5(ast_dump($ast)), $file);
}

このようなプログラムを使って、

$ php ast_check.php A.php B.php C.php
d436cc6a6ab4ad36a6150d6f11e4bbcd: A.php
d436cc6a6ab4ad36a6150d6f11e4bbcd: B.php
d6d0e62c871c073620aa13a739fa2638: C.php

md5ハッシュ値のチェックができるようになりました。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9