さわって慣れるPHP Parser
先日PHPカンファレンス福岡2023で登壇する機会をいただきました。
Rectorと目指す 負債をためないシステム開発~はじめの一歩~
以下の様なフィードバックをいただいた(ありがとうございます!)ので補足説明したいと思います。
- 発表内容は概ね理解できた気がするが手を動かせる自信がない。
- Identifierノードがわかりにくい。
前者については、まずメンタルモデル(発表参照)を作り、ノードを理解することから始めるのが有効と考えていて、ノード[1]を理解するにはPHP Parserを動かして結果を観察するのが有効なのではないかと考えています。というわけで、Identifierノードを観察するというケーススタディで具体的な手順を書いてみようと思います。
まずIdentifierの言葉から確認すると、発音はaidéntifàiər(アイデンティファイア的な)で、意味は識別子です。
ではPHP Parserをインストールします。
$ composer require --dev nikic/php-parser
解析対象のコード(TheWorld.php
)をしたためます。(PHPとしての適切さは度外視し、ノードを観察するためだけのコードです。)
<?php
class TheWorld {}
function theWorld() {}
解析します。
$ vendor/bin/php-parse TheWorld.php
====> File TheWorld.php:
==> Node dump:
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: TheWorld
)
extends: null
implements: array(
)
stmts: array(
)
)
1: Stmt_Function(
attrGroups: array(
)
byRef: false
name: Identifier(
name: theWorld
)
params: array(
)
returnType: null
stmts: array(
)
)
)
色々でてますがサラリと流してIdentifier
にのみに注意を払います。
Stmt_Class
とStmt_Function
の中にIdentifier
がいることがわかります。
前者にはクラス名が入っていて、後者には関数名が入っています。
Identifier(
name: TheWorld
)
Identifier(
name: theWorld
)
ひとまず何者なのか目視確認できましたが、仕様の裏どりもしたいと思います。
上記の解析結果では何のオブジェクトなのかがわかりにくいので--var-dump
モードでも解析してみます。
$ vendor/bin/php-parse --var-dump TheWorld.php
====> File TheWorld.php:
==> var_dump():
array(2) {
[0]=>
object(PhpParser\Node\Stmt\Class_)#1202 (8) {
["flags"]=>
int(0)
["extends"]=>
NULL
["implements"]=>
array(0) {
}
["name"]=>
object(PhpParser\Node\Identifier)#1201 (2) {
["name"]=>
string(8) "TheWorld"
["attributes":protected]=>
array(4) {
["startLine"]=>
int(2)
["startFilePos"]=>
int(12)
["endLine"]=>
int(2)
["endFilePos"]=>
int(19)
}
}
["stmts"]=>
array(0) {
}
["attrGroups"]=>
array(0) {
}
["namespacedName"]=>
NULL
["attributes":protected]=>
array(4) {
["startLine"]=>
int(2)
["startFilePos"]=>
int(6)
["endLine"]=>
int(2)
["endFilePos"]=>
int(22)
}
}
[1]=>
object(PhpParser\Node\Stmt\Function_)#1204 (8) {
["byRef"]=>
bool(false)
["name"]=>
object(PhpParser\Node\Identifier)#1203 (2) {
["name"]=>
string(8) "theWorld"
["attributes":protected]=>
array(4) {
["startLine"]=>
int(3)
["startFilePos"]=>
int(33)
["endLine"]=>
int(3)
["endFilePos"]=>
int(40)
}
}
["params"]=>
array(0) {
}
["returnType"]=>
NULL
["stmts"]=>
array(0) {
}
["attrGroups"]=>
array(0) {
}
["namespacedName"]=>
NULL
["attributes":protected]=>
array(4) {
["startLine"]=>
int(3)
["startFilePos"]=>
int(24)
["endLine"]=>
int(3)
["endFilePos"]=>
int(45)
}
}
}
情報量が増えてわけわかりにくくなりましたが、バシバシ無視しましょう。以下に注目します。
PhpParser\Node\Stmt\Class_
PhpParser\Node\Stmt\Function_
PhpParser\Node\Identifier
IDEでジャンプするか、PHPStanのドキュメントを参照するなどしてこれらの定義を見に行きます。
ClassLike
を継承しているのでそちらも確認しに行きます。
Identifier|null
型のname
というプロパティを持っていることが分かります。
Identifier
も確認します。
Represents a non-namespaced name.
名前空間をつけない名前を表すものであるということがわかりました。
Function_
も同じ要領で確認します。
こんな感じで簡単なコードを書いて解析して観察することでノードの理解をこつこつ深めております。そうしてまとめた資料がこちらです。
Rectorはコードをリファクタします。
説明に用いた事例は呼び出すメソッド名をリファクタするというものでした。
PhpParser\Node\Expr\MethodCall
もメソッド名を表すのにIdentifier
をもっており、Identifier
を取り換えることでメソッド名を書き換えることが可能という話でした。
Discussion