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.xmlvendor/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

Tags: codeigniter, codeigniter4, psalm