Slide 1

Slide 1 text

PHPのバージョンアップ時に ASTが変わらない変更の見抜き方 2026年11月14日 PHPカンファレンス新潟2026 サイボウズ株式会社 松尾篤 1

Slide 2

Slide 2 text

はじめに 今回の話題 • PHPのバージョンアップ時にASTが変わらない変更は、移行ガイドを読んだ段階で目星をつけられる • PHPのバージョンアップ時にASTの差分がないことを確認することで大幅にテスト工数を削減できたケースが あったことをPHPカンファレンス名古屋2025とPHPerKaigi 2026で紹介 • 今回はその見抜き方について移行ガイドの「推奨されなくなる機能」をどう読めばよいかという形で紹介 2

Slide 3

Slide 3 text

はじめに 自己紹介 • 松尾篤 • PHP使用歴:約21年 • PHPを使った開発歴は18年 • 2023年4月にGaroon開発チームに加入 • これまでの登壇実績(AST関連) • PHPカンファレンス名古屋2025「PHPのバージョンアップ時にも役立ったAST」 • PHPerKaigi 2026「PHPのバージョンアップ時にも役立ったAST(2026年版)」 • PHPerKaigi mini #3「PHP 8.5へのバージョンアップ時にも役立つAST(抽象構文木)」 3

Slide 4

Slide 4 text

PHPのリリースサイクルと バージョンアップの必要性 4

Slide 5

Slide 5 text

PHPのリリースサイクルとバージョンアップの必要性 PHPのリリースサイクル • 年に1回新しいバージョンが公開 • 2年間のアクティブサポート期間 • 不具合およびセキュリティ問題が修正される • アクティブサポート期間の後に2年間のセキュリティサポート期間 • 重要なセキュリティ問題のみが修正される 5

Slide 6

Slide 6 text

PHPのリリースサイクルとバージョンアップの必要性 もしもバージョンアップを行わなかったら • PHPに起因するセキュリティ脆弱性への対応が困難になる • 例:2026年10月のPHP2Shellでは、PHP 8.2以降向けには修正バージョンが公開されたが、サポート終了済みの PHP 7.4、PHP 8.0、PHP 8.1は例外的にパッチが提供されただけで修正版のリリースはなかった • 深刻なセキュリティ脆弱性がPHPに見つかって製品に重大な影響を与えてしまうような問題が出てきた場 合にユーザーが危険な状況に陥ってしまう • サービスやビジネスの継続に影響を与えてしまう恐れも 6

Slide 7

Slide 7 text

PHPのリリースサイクルとバージョンアップの必要性 セキュリティのためにもバージョンアップは必要 • セキュリティに対するリスクマネジメントは重要な経営課題の1つ • 最新のセキュリティ更新をすぐに取り込める状態を保つことはとても重要 • プロダクトを長期的に安全に運用するためにはPHPを継続的に更新していく必要がある • 一方で、歴史の長いプロダクトほどPHPのバージョンアップは簡単ではない • 特に大規模プロダクトでは影響範囲の調査や修正、テストなどに大きな工数がかかってしまう 7

Slide 8

Slide 8 text

PHPのバージョンアップとAST 8

Slide 9

Slide 9 text

PHPのバージョンアップとAST PHPのバージョンアップ • PHPをバージョンアップする際には多くの変更点の調査やテストが必要になる • 一方で、構文の見た目は変わっていてもAST(抽象構文木)が変化せず、プログラム構造としては同等と みなせる変更が存在する場合もある 9

Slide 10

Slide 10 text

PHPのバージョンアップとAST AST • 抽象構文木、Abstract Syntax Treeの略 • プログラムの本質的な構造を木構造で表したもので、ソースコードの解析や変換に活用できる • 括弧、空白、コメントなど表記上の情報は含まれず、論理的な構造の比較に適している • ただし、/** */のPHPDocコメントはASTに含まれる ソースコード

Slide 11

Slide 11 text

PHPのバージョンアップとAST 同じソースコードであればASTも同じになる 1回目 ソースコード AST

Slide 12

Slide 12 text

PHPのバージョンアップとAST 同じソースコードであればASTも同じになる 1回目 ソースコード AST 2回目

Slide 13

Slide 13 text

PHPのバージョンアップとAST PHPをバージョンアップする前に • 新バージョンへの移行ガイドを必ず確認 • 移行ガイドに記載されている内容に基づいて製品への影響がないか調査する • 移行ガイドには新機能だけでなく「下位互換性のない変更点」や「推奨されなくなる機能」も記載されている • PHP 8.5の移行ガイド : https://www.php.net/manual/ja/migration85.php 13

