1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PHPAdvent Calendar 2024

Day 6

PHPStanのカスタムルールを作ってみる

Last updated at Posted at 2024-12-08

初めに

PHPの静的解析ツールでおなじみのPHPStanではユーザ独自のカスタムルールを作成することができます。単純なルールであればサクッと作ることができておすすめなので、今回はPHPStanの簡単なカスタムルールを作ってみようと思います。

説明で使用するPHPStanのバージョンは1.12系です。

(前提知識)PHPStanのルール

PHPStanで定義されている全てのルールは以下のRuleというInterfaceを継承して作る必要があります。

PHPStanが提供しているRuleインターフェース
<?php declare(strict_types = 1);

namespace PHPStan\Rules;

use PhpParser\Node;
use PHPStan\Analyser\Scope;

interface Rule
{

	/**
	 * @phpstan-return class-string<TNodeType>
	 */
	public function getNodeType(): string;

	/**
	 * @phpstan-param TNodeType $node
	 * @return (string|RuleError)[] errors
	 */
	public function processNode(Node $node, Scope $scope): array;

}

インターフェースにはgetNodeType()processNode()の二つのメソッドがあり、それぞれ解析対象解析ルールの役割を担います。

メソッド 役割
getNodeType() 解析する対象を定める
processNode() 解析するルールを定める

getNodeType()

getNodeType()には解析対象を定義します。ここで返すのはPHP-ParserのNodeを継承したクラスの名前です。PHPStanではNodeを継承したクラスが細かく用意されており、様々な場所を解析対象に定めることができます(このディレクトリにあります)。

processNode()

processNode()には解析するルールを定義します。返り値のarrayにはエラー時に出力する文章を定義します。エラーがない場合は空配列で問題ありません。
引数の$ode$scopeには解析対象の情報が詰められており、これを元に解析でエラーを出すかどうかを決めることができます。

実際に作ってみる

というわけで作ってみましょう。
今回は練習として、「Hogeクラスのメソッドはgetpostで始まらないといけない」というルールを実装してみました(いかにもプロジェクト固有なルールで恐縮ですが、、、)。

Ruleを作る

まずは以下のようなRuleを実装してみました。

MethodNameRule.php
<?php

declare(strict_types=1);

namespace Test\rule;

use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;

class MethodNameRule implements Rule
{
    public function getNodeType(): string
    {
        return Node\Stmt\ClassMethod::class;
    }

    /**
     * @param Node\Stmt\ClassMethod $node
     * @param Scope $scope
     * @return string[]
     */
    public function processNode(Node $node, Scope $scope): array
    {
        $className = $scope->getClassReflection()?->getName();

        // 対象クラスを A のみに制限
        // NOTE: $classNameはnamespaceもついてくるのでstr_ends_withで比較
        if (!str_ends_with($className, '\\Hoge')) {
            return [];
        }

        $methodName = $node->name->toString();

        // 'get' または 'post' で始まっているかをチェック
        if (!preg_match('/^(get|post)/', $methodName)) {
            return [sprintf(
                'メソッドの名前はgetかpostで始めてください! %s::%s',
                $className,
                $methodName
            )];
        }

        return [];
    }
}

getNodeType()ではNode\Stmt\ClassMethodのクラス名をreturnすることで、解析対象をクラスのメソッドだけに絞っています。
また、processNode()では$scopeが持っているクラス名の情報を利用しHogeメソッドのみが対象となるように指定します。その後、$nodeが持っているメソッド名を正規表現にかけ解析エラーかどうかを判断します。

設定ファイル(.neon)ファイルに記載

作成したルールはphpstan.neonファイル以下のように記載すればOKです。

phpstan.neon
rules:
    - Test\rule\MethodNameRule

ちなみに、ルール自体にコンストラクタなどを定義している場合、rulesではなくServiceなどに定義が必要です。詳しくは公式ドキュメントをご覧ください。

実行してみる

それでは実行してみます。
テスト用に以下のクラスを作成してみました。

<?php
namespace Test\analyseObject;

class Hoge
{
    public function getTest(): void {}

    public function postTest(): void {}

    public function putTest(): void {}
}

解析をかけた結果がこちらです。ちゃんと意図したところでだけエラーがでるのが確認できました!

./vendor/bin/phpstan analyse
Note: Using configuration file C:\path\to\project\phpstan.neon.
 9/9 [============================] 100%
 
 ------ ------------------------------------------------------------------------------------------ 
  Line   analyseObject\Hoge.php
 ------ ------------------------------------------------------------------------------------------
  :10    メソッドの名前はgetかpostで始めてください! Test\analyseObject\Hoge::putTest
         ✏️  analyseObject\Hoge.php
 ------ ------------------------------------------------------------------------------------------

終わりに

今回はPHPStanのカスタムルールを作成してみました。
chatGPTにいろいろ聞きながら作ってみましたが、サクッと作れていい感じです。
プロジェクト固有のルールはどこにでもあると思うので、レビューでよく指摘する内容などはルールに起こすのもアリだと思います。

ここまで読んでいただきありがとうございました!

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?