PHPStanのデバッグを行う方法
この記事は カオナビ Advent Calendar 2023 19日目です。
はじめに
はいさい。@shimabox です。
気づいたら12月、12月なのに暑かったり寒かったりよくわからない感じですがみなさん元気でしょうか。
今回、何を書こうかなぁと考えて「せや、PHPStanがどうやって解析をしているのか解き明かしたろ」と思いデバッグを開始したのですけど、これはじっくりと時間をかけないと無理なやつでした。。
このままではアカンので、せめてデバッグを行えるまでの手順を記事にしたいと思います。
この記事に真のタイトルをつけるのならば、PHPStanの解析を試みたが挫けた話です。
おことわり
今回の作業はすべてVSCodeで行います。
あと、docker compose
は使えるものとしています。
PHPStanとは
まずはPHPStanとはなんぞやという話ですが、PHPStanはPHPの静的解析ツールです。
Getting Started | PHPStan
コードを実行せずに
- 関数に渡すパラメータの数が適切か?
- 未定義のものにアクセスしようとしていないか?
- 関数に渡す値が関数のパラメータの型宣言と一致するか?
- PHPDocの内容と関数の戻り値は同じか?
- etc...
などのエラーを見つけてくれます。
端的に言うと型チェックをしてくれる優れものだと認識してくれればいいのかなと思います。
PHPStanのソースはどこ?
これ、知っている人は知っているかもしれませんが、じゃあデバッグするぞとPHPStanのソースを見ようと思って phpstan/phpstan を覗いても phpstan.phar しかありません。ファーーー。
※ phpstanを実行するときの、vendor/bin/phpunit
はこのpharファイルを読み込んでいるだけです
じゃあ、どこを見ればいいんや?という話ですが、phpstanのソースは phpstan/phpstan-src に大本があります。そして phpstan-src/.github/workflows/phar.yml のジョブで phpstan.phar が作成されています(たぶん)。
なので、PHPStanのデバッグはphpstan/phpstan-src
に対して行っていけばよさそうです。
拡張機能を入れておく
デバッグの前に以下2つの拡張機能をVSCodeに入れておきます。
- Dev Containers
- PHP Debug
- 後述する、Dev container を使う場合は必要ないです
デバッグする
ではどのようにデバッグするかですが、今回は2つの方法を試してみたのでそれぞれ書いていきます。簡単に言うと、Dev container を使う場合と使わない場合です。
Dev container を使う
おそらく、これが一番シンプル?だと思います。
phpstan/phpstan-src
には、phpstan-src/.devcontainer があるのでこのコンテナーを利用してデバッグします。
ただ、自分が試した限り少し修正しないとうまく動かなかったのでそのあたりの対応方法も合わせて説明していきます。
1. phpstan-srcをclone
git clone https://github.com/phpstan/phpstan-src.git
cd phpstan-src
2. .devcontainer/Dockerfileを修正
.devcontainer/Dockerfile
に ENV COMPOSER_ROOT_VERSION 1.11.x-dev
を追記します。
こうしないと、ビルド時のcomposer installに自分は失敗しました。
〜
COPY /usr/bin/composer /usr/local/bin/composer
# 以下を追加
# https://github.com/phpstan/phpstan-src インストール用
# @see https://github.com/phpstan/phpstan-src/tree/1.11.x?tab=readme-ov-file#installation
ENV COMPOSER_ROOT_VERSION 1.11.x-dev
3. .vscode/launch.jsonの作成
.vscode/launch.json
を作成しておきます。portは9000
です。
{
"version": "0.2.0",
"configurations": [
{
"name": "Listen for Xdebug",
"type": "php",
"request": "launch",
"port": 9000
}
]
}
4. VSCodeで開く
Cmd + o
なり、File → Open なりでcloneしたソース(phpstan-src)を選択します。
5. Reopen in Container する
Reopen in Container
と右下に表示されると思うので選択します。
6. ビルドがはじまる
ビルドがはじまるのでしばし待ちます。
7. コンテナーに接続される
ビルドが終わると、コンテナーに接続された状態になります。
8. 適当にデバッグ用ファイルを作る
適当にデバッグ用ファイルを作ります。今回は、app/User.php
を作ってみました。
<?php declare(strict_types=1);
class User
{
/**
* @param int $id
* @param string $name
*/
public function __construct(
private readonly int $id,
private readonly string $name,
) {}
/**
* @return array
*/
public function toArray(): array
{
return [
'id' => $this->id,
'name' => $this->name,
];
}
}
9. デバッグを起動する
拡張機能のPHP Debugを選択して、Listen for Xdebug
を起動しておきます。
※ PHP DebugはDevContainerの設定で勝手にインストールされているはずです
10. デバッグを実行する
src/Command/AnalyseCommand.php
の execute()
に適当にブレークポイントをはって、phpstanを実行します。コマンドは以下の通りになります。bin/phpstan が実行ファイルになります。
$ bin/phpstan analyze -l 6 app/User.php
実行すると、このようにブレイクされるはずです。
なお、ブレイクを抜けて最後まで実行した結果はこうです。
vscode ➜ /workspaces/phpstan-src (1.11.x) $ bin/phpstan analyze -l 6 app/User.php
Note: Using configuration file /workspaces/phpstan-src/phpstan.neon.dist.
1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
------ -------------------------------------------------------------------------------------------
Line User.php
------ -------------------------------------------------------------------------------------------
:17 Method User::toArray() return type has no value type specified in iterable type array.
💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
------ -------------------------------------------------------------------------------------------
[ERROR] Found 1 error
こんな感じで、phpstanを実行しつつデバッグできます。
これでブレークポイントをはりまくってデバッグできますね!
なお、この環境は
$ php -v
PHP 8.1.26 (cli) (built: Nov 27 2023 23:09:30) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.26, Copyright (c) Zend Technologies
with Xdebug v3.3.0, Copyright (c) 2002-2023, by Derick Rethans
となります。
Dev container を使わない
なんで Dev container を使わないかというと、これには特に意味はなく自分が最初に試していた方法がこれだったからです。
(phpstan-srcでDev containerを使えることに気づいていなかったし、Dev containerに詳しくなかったというのもあります)
方法としては単純にPHP Debugを使うだけです。
サンプル
というわけで、自分で試していたということもありサンプルがあります。
ではこちらのリポジトリを使ってデバッグしてみたいと思います。
ちなみに、こちらの環境は
$ php -v
PHP 8.3.0 (cli) (built: Dec 16 2023 02:34:44) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.3.0, Copyright (c) Zend Technologies
with Xdebug v3.3.1, Copyright (c) 2002-2023, by Derick Rethans
となります。PHP8.3!!!
1. 準備する
cloneして、makeコマンド(make init
)を叩けば試せる環境が整います。
git clone https://github.com/shimabox/sample-debug-phpstan.git
cd sample-debug-phpstan
make init
このコマンドを実行すると、sample-debug-phpstan/debug/
以下に、phpstan/phpstan-srcがインストールされます。
2. コンテナーに入る
make app
でコンテナーに入っておきます。
make app
3. デバッグを起動する
拡張機能のPHP Debugを選択して、Listen for Xdebug
を起動しておきます。
4. デバッグを実行する
src/Command/AnalyseCommand.php
の execute()
に適当にブレークポイントをはって、phpstanを実行します。コマンドは以下の通りになります。
# debug以下に、phpstan/phpstan-srcがインストールされている
$ debug/phpstan-src/bin/phpstan analyze -l 6 src/User.php
実行すると、このようにブレイクされるはずです。
最後まで実行した結果はこうです。
root@9976e620471b:/var/www/html# debug/phpstan-src/bin/phpstan analyze -l 6 src/User.php
Note: Using configuration file /var/www/html/phpstan.neon.dist.
1/1 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
------ --------------------------------------------------------------------------------------------
Line User.php
------ --------------------------------------------------------------------------------------------
23 Method App\User::toArray() return type has no value type specified in iterable type array.
💡 See: https://phpstan.org/blog/solving-phpstan-no-value-type-specified-in-iterable-type
------ --------------------------------------------------------------------------------------------
[ERROR] Found 1 error
こちらの方法でも、こんな感じでphpstanを実行しつつデバッグできます。
これでブレークポイントをはりまくって(略
自分的には、自由に試せる感じがするのでこっちのほうが好みです。はい。
おわりに
というわけで、PHPStanをデバッグする方法を書いてみました。
最初けっこうつまづいたので誰かの役に立てたら嬉しい限りですし、他にもこんなやり方あるよ!!とか教えてくれるとさらに嬉しいです(実はもっと簡単に出来るんじゃないかと内心ヒヤヒヤしています)。
本当はこれでデバッグしまくってPHPStanがどのように解析しているのかをバシッと知りたかったのですが、とりあえず環境は出来たので時間を見つけてanalyzeしていきたいと思います。来年に持ち越しだ!!!
P.S. Dev Container なにげに使ったことがなかったので最初よくわからなかったけど、使いこなせるようになったら便利なんだろうなーとちょっと思いました
Discussion