Slide 14

Slide 14 text

PHPのバージョンアップ時に ASTが変わらない変更の見抜き方 14

Slide 15

Slide 15 text

PHPのバージョンアップ時にASTが変わらない変更の見抜き方 PHPのバージョンアップ時にASTが変わらない変更の見抜き方 • 移行ガイドにある「推奨されなくなる機能」をまず見る • 書き換えてもASTが変わらない項目が含まれることがある • 「推奨されなくなりました」および(推奨されなくなったものの代わりに)「使ってください」という記 述のある置き換え案内において表記や区切り記号が変わる場合にはASTが変わらない変更の候補 • 関数名・引数・制御フロー・構文形式が変わる場合にはASTは変わる • 最終的にはバージョンアップ前のPHPでASTを出力して確認 • 「Aの代わりにB」なら何でも対象ではない 15

Slide 16

Slide 16 text

PHPのバージョンアップ時にASTが変わらない変更の見抜き方 「何が変わるか」で仕分ける 変わる要素 例 ASTで変わるもの 表記・区切り記号 ${var} → {$var} (integer) → (int) case 1; → case 1: 何も変わらない(候補) 関数名・API名・識別子名 is_integer() → is_int() 名前ノードが持つ値 引数・入れ子構造 オブジェクト → get_object_vars() ?array → array ノードの数や入れ子 制御フロー finallyからのreturnをやめる ノードの位置 16

Slide 17

Slide 17 text

PHPのバージョンアップ時にASTが変わらない変更の見抜き方 なぜ表記や区切り記号ならASTが変わらないのか • ソース → 字句解析 → 構文解析(AST) → コンパイル(opcode) → 実行 • 表記や区切り記号は、ASTになる前に消える • キャストの別名は字句解析で正規形のトークンに畳まれ、caseの;は構文解析時に消える • 関数名・引数・制御フローは、ASTのノードとして残る • ただし、新バージョンは、非推奨を検知するためにASTにフラグ(属性)を付けることがある(8.2の"${var}"、 8.5のcase 1;) • そのため、バージョンアップ前のPHPを使うとASTが同じであることを確認しやすい • 書き換えていなければ、PHPをバージョンアップした時点でフラグが付いてASTが変わる • 書き換えてあれば、PHPをバージョンアップしてもASTは変わらない • ASTが同じでも、新バージョンで警告が消えたことは別途確認する必要あり 17

Slide 18

Slide 18 text

PHPのバージョンアップ時に ASTが役立った事例 18

Slide 19

Slide 19 text

PHPのバージョンアップ時にASTが役立った事例 PHP 8.1からPHP 8.2にバージョンアップした際にASTが役立った事例 • PHP 8.2にバージョンアップする作業の一部でASTの差分がないことを確認することで大幅にテスト工数 を削減できたケースがあったことをPHPカンファレンス名古屋2025とPHPerKaigi 2026で紹介 • https://fortee.jp/phpcon-nagoya-2025/proposal/143e3ef0-b235-423e-b3e8-8f6338aab8a8 • https://fortee.jp/phperkaigi-2026/proposal/6c422c5c-8789-4464-9ccd-5e351a0f3bbc 19

Slide 20

Slide 20 text

PHPのバージョンアップ時にASTが役立った事例 きっかけはPHP勉強会@東京 • PHP 8.2にバージョンアップする際の「"${var}" / "${expr}" 形式の、文字列への値の埋め込み」につ いてはASTを活用できることに気づく • 2023年11月に開催された「第158回 PHP勉強会@東京」で金城さんのASTに関する発表を聞いたことが きっかけ 金城さんのASTに関する発表スライドより 20

Slide 21

Slide 21 text

