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