CodeIgniter DevKit で Psalm を使い静的解析する
この記事は CodeIgniter Advent Calendar 2022 - Qiita の22日目です。まだ、空きがありますので、興味のある方は気軽に参加してください。
「CodeIgniter DevKit で PHPStan を使い静的解析する」 の続きです。
Psalmとは?
PsalmはPHPの静的解析ツールです。
Psalmの設定
DevKitをインストールすれば、Psalmはインストール済みです。
DevKitのインストールについては 「CodeIgniter DevKit で php-cs-fixer を使いコーディングスタイルを修正する」 を参照してください。
vendor/codeigniter4/devkit/src/Template/psalm.xml
と
vendor/codeigniter4/devkit/src/Template/psalm_autoload.php
をプロジェクトルートにコピーします。
そして、コピーした psalm.xml
をプロジェクトに合わせて調整します。
<?xml version="1.0"?>
<psalm
errorLevel="7"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
autoloader="psalm_autoload.php"
cacheDirectory="build/psalm/"
>
<projectFiles>
<directory name="app/" />
<directory name="tests/" />
<ignoreFiles>
<directory name="vendor" />
<directory name="app/Views" />
</ignoreFiles>
</projectFiles>
</psalm>
ここでは、特に変更せずそのままです。
なお、パスの設定が正しいかは必ず確認してください。DevKitのテンプレートのパスはCodeIgniter4のアプリのパスに設定されているため、 ライブラリの場合は少し修正が必要です。
composer.json の設定
実行するためのコマンドを composer.json
に追加します。
--- a/composer.json
+++ b/composer.json
@@ -29,7 +29,8 @@
"cs-fix": "php-cs-fixer fix --ansi --verbose --diff --using-cache=yes",
"style": "@cs-fix",
"analyze": [
- "bash -c \"XDEBUG_MODE=off phpstan analyse\""
+ "bash -c \"XDEBUG_MODE=off phpstan analyse\"",
+ "psalm"
],
"sa": "@analyze"
},
これで、composer sa
でPHPStanの後にPsalmが実行されます。
Psalmの実行
それではPsalmを実行してみましょう。
$ composer sa
...
> psalm
Target PHP version: 7.2 (inferred from composer.json) Extensions enabled: (unsupported extensions: )
Scanning files...
Deprecation: Psalm stubs for ext-redis loaded using legacy way. Instead, please declare ext-redis as dependency in composer.json or use <enableExtensions> directive in Psalm config.
Analyzing files...
░░░░░░░░░░░E░░░░░░E░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
ERROR: ParseError - app/Config/Events.php:37:25 - Syntax error, unexpected T_STRING, expecting T_PAAMAYIM_NEKUDOTAYIM on line 37 (see https://psalm.dev/173)
ob_start(static fn ($buffer) => $buffer);
ERROR: UndefinedFunction - app/Config/Events.php:37:25 - Function Config\fn does not exist (see https://psalm.dev/021)
ob_start(static fn ($buffer) => $buffer);
ERROR: ParseError - app/Config/Events.php:37:38 - Syntax error, unexpected T_DOUBLE_ARROW on line 37 (see https://psalm.dev/173)
ob_start(static fn ($buffer) => $buffer);
ERROR: MissingFile - app/Config/Routes.php:60:5 - Cannot find file /.../codeigniter4login/app/Config/testing/Routes.php to include (see https://psalm.dev/107)
require APPPATH . 'Config/' . ENVIRONMENT . '/Routes.php';
------------------------------
4 errors found
------------------------------
77 other issues found.
You can display them with --show-info=true
------------------------------
Psalm can automatically fix 9 of these issues.
Run Psalm again with
--alter --issues=MissingReturnType,MissingParamType --dry-run
to see what it can fix.
------------------------------
Checks took 10.81 seconds and used 308.240MB of memory
Psalm was able to infer types for 94.4882% of the codebase
Script psalm handling the analyze event returned with error code 2
Script @analyze was called via sa
エラーが報告されました。
Target PHP version: 7.2 (inferred from composer.json)
対象としているPHPのバージョンが古すぎました。
composer.json
の設定を修正します。
--- a/composer.json
+++ b/composer.json
@@ -5,7 +5,7 @@
"homepage": "https://codeigniter.com",
"license": "MIT",
"require": {
- "php": ">=7.2",
+ "php": ">=7.4",
"codeigniter4/framework": "^4"
},
"require-dev": {
再度、Psalmを実行します。
$ vendor/bin/psalm
Target PHP version: 7.4 (inferred from composer.json) Extensions enabled: (unsupported extensions: )
Scanning files...
Analyzing files...
ERROR: UndefinedFunction - app/Config/Events.php:37:25 - Function Config\fn does not exist (see https://psalm.dev/021)
ob_start(static fn ($buffer) => $buffer);
ERROR: MissingFile - app/Config/Routes.php:60:5 - Cannot find file /.../codeigniter4login/app/Config/testing/Routes.php to include (see https://psalm.dev/107)
require APPPATH . 'Config/' . ENVIRONMENT . '/Routes.php';
------------------------------
2 errors found
------------------------------
77 other issues found.
You can display them with --show-info=true
------------------------------
Checks took 0.05 seconds and used 7.175MB of memory
No files analyzed
Psalm was able to infer types for 94.4882% of the codebase
エラーが報告されました。
Psalm設定の調整
ERROR: UndefinedFunction - app/Config/Events.php:37:25 - Function Config\fn does not exist (see https://psalm.dev/021) ob_start(static fn ($buffer) => $buffer);
このエラーは正直よくわかりません。Psalmのバグでしょうか?
fn
は関数ではありませんから。
ERROR: MissingFile - app/Config/Routes.php:60:5 - Cannot find file /.../codeigniter4login/app/Config/testing/Routes.php to include (see https://psalm.dev/107) require APPPATH . 'Config/' . ENVIRONMENT . '/Routes.php';
こちらはファイルが存在しないというエラーです。このファイルは存在しないので、その通りです。
どちらも対処が難しいので、エラーを抑制します。
ベースラインの作成
ベースラインを作成しましょう。
$ vendor/bin/psalm --set-baseline=psalm-baseline.xml
Target PHP version: 7.4 (inferred from composer.json) Extensions enabled: (unsupported extensions: )
Scanning files...
Deprecation: Psalm stubs for ext-redis loaded using legacy way. Instead, please declare ext-redis as dependency in composer.json or use <enableExtensions> directive in Psalm config.
Analyzing files...
░░░░░░░░░░░E░░░░░░E░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
Writing error baseline to file...
Baseline saved to psalm-baseline.xml.
ERROR: UndefinedFunction - app/Config/Events.php:37:25 - Function Config\fn does not exist (see https://psalm.dev/021)
ob_start(static fn ($buffer) => $buffer);
ERROR: MissingFile - app/Config/Routes.php:60:5 - Cannot find file /.../codeigniter4login/app/Config/testing/Routes.php to include (see https://psalm.dev/107)
require APPPATH . 'Config/' . ENVIRONMENT . '/Routes.php';
------------------------------
2 errors found
------------------------------
76 other issues found.
You can display them with --show-info=true
------------------------------
Psalm can automatically fix 9 of these issues.
Run Psalm again with
--alter --issues=MissingReturnType,MissingParamType --dry-run
to see what it can fix.
------------------------------
Checks took 6.62 seconds and used 384.126MB of memory
Psalm was able to infer types for 94.4882% of the codebase
これで、以下の psalm-baseline.xml
が作成されました。
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="5.4.0@62db5d4f6a7ae0a20f7cc5a4952d730272fc0863">
<file src="app/Config/Events.php">
<UndefinedFunction occurrences="1">
<code>fn ($buffer)</code>
</UndefinedFunction>
</file>
<file src="app/Config/Routes.php">
<MissingFile occurrences="1">
<code>require APPPATH . 'Config/' . ENVIRONMENT . '/Routes.php'</code>
</MissingFile>
</file>
</files>
psalm.xml
にも以下が自動的に追加され、ベースラインが使われるようになりました。
--- a/psalm.xml
+++ b/psalm.xml
@@ -7,6 +7,7 @@
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
autoloader="psalm_autoload.php"
cacheDirectory="build/psalm/"
+ errorBaseline="psalm-baseline.xml"
>
<projectFiles>
<directory name="app/" />
再度、実行してエラーが出ないことを確認しましょう。
$ vendor/bin/psalm
Target PHP version: 7.4 (inferred from composer.json)
Scanning files...
Analyzing files...
------------------------------
No errors found!
------------------------------
74 other issues found.
You can display them with --show-info=true
------------------------------
Checks took 0.05 seconds and used 7.023MB of memory
No files analyzed
Psalm was able to infer types for 94.4990% of the codebase
設定が完了し、問題がなければ、Gitでcommitしてください。
まとめ
- CodeIgniter DevKit はCodeIgniterのライブラリとプロジェクトのための開発用のツールキットです。
- DevKitにはPsalmが含まれており、簡単に静的解析を実行できます。
この記事は CodeIgniter Advent Calendar 2022 - Qiita の22日目です。まだ、空きがありますので、興味のある方は気軽に参加してください。
関連
参考
Date: 2022/12/22