PHPコードの品質を劇的に向上させる静的解析ライブラリ4選！

# PHPコードの品質を劇的に向上させる静的解析ライブラリ4選！

この記事では、内部品質を高めるために役立つ4つのツールを紹介したいと思います。
どれも慣れれば小一時間で導入できるものです。

## 1. phpstan : 型に特化した静的解析ツール

https://phpstan.org/

静的解析ツールとは、コードを実行する前に、コードの品質や潜在的なエラーを検出するためのツールのことです。
コード内の潜在的なエラーやタイポを自動的に検出してくれるツールです。

### できること

- 静的コード解析による型検査
- PHPdocとの乖離がないかの検索
- 構文、文法の解析
- エラー/警告の無視した実装を検索

また、phpstanは設定ファイルを使って柔軟にカスタマイズできるため、プロジェクトに合わせたルールを設定できます。
これにより、不必要なエラーや警告が発生しないようにすることもできます。

マジックメソッドなどが多く存在するLaravelでは[Larastan](https://github.com/nunomaduro/larastan)という、
予めカスタムルールが設定された、phpStanのラッパーも存在しています。


### 追加の厳格ルールに関して

https://github.com/phpstan/phpstan-strict-rules

さらに厳格にプロジェクトを運用したい場合は追加のstrict-rulesも存在します。
詳細は上記のgithubに記載がありますが、幾つか抜粋すると

- if式内でbool値以外を使わないようにする `NG: if([0]) {`
- `in_array`, `array_search`などのstrictオプションがある関数の`true`指定の必須化
- 緩やかな比較である`empty`関数の使用不可

などさらに設定できます（既存のプロダクトだとエラー出すぎて導入が大変かもしれない）

### 実行例

下記のような、レビューで見落としそうなコードに対しても、PHPStanは的確にレビューをしてくれます。

<details><summary>サンプルコード</summary>

```php
<?php declare(strict_types = 1);

class Order
{
    public function __construct(private array $items)
    {
    }

    public function total()
    {
        return array_reduce($this->items, function($total, $item) {
            return $total + $item->price();
        }, 0);
    }
}

class Item
{
    public function __construct(private float $price)
    {
    }

    public function price(): int
    {
        return $this->price;
    }
}

$items = [
    new Item(100),
    new Item(200),
    new Item(300),
];

$order = new Order($items);

echo 'Total: ' . $order->total();
```

```bash
Line	Error
5	Property Order::$items type has no value type specified in iterable type array.
See: Solving PHPStan error "No value type specified in iterable type"
7	Method Order::__construct() has parameter $items with no value type specified in iterable type array.
See: Solving PHPStan error "No value type specified in iterable type"
12	Method Order::total() has no return type specified.
29	Method Item::price() should return int but returns float.
```

</details>


# 2. php-cs-fixer : フォーマッター

https://cs.symfony.com/

PHP Coding Standards Fixer (PHP CS Fixer) ツールは、標準に準拠するようにコードを修正します。
具体的には、コードのインデント、スペースの配置、改行の挿入などのルールを適用して、コードを整形します。

php-cs-fixerは、静的解析ライブラリではないと思いますが、厳密な型宣言を使用することが強制できる`declare(strict_types=1);`の有無を調べるのにも利用できるので、この記事では静的解析ライブラリとして扱わせてください。

フォーマットとしては、`PSR2`、`PSR12`はもちろん、`PhpCsFixer`、`Symfony`がデフォルトで定めているルールセットを利用できます。

php-cs-fixerを導入することで、PSR2/12に準拠していない状態のコードが存在していない状態でレビューを開始することができます。

より詳しくルールに知りたい場合は下記のサイトが便利です。
https://mlocati.github.io/php-cs-fixer-configurator/

dry-runを上手く活用することで、PSR12に準拠していないものや、`declare(strict_types=1);`が記載されていないコードを防ぐことも可能です。

#### `declare(strict_types=1);`に関する補足

`declare(strict_types=1);` これをファイルの先頭に記述することで、強い型付けを実行時に有効にすることができます。
強い型付けを有効にする理由としては、意図しない不具合の原因となる、暗黙の型変換を抑制することができます。

```php
// declare(strict_types=1); // コメントアウトするとTypeErrorとなる

function dump(int $value): void  
{  
 var_dump($value);  
}  

dump('13.37');
```

### できること

・PSR2/12に準拠しているコードであることの保証（可読性の向上）
・`declare(strict_types=1);`がついていないコードの検査

### 実行例

下記のようにフォーマットと、`declare(strict_types=1);`の追加を行えます。


<details><summary>サンプルコード</summary>

#### フォーマット前
```php
<?php

class Person{
  private $firstName;
  private $lastName;
  function __construct($firstName, $lastName){
  $this->firstName = $firstName;
    $this->lastName = $lastName;
    }
    public function getFullName(){
        return $this->firstName." ".$this->lastName;
  }
}
echo "My name is ".(new Person("John","Doe"))->getFullName();
```

#### フォーマット後
```php
<?php

declare(strict_types=1);

class a
{
    private $firstName;
    private $lastName;

    public function __construct($firstName, $lastName)
    {
        $this->firstName = $firstName;
        $this->lastName = $lastName;
    }

    public function getFullName()
    {
        return $this->firstName.' '.$this->lastName;
    }
}

echo 'My name is '.(new Person('John', 'Doe'))->getFullName();

```
</details>



## 3. Deptrac : 依存関係に特化した静的解析ツール

https://qossmic.github.io/deptrac/

Deptracは、PHPアプリケーションの依存関係を管理するためのツールです。
PHPアプリケーションの依存関係を静的に解析し、アプリケーションのアーキテクチャに対して定義されたルールに従って依存関係を制限することができます。

### できること

- 依存関係の可視化
- 依存関係の制約
- アーキテクチャの保持

特定のレイヤー間の依存関係を制限するルールを定義しておくことで、意図しない依存関係が導入されるのを防ぐことができます。
たとえばシンプルなMVCアーキテクチャを採用していた場合、ModelがController,Serivceに依存していないことを検査できあます。

MVCなどのシンプルなアーキテクチャでは恩恵は少ないですが、クリーンアーキテクチャのような、クラスの数と種類が膨大になると、
効果を発揮すると思います。

## 4. PHP Mess Detector : コードの設計を中心とした静的解析ツール

https://phpmd.org/

こちらもPHPコードの静的解析ツールの1つです。
phpstanと内容が被っている部分も多いのですが、こちらは設計を中心とした機能が多い印象です。

メソッドの長さ、ネストの深さ、制御構造の複雑さなどを評価し、維持が困難な複雑さを検出することができます。

Mess Detectorを導入することで、保守が困難なメソッド、クラスの定義が防がれるので、保守する側としてはとても有り難いです。

### できること

ルールはここに説明しきれないぐらいありますが、特に役立ちそうなのを抜粋すると以下のような物があります。

- コードの複雑さの検出
  - サイクロマティック複雑度(循環的複雑度)に上限を設定、NPath値も可
  - public関数、クラスの依存数に上限を設定
  - 依存しているオブジェクト数に上限を設ける
- コードの長さの検出
  - クラス・メソッドの最大行数に上限を設定
- 命名
  - 変数名、メソッド名、クラス名の長さに下限と上限を設定

現在のプロダクトだと循環的複雑度を10→5に変更して、Static関数呼び出し禁止だけ無効にして運用しています。

### 実行例

下記のような複雑度が高いサンプルがあった場合、以下のような出力が出ます。

<details><summary>サンプルコード</summary>

```php
<?php

class ComplexClass {
    public function complexMethod($param1, $param2) {
        if ($param1 > 10 && $param2 < 5) {
            for ($i = 0; $i < 10; $i++) {
                if ($i % 2 === 0) {
                    echo 'Even number: ' . $i . PHP_EOL;
                } else {
                    echo 'Odd number: ' . $i . PHP_EOL;
                }
            }
        } elseif ($param1 <= 10) {
            switch ($param2) {
                case 1:
                    echo 'Param2 is 1.' . PHP_EOL;
                    break;
                case 2:
                    echo 'Param2 is 2.' . PHP_EOL;
                    break;
                default:
                    echo 'Param2 is not 1 or 2.' . PHP_EOL;
            }
        } else {
            while ($param2 > 0) {
                $param2--;
                echo 'Countdown: ' . $param2 . PHP_EOL;
            }
        }
    }
}
```

```bash
complex.php:4   CyclomaticComplexity  The method complexMethod() has a Cyclomatic Complexity of 9. The configured cyclomatic complexity threshold is 5.
complex.php:9   ElseExpression        The method complexMethod uses an else expression. Else clauses are basically not necessary and you can simplify the code by not using them.
complex.php:24  ElseExpression        The method complexMethod uses an else expression. Else clauses are basically not necessary and you can simplify the code by not using them.
```
</details>


## まとめ

いかがだったでしょうか。

既存のプロダクトに入れるには、導入にコストがかるかもしれませんが、
新規プロダクトだと導入して損はないものばかりだと思います。

このような静的解析ツールを導入することで、レビューで細々としたことを指摘して、時間を浪費することがなくなり、
より少ない時間で、より高品質なものが作れると思います。