PHP_CodeSnifferとPHP Mess Detectorで静的品質を担保する

概要

PHPプロジェクトのコード品質を機械的に担保する入門として、2つのツールを扱います。

  • PHP_CodeSniffer(phpcs / phpcbf) — コーディング規約への準拠をチェックし、一部は自動修正する
  • PHP Mess Detector(phpmd) — 複雑度・未使用変数・設計上の匂い(コードスメル)を検出する

どちらも「人間のレビューで指摘するには数が多すぎて非効率」な観点を機械に任せるためのツールです。役割が重なっているようで、実は守備範囲が違います。本記事では両者の違い・導入・設定・CIへの組み込みまでを順に説明します。

環境

  • PHP 8.3 以上
  • Composer 2.x

バージョンは例示です。手元の環境に合わせて読み替えてください。

2つのツールの違い

最初に混乱しやすい点を整理します。両者は「静的解析」という言葉でまとめられがちですが、見ている対象が異なります。

観点 PHP_CodeSniffer PHP Mess Detector
主な関心 書式・コーディング規約(見た目のルール) 設計・複雑度(構造のルール)
代表的な検出 インデント、命名、空白、PSR準拠 循環的複雑度、長すぎるメソッド、未使用引数
自動修正 あり(phpcbf) なし
設定ファイル phpcs.xml phpmd.xml(ruleset)

ざっくり言うと、phpcs は「書き方」を、phpmd は「作り方」をチェックします。両方入れて初めて「規約も守られ、かつ複雑すぎない」コードという状態を機械的に保証できます。

導入方法

プロジェクトローカルに開発依存として入れるのが基本です。グローバルインストールは環境差を生むので避けます。

composer require --dev phpcsstandards/php_codesniffer
composer require --dev phpmd/phpmd

PHP_CodeSniffer はメンテナンスが squizlabs/php_codesniffer から PHPCSStandards 組織へ移管されており、新規導入では phpcsstandards/php_codesniffer を指定します。旧 squizlabs/php_codesniffer パッケージは事実上更新停止(セキュリティ・新PHP対応のみの限定サポート)のため、これから入れるなら新パッケージに揃えておくのが安全です。現行はメジャー 4 系で、v3 から一部破壊的変更が入っています(PHP 8.5 への対応、既定の適用標準が PEAR から PSR-12 へ変更、など)。v3 の設定をそのまま持ち込む場合は公式の 4.0 アップグレードガイドで差分を確認してください。

インストール後、実行バイナリは vendor/bin/ 以下に配置されます。

vendor/bin/phpcs --version
vendor/bin/phpmd --version

PHP_CodeSniffer の使い方

基本の実行

対象ディレクトリを指定してチェックします。

vendor/bin/phpcs src/

規約違反があると、ファイル・行番号・違反内容が一覧で出力されます。

FILE: src/Service/UserService.php
--------------------------------------------------------------------
FOUND 2 ERRORS AFFECTING 2 LINES
--------------------------------------------------------------------
 12 | ERROR | [x] Line indented incorrectly; expected 4 spaces, found 2
 25 | ERROR | [ ] Method name "GetUser" is not in camel caps format
--------------------------------------------------------------------

[x] が付いている違反は自動修正できます。

自動修正(phpcbf)

vendor/bin/phpcbf src/

phpcbf はインデントや空白など機械的に直せる違反を書き換えます。命名規則のように意図が絡むものは直さないので、修正後に必ず差分を確認してください。

設定ファイル phpcs.xml

プロジェクト直下に phpcs.xml を置くと、オプションを毎回打たずに済みます。

