🔍

PHPStanを使ってPHPコードの静的解析をしてみた

2024/04/04に公開

はじめに

PHPStanはPHPの静的解析ツールです。ソースコードが潜在的に持っている問題のうち、コードを実際に実行しなくても発見可能な問題を指摘してくれます。

今回はPHPStanを実際に実行してみて、使い方を簡単に確認してみました。

実行時の様子

ここにどこからともなく出てきたPHPのソースコードがあります。ちょっと読みづらくて申し訳ないのですが、色々と怪しそうな点があるかと思います。

<?php
function sum(int $a, int $b): int {
    return $a + $b;
}

echo sum('1', 1);

/**
 * @param int|float $a
 * @return int|float
 */
function increment($a) {
    return $a + 1;
}

echo sum(increment(1), 1);

class Person {
    public string $name = "John";
    public int $age = 30;

    public function getNameAndAge() {
        return [$this->name, $this->age];
    }
}

function getNextYearAge(?Person $person): string {
    $age = $person->age;
    return $age + 1;
}

$john = New Person("John", 30);
echo $john->getCity();

function processMixed(mixed $m): mixed {
    return $m + 1;
}

echo processMixed(1);

echo $undefined;
exit(0);
echo 'dead code';

こちらのソースコードをPHPStanにかけると、以下のような指摘をしてくれます。

------ ------------------------------------------------------------------------------------------- 
  :6     Parameter #1 $a of function sum expects int, string given.                                 
  :16    Parameter #1 $a of function sum expects int, float|int given.                              
  :22    Method Person::getNameAndAge() has no return type specified.                               
  :28    Cannot access property $age on Person|null.                                                
  :29    Function getNextYearAge() should return string but returns int.                            
  :32    Class Person does not have a constructor and must be instantiated without any parameters.  
  :33    Call to an undefined method Person::getCity().                                             
  :39    Parameter #1 (mixed) of echo cannot be converted to string.                                
  :41    Variable $undefined might not be defined.                                                  
  :43    Unreachable statement - code above always terminates.                                      
 ------ ------------------------------------------------------------------------------------------- 
 
 [ERROR] Found 10 errors

今回は10個のエラーを指摘されました。このように、ソースコードの異常や型宣言及びPHPDoc形式のコメントの内容を加味した不整合を把握することができます。(上記各指摘とソースの指摘箇所の対応についてはこの後のルールレベルのところで記載しています。)

次に、PHPStanのインストール方法や使い方について紹介します。

インストールの方法

インストールの方法はいくつかある様ですが、公式のドキュメントでは以下のようにComposerを使いインストールすることが最初に紹介されています。

composer require --dev phpstan/phpstan

今回はこちらのComposerを使う方法でインストールしました。

実行方法

ComposerでPHPStanをインストールした場合、vender/binディレクトリにPHPStanの実行ファイルがあります。以下のようなコマンドで解析を実行できます。

vendor/bin/phpstan analyse [options] [<paths>...]

optionsの部分に指定できるオプション例は後述します。<paths>...の部分は以下の様にディレクトリやファイルを指定できます。

vendor/bin/phpstan analyse src tests
vendor/bin/phpstan analyse main.php

ルールレベル

PHPStanにはルールレベルという設定項目があります。ルールレベルは0から9まであり、0が最も緩く、9が最も厳密になります。デフォルトは0です。

なお、冒頭に紹介したPHPコードはルールレベル0から9のうち各レベルの指摘がそれぞれ1個ずつされるようなコードになっています。冒頭のPHPコードについて、各ルールレベルで指摘を受ける箇所をコメントで追記したバージョンが以下になります。

<?php
function sum(int $a, int $b): int {
    return $a + $b;
}

echo sum('1', 1); // 引数の型がおかしい => Rule Level 5

/**
 * @param int|float $a
 * @return int|float
 */
function increment($a) {
    return $a + 1;
}

echo sum(increment(1), 1); // 交差型が部分的におかしい => Rule Level 7

class Person {
    public string $name = "John";
    public int $age = 30;

    public function getNameAndAge() { // 型ヒントがない => Rule Level 6
        return [$this->name, $this->age];
    }
}

function getNextYearAge(?Person $person): string {
    $age = $person->age; // null許容型のプロパティにアクセスしている => Rule Level 8
    return $age + 1; // 戻り値の型がおかしい => Rule Level 3
}

$john = New Person("John", 30); // 引数の数がおかしい => Rule Level 0
echo $john->getCity(); // 未知の関数 => Rule Level 2

function processMixed(mixed $m): mixed {
    return $m + 1;
}

echo processMixed(1); // mixedをmixedではないものに変換している => Rule Level 9

echo $undefined; // 変数が定義されていない可能性がある => Rule Level 1
exit(0);
echo 'dead code'; // 到達しないコード => Rule Level 4

おわりに

今回はPHPStanの解析結果と各ルールレベルで指摘される内容例を確認してみました。いい感じにツールを活用して、機械的に検出できる問題は早期に解消できるようにしていきたいです。

VideogLabo

Discussion