PHPのバージョンアップ時にASTが役立った事例 PHP 8.1からPHP 8.2にバージョンアップした際にASTが役立った事例 • 当該事例では、PHP 8.2で非推奨になった「“${var}” / “${expr}” 形式の、文字列への値の埋め込み」 について434箇所変更すべき箇所が見つかった • 実装としてはエディターで${を{$に一括置換するだけだったが、品質保証やテスト戦略をどうするべき かという課題に直面 • 変更前の例:$message = "${var}"; • 変更後の例:$message = "{$var}"; • バージョンアップ前のPHP 8.1+php-astで、ast_dump()の出力から求めたSHA-256ハッシュ値が変更 前後で変わらないことを確認することで434箇所個別に手動テストを行う必要はないと最終的に判断 21

Slide 22

Slide 22 text

PHPのバージョンアップ時にASTが役立った事例 "${var}"から"{$var}"に置き換えてもASTが変わらない(PHP 8.1の場合) "${var}" ソースコード AST "{$var}"

Slide 23

Slide 23 text

PHPのバージョンアップ時にASTが役立った事例 "${var}"のままだとASTが変わる(PHP 8.2の場合) "${var}" ソースコード AST

Slide 24

Slide 24 text

PHPのバージョンアップ時にASTが役立った事例 "${var}"から"{$var}"に置き換えるとASTが元に戻る(PHP 8.2の場合) "{$var}" ソースコード AST ハッシュ値(SHA-256)

Slide 25

Slide 25 text

PHPのバージョンアップ時にASTが役立った事例 ASTが同じならなぜテスト工数を削減できるのか • ASTが同じ=プログラムの構造が同じ(違うのは表記だけ) • PHP 7以降、コンパイラはASTを入力としてopcodeを生成する • ASTに関するPHP RFC : https://wiki.php.net/rfc /abstract_syntax_tree • 同じバージョンのPHPおよび同じコンパイル時設定でASTが同じなら、生成されるopcodeも同じ • opcodeが同じなら実行時の挙動も変わらないと判断できる 25

Slide 26

Slide 26 text

PHPでASTを取得および そのハッシュ値を比較する方法 26

Slide 27

Slide 27 text

PHPでASTを取得およびそのハッシュ値を比較する方法 ASTを取得およびそのハッシュ値を比較する手順の流れ • バージョンアップ前のPHPでphp-astをインストール • php.iniに「extension=ast.so」(Windowsの場合には「extension=php_ast.dll」)を追加 • php-astのutil.phpを使用してASTを取得 • hash()を使って取得したASTのハッシュ値(SHA-256)を生成 • 変更前と変更後のハッシュ値を比較して一致するか確認 27

Slide 28

Slide 28 text

PHPでASTを取得およびそのハッシュ値を比較する方法 php-astのインストール • https://github.com/nikic/php-ast?tab=readme-ov-file#installation を参照 • macOSやLinuxの場合 • 「pecl install ast」を実行してphp.iniに「extension=ast.so」を追加 • 今ならPIE(PHP Installer for Extensions)を使ってインストールする方法も • もしくはコンパイルしてからインストールする方法もある(phpize → ./configure → make → sudo make install) • Windowsの場合 • php-astのDLLファイルをダウンロードしてextディレクトリに配置してからphp.iniに「extension=php_ast.dll」を追加 28

Slide 29

Slide 29 text

PHPでASTを取得およびそのハッシュ値を比較する方法 php-astのutil.phpで定義されているast_dump()でASTを出力できる • https://github.com/nikic/php-ast?tab=readme-ov-file#example 29

Slide 30

Slide 30 text

PHPでASTを取得およびそのハッシュ値を比較する方法 php-astでASTを取得するサンプル • php-astを使用してASTを取得 • php-astのutil.phpで定義されているast_dump()を使用 • 第2引数にAST_DUMP_EXCLUDE_DOC_COMMENTを指定すればPHPDocコメントが除外されて出力される 30

Slide 31

Slide 31 text

PHPでASTを取得およびそのハッシュ値を比較する方法 php-astでASTのハッシュ値を取得するサンプル • hash()を使って取得したASTのハッシュ値(SHA-256)を生成 • 引数にファイルパスを指定して実行 • 実行例 : php dump-ast-hash-by-php-ast.php /path/to/file.php 31

Slide 32

Slide 32 text

PHPでASTを取得およびそのハッシュ値を比較する方法 ASTを比べるときの注意 • バージョンアップ前のPHPで実行する • PHP 8.1からPHP 8.2にバージョンアップする場合ならPHP 8.1で実行する • 比較に使うPHPやphp-astのバージョンを揃える • PHPDocコメントを無視する場合にはast_dump()の第2引数に AST_DUMP_EXCLUDE_DOC_COMMENTを指定する • バージョンアップ後、書き換えたコードをもう一度ダンプし、バージョンアップ前の結果と一致すること を確認する(PHPやphp-astだけでなくAST versionも揃える) 32

Slide 33

Slide 33 text

PHPでASTを取得およびそのハッシュ値を比較する方法 PHP 8.2で"${var}"のASTにフラグが付く仕組み • PHP 8.2の構文解析器は"${var}"のZEND_AST_VARにフラグを付け(zend_language_parser.y)、コ ンパイラがそのフラグを見て非推奨の警告を出す(zend_compile.c) • php-astはこのフラグ情報を出力するので、"${var}"のままだとASTの差分として見える • 書き換えた"{$var}"にはフラグが付かないので、PHP 8.1と同じASTに戻る 33

Slide 34

Slide 34 text

PHPでASTを取得およびそのハッシュ値を比較する方法 PHP 8.5での実例1:正規化されていない型名でのキャスト • (integer)は(int)に、(boolean)は(bool)に、(double)は(float)に、(binary)は(string)に変える • ソースコードの表記は変わっているが、ASTとしては同じ構造になることが期待できそうに見える • 実際にphp-astを使って確認してみたところ、対象となるキャスト表記を正規化したうえで変更前後の ASTを比較し、ASTに差分が出ないことを確認できた PHP 8.4でphp-astを使用 PHP 8.5でphp-astを使用 ソースコード変更前のハッシュ値 (例) 4693d072bbfa4fb1832507cd6bea6 183e79a5f1bf42bddd1f47df8b7e5b 454b6 4693d072bbfa4fb1832507cd6bea6 183e79a5f1bf42bddd1f47df8b7e5b 454b6 ソースコード変更後のハッシュ値 (例) 4693d072bbfa4fb1832507cd6bea6 183e79a5f1bf42bddd1f47df8b7e5b 454b6 4693d072bbfa4fb1832507cd6bea6 183e79a5f1bf42bddd1f47df8b7e5b 454b6 34

Slide 35

Slide 35 text

PHPでASTを取得およびそのハッシュ値を比較する方法 PHP 8.5での実例2: switch文のcaseをセミコロンで終了させる書き方 • case文の末尾にある ;(セミコロン)を :(コロン)に変更した場合 • テキストとしては;が:に変わるため差分があるが、構文上はどちらもswitch文のcaseラベルを表す • バージョンアップ前のPHP 8.4環境でphp-astを使って確認してみたところ、セミコロンをコロンに置き 換えてもASTは同じであり、switch文の分岐構造そのものは変わっていないことを確認できた PHP 8.4でphp-astを使用 PHP 8.5でphp-astを使用 ソースコード変更前のハッシュ値 (例) 617f67046f0e38cb19be702c4946a a21dbf9fc5817f2450305d12a59c15 b243b4ae61bdd445c1d8a51afe8cfe 0861fe3d161db5c4ae9e0363560cc 9a0ae a5ee4 ソースコード変更後のハッシュ値 (例) 617f67046f0e38cb19be702c4946a 617f67046f0e38cb19be702c4946a b243b4ae61bdd445c1d8a51afe8cfe b243b4ae61bdd445c1d8a51afe8cfe 9a0ae 9a0ae 35

Slide 36

Slide 36 text

PHPでASTを取得およびそのハッシュ値を比較する方法 余談:ASTが違ってもZend Engineが最後に呼ぶ処理は同じことがある • 同じ「別名の正規化」でも結果が違う • キャストの別名は字句解析で解決され、関数の別名は実行時に解決される • is_integer()とis_int()はASTでは別の名前だが、Zend Engineの関数テーブルでは同一のCハンドラーを 指す • PHPをバージョンアップする際にASTが違っていてもテスト工数を削減できるケースがあるかもしれない 36

Slide 37

Slide 37 text

まとめ 37

Slide 38

Slide 38 text

まとめ PHPのバージョンアップ時にASTが変わらない変更の見抜き方 • 移行ガイドにある「推奨されなくなる機能」をまず見る • 書き換えてもASTが変わらない項目が含まれることがある • 「推奨されなくなりました」および(推奨されなくなったものの代わりに)「使ってください」という記 述のある置き換え案内において表記や区切り記号が変わる場合にはASTが変わらない変更の候補 • 関数名・引数・制御フロー・構文形式が変わる場合にはASTは変わる • 候補はバージョンアップ前のPHPでASTを出力して確かめる • 「Aの代わりにB」なら何でも対象ではない • 最終的にはopcodeを比較するのが確実 38

Slide 39

Slide 39 text

まとめ まとめ • ASTが変わらない変更を見分ければ、PHPの移行ガイドはテスト工数削減のヒントに変わる • 移行ガイドの「推奨されなくなる機能」には、ASTが変わらない変更が含まれることがある • ASTが変わらないのは表記や区切り記号を変える変更 • 関数名が異なるとASTは変わる • ASTはバージョンアップ前のPHPで確認すると比較しやすい • 警告が消えたことは別途確認する必要あり • ASTが違ってもZend Engineが最後に呼ぶ処理は同じことがある話は来年どこかのカンファレンスで 39

Slide 40

Slide 40 text

40