<?xml version="1.0"?>
<ruleset name="MyProject">
    <description>プロジェクト共通のコーディング規約</description>

    <!-- チェック対象 -->
    <file>src</file>
    <file>tests</file>

    <!-- 除外 -->
    <exclude-pattern>*/vendor/*</exclude-pattern>
    <exclude-pattern>*/migrations/*</exclude-pattern>

    <!-- 適用する規約。PSR-12 をベースに -->
    <rule ref="PSR12"/>

    <!-- 個別ルールの調整例: 1行の長さ警告を緩める -->
    <rule ref="Generic.Files.LineLength">
        <properties>
            <property name="lineLimit" value="120"/>
            <property name="absoluteLineLimit" value="0"/>
        </properties>
    </rule>
</ruleset>

この状態で引数なしに vendor/bin/phpcs を実行すると、phpcs.xml が自動的に読み込まれます。

標準で使える規約(スタンダード)は次で確認できます。

vendor/bin/phpcs -i

規約選びで迷ったら、PER Coding Style を念頭に置くのがよいでしょう。PER Coding Style(PER-CS)は PHP-FIG が PSR-12 の後継として策定した「生きた」標準で、PSR-12 を拡張して match・enum・attributes といった新しい構文までカバーします(現行は PHP 8.4 対応の v3 系)。PHP-FIG 上では PSR-12 は旧扱い(stale)となり、以後の更新は PER-CS 側で行われます。

ただし phpcs 側の組み込み標準としては現状 PSR12 が提供され、v4 では既定の標準もこの PSR12 です。上記の <rule ref="PSR12"/> はそのまま使えます。PER-CS は PSR-12 を包含するため、まずは PSR12 をベースにしておき、PER の新構文ルールまで厳密に揃えたい場合は PER 準拠のルールセットを別途導入する、という進め方が現実的です。いずれにせよ、独自ルールをゼロから作るより広く使われている標準に乗る方が、チーム全体の学習コストが低く済みます。

PHP Mess Detector の使い方

基本の実行

対象、出力形式、適用するルールセットの3つを指定します。

vendor/bin/phpmd src/ text cleancode,codesize,design,naming,unusedcode
  • 第1引数: 対象パス
  • 第2引数: 出力形式(text / xml / json など)
  • 第3引数: ルールセット(カンマ区切り、またはxmlファイルのパス)

組み込みのルールセットには次のようなものがあります。

ルールセット 検出する内容
codesize 循環的複雑度、メソッド/クラスの長さ
design 結合度、深すぎるネスト、出口の多さ
naming 短すぎる/長すぎる名前
unusedcode 未使用のローカル変数・引数
cleancode static呼び出し、else回避など

出力例

src/Service/ReportService.php:42	The method generate() has a Cyclomatic Complexity of 15. The configured cyclomatic complexity threshold is 10.
src/Service/ReportService.php:88	Avoid unused local variables such as '$tmp'.

複雑度のような指摘は「このメソッドは分割を検討した方がよい」という設計上のシグナルです。phpcs とは違って自動修正はできません。人間がリファクタリングする前提のツールです。

ruleset をカスタマイズする

組み込みのしきい値が厳しすぎる/緩すぎる場合は、独自の phpmd.xml を用意します。

<?xml version="1.0"?>
<ruleset name="MyProject PHPMD rules"
         xmlns="http://pmd.sf.net/ruleset/1.0.0">
    <description>複雑度としきい値の調整</description>

    <!-- 既存ルールセットを取り込む -->
    <rule ref="rulesets/codesize.xml">
        <!-- 個別ルールのしきい値だけ上書き -->
        <exclude name="CyclomaticComplexity"/>
    </rule>
    <rule ref="rulesets/codesize.xml/CyclomaticComplexity">
        <properties>
            <property name="reportLevel" value="12"/>
        </properties>
    </rule>

    <rule ref="rulesets/unusedcode.xml"/>
    <rule ref="rulesets/naming.xml">
        <!-- 短い変数名を一律禁止するルールは誤検知が多いので外す例 -->
        <exclude name="ShortVariable"/>
    </rule>
</ruleset>

実行時はこのファイルを指定します。

vendor/bin/phpmd src/ text phpmd.xml

CI への組み込み

静的解析は「手元で気が向いたときに走らせる」運用だと形骸化します。CIで強制することで初めて効きます。GitHub Actions を例に示します。

name: Static Analysis

on:
  pull_request:

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.3'
          coverage: none

      - name: Install dependencies
        run: composer install --prefer-dist --no-progress

      - name: PHP_CodeSniffer
        run: vendor/bin/phpcs

      - name: PHP Mess Detector
        run: vendor/bin/phpmd src/ github phpmd.xml

phpmd の出力形式に github を指定すると、GitHub のアノテーションとしてPR上に指摘が表示され、該当行が見やすくなります。

composer スクリプトにまとめる

手元でもCIでも同じコマンドで実行できるよう、composer.json にエイリアスを定義しておくと運用が揃います。

{
  "scripts": {
    "lint": "phpcs",
    "lint:fix": "phpcbf",
    "md": "phpmd src/ text phpmd.xml",
    "check": ["@lint", "@md"]
  }
}
composer check

既存コードへ後から導入するときのコツ

ルールを全開にすると、既存プロジェクトでは数千件の指摘が出て心が折れます。段階的に入れるのが現実的です。

  1. まず phpcbf で自動修正できるものを一括修正し、1コミットにまとめる(差分が大きいので他の変更と混ぜない)
  2. 残った違反のうち、すぐ直せないものは baseline や exclude-pattern で一旦除外し、「これ以上増やさない」状態を作る
  3. phpmd のしきい値は最初ゆるめに設定し、徐々に厳しくする

「いきなり完璧」ではなく「これ以上悪化させない」ラインを機械で固定するのが、継続する静的解析の勘所です。

確認方法

  • vendor/bin/phpcs を実行し、違反件数が0、または意図した除外のみになっていること
  • vendor/bin/phpcbf 実行後、git diff で想定外の書き換えがないこと
  • vendor/bin/phpmd src/ text phpmd.xml を実行し、しきい値超過が意図通りに検出されること
  • CI上でPRごとに両ツールが走り、違反時にジョブが赤くなること

注意点

  • phpcs と phpmd は守備範囲が違う。片方だけでは「規約は守られているが複雑怪奇」「設計は綺麗だが書式バラバラ」のどちらかに偏ります
  • phpcbf の自動修正は万能ではありません。命名や設計意図が絡む違反は直らないので過信しないでください
  • phpmd の複雑度指摘は「エラー」ではなく「相談」です。機械的にしきい値を満たすためだけの不自然な分割はかえって読みにくくなります
  • ルールはチームで合意し、phpcs.xml / phpmd.xml としてリポジトリにコミットします。各自の手元設定に依存させないことが重要です

まとめ

  • PHP_CodeSniffer は書式・規約、PHP Mess Detector は複雑度・設計を担保する。両方入れて初めて静的品質が揃う
  • 設定ファイルをリポジトリにコミットし、チーム共通のルールにする
  • CIで強制することでレビューの負荷を下げ、品質のばらつきを機械的に防ぐ
  • 既存コードには段階的に導入し、「悪化させない」ラインから始める

静的解析は「指摘されないコードを書くための縛り」ではなく、「人間がレビューで本当に見るべき設計や仕様の議論に集中するための地ならし」だと捉えると導入が進めやすくなります。

\ 最新情報をチェック /

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください。