Posted at

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


背景・概要

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ハッシュ値のチェックができるようになりました。