PHPMDでPHPのソースコードの静的解析を行う

PHPMDでPHPのソースコードの静的解析を行う プログラミング

PHPの静的解析と言えば、今は「PHPStan」の方が勢いはあります。
PHPStanは比較的新しい静的解析ツールですからね。

PHPStanと比べると、PHPMDには歴史があります。
しかし、ここで言いたいのはどっちがいいとかではありません。

むしろ、二者択一ではなく、両方とも使いましょうと言う意見です。
PHPStanに関しては、以下の記事をご覧ください。

「両方とも使いましょう」と気安く言うには、理由があります。
なぜなら、とても簡単に使えるからです。

PHPStanもPHPMDもです。
どれくらい簡単に利用できるのかは、当記事で解説しています。

本記事の内容

  • PHPMDとは?
  • PHPMDのインストール
  • PHPMDの使い方

それでは、上記に沿って解説していきます。

PHPMDとは?

Javaには、静的解析ツール「PMD」というモノが存在します。
PHPMDは、そのPMDのPHP版を目指しているとのこと。

PHPMDが行うことは、PHPのソースコードから潜在的な問題を発見することになります。
そして、その潜在的な問題とは以下。

  • 可能性のあるバグ
  • 最適なコード
  • 複雑すぎる表現
  • 未使用のパラメータ、メソッド、プロパティ

これらの問題を見つけるために、PHPMDには多様なルールが提供されています。

静的解析であるため、実際にプログラムを動かして解析する訳でありません。
コードをルールに基づいて解析して、何かしらの問題を見つけるということです。

簡単に言うと、コードレビューを受けているようなモノでしょう。
あくまで、PHPMDは解析して、その解析結果を表示するだけです。
決して、コードを自動的に修正することはありません。

PHPMDのインストール

PHPMD公式
https://phpmd.org/

2020年12月末時点の最新バージョンは、2.9.1となります。
2020年9月23日に公開されています。

最初のバージョンである0.1.0は、2009年12月20日に公開です。
PHPMDが結構高い頻度で更新されていることを確認できます。
そして、まだまだ現役でメンテナンスもされています。

ソフトウェア要件

ポイントとしては、PHPMDはコマンドラインのPHPで動くモノだというところです。
コマンドラインのPHPは、php-cliとも表現されます。

以下のコマンドで表示されるPHPです。

$ php -v
PHP 7.2.24-0ubuntu0.18.04.7 (cli) (built: Oct  7 2020 15:24:25) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.2.24-0ubuntu0.18.04.7, Copyright (c) 1999-2018, by Zend Technologies

インストール

複数のインストール方法があります。

  • Pharとしてインストール
  • Composer経由でのインストール
  • githubリポジトリからインストール

Pharとしてインストール

Composerがインストールされていないなら、「Pharとしてインストール」をおススメします。
でも、これを機会にComposerをインストールしておくのもアリですけどね。

Pharのインストールは、簡単です。

今回は、phpmd.pharをダウンロードするだけです。
最新の安定版をダウンロード可能。

$ wget -c https://phpmd.org/static/latest/phpmd.phar

Composer経由でのインストール

Composerがインストール済みなら、この方法でインストールしましょう。
今回の検証では、Composer経由でのインストールを実施しています。

以下のコマンドを実行するだけです。

$ composer require --dev "phpmd/phpmd=@stable"

インストールが完了したら、次のコマンドで確認しましょう。

$ ./vendor/bin/phpmd --version
PHPMD 2.9.1

最新バージョン2.9.1がインストールされていますね。
PHPMDのインストールは、問題なく完了です。

githubリポジトリからインストール

公式サイトでは、gitコマンドを使ったインストール方法が記載されています。
正直、おススメしません。

そもそも、PHPMDの開発に参加するプログラマーに向けたモノです。
そのため、普通はgithubでのインストールは不要でしょう。

PHPMDの使い方

PHPMDの使い方は、以下。
ヘルプに結構しっかりと書いてあります。

$ ./vendor/bin/phpmd --help
Mandatory arguments:
1) A php source code filename or directory. Can be a comma-separated string
2) A report format
3) A ruleset filename or a comma-separated string of rulesetfilenames

Example: phpmd /path/to/source format ruleset

Available formats: ansi, html, json, text, xml.
Available rulesets: cleancode, codesize, controversial, design, naming, unusedcode.

Optional arguments that may be put after the mandatory arguments:
--minimumpriority: rule priority threshold; rules with lower priority than this will not be used
--reportfile: send report output to a file; default to STDOUT
--suffixes: comma-separated string of valid source code filename extensions, e.g. php,phtml
--exclude: comma-separated string of patterns that are used to ignore directories. Use asterisks to exclude by pattern. For example *src/foo/*.php or *src/foo/*
--strict: also report those nodes with a @SuppressWarnings annotation
--ignore-violations-on-exit: will exit with a zero code, even if any violations are found

使用例は、「phpmd /path/to/source format ruleset」となります。
以下の3つの引数は必須です。

  • /path/to/source
  • format
  • ruleset

それぞれを説明していきます。

/path/to/source

ソースコードのファイル名またはディレクトリ名を指定します。
カンマ区切りの文字列で複数指定も可能です。

format

解析結果の出力フォーマットを指定します。
次のフォーマットの中から選択します。

  • ansi
  • html
  • json
  • text
  • xml

それぞれの説明は以下。
普通に「phpmd /path/to/source format ruleset」を実行すると、そのフォーマットのファイルが作成されるわけではありません。
コマンドライン上に結果が表示されます。

ansi

コマンドライン用のカラフルで整形されたテキストによる表示。

FILE: Something.class.php
-------------------------------------------
5 | VIOLATION | Avoid unused private fields such as '$FOO'.
6 | VIOLATION | Avoid unused private fields such as '$i'.

「VIOLATION」部分が赤文字にカラーリングされています。

html

htmlタグで表示。

<html><head> <script> function toggle(id) { var item = document.getElementById(id); item.classList.toggle('hidden'); } </script> <style> @media (min-width: 1366px) { body { max-width: 80%; margin: auto; } } body { font-family: sans-serif; } a { color: #2f838a; } a:hover { color: #333; } em { font-weight: bold; font-style: italic; } h1 { padding: 0.5ex 0.2ex; border-bottom: 2px solid #333; } table { width: 100%; border-spacing: 0; } table tr > th { text-align: left; } table caption { font-weight: bold; padding: 1ex 0.5ex; text-align: left; font-size: 120%; border-bottom: 2px solid #333; } tbody tr:nth-child(odd) { background: rgba(0, 0, 0, 0.08); } tbody tr:hover { background: #ffee99; } thead th { border-bottom: 1px solid #aaa; } table td, table th { padding: 0.5ex; } /* Table 'count' and 'percentage' column */ .t-cnt, .t-pct { width: 5em; } .t-pct { opacity: 0.8; font-style: italic; font-size: 80%; } /* Table bar chart */ .t-bar { height: 0.5ex; margin-top: 0.5ex; background-color: #2f838a; /* rgba(47, 131, 138, 0.2); */ } section, table { margin-bottom: 2em; } #details-link.hidden { display: none; } #details-wrapper.hidden { display: none; } ul.code { margin: 0; padding: 0; } ul.code.hidden { display: none; } ul.code li { display: flex; line-height: 1.4em; font-family: monospace; white-space: nowrap; } ul.code li:nth-child(odd) { background-color: rgba(47, 131, 138, 0.1) } /* Excerpt: Line number */ ul.code .no { width: 5%; min-width: 5em; text-align: right; border-right: 1px solid rgba(47, 131, 138, 0.6); padding-right: 1ex; box-sizing: border-box; } /* Excerpt: Code */ ul.code .cd { padding-left: 1ex; white-space: pre-wrap; box-sizing: border-box; word-wrap: break-word; overflow: hidden; } .hlt { background: #ffee99 !important } .prio { color: #333; float: right; } .indx { padding: 0.5ex 1ex; background-color: #000; color: #fff; text-decoration: none; } .indx:hover { background-color: #2f838a; color: #fff; } /* Problem container */ .prb h3 { padding: 1ex 0.5ex; border-bottom: 2px solid #000; font-size: 95%; margin: 0; } .info-lnk { font-style: italic !important; font-weight: normal !important; text-decoration: none; } .info-lnk.blck { padding: 0.5ex 1ex; background-color: rgba(47, 131, 138, 0.2); } .path-basename { font-weight: bold; } .hlt-info { display: inline-block; padding: 2px 4px; font-style: italic; } .hlt-info.quoted { background-color: #92de71; } .hlt-info.variable { background-color: #a3d2ff; } .hlt-info.method { background-color: #f7c0ff; } .sub-info { padding: 1ex 0.5ex; } /* Handle printer friendly styles */ @media print { body, th { font-size: 10pt; } .hlt-info { padding: 0; background: none; } section, table { margin-bottom: 1em; } h1, h2, h3, table caption { padding: 0.5ex 0.2ex; } .prb h3 { border-bottom: 0.5px solid #aaa; } .t-bar { display: none; } .info-lnk { display: none; } #details-wrapper { display: block !important; font-size: 90% !important; } } </style>
<title>PHPMD Report</title></head><body>


            <header>
                <h1>PHPMD Report</h1>
                Generated at <em>2020-12-27 03:31</em>
                with <a href='https://phpmd.org' target='_blank'>PHP Mess Detector</a>
                on <em>PHP 7.2.24-0ubuntu0.18.04.7</em>
                on <em>ubuntu-bionic</em>
            </header>
        <h3>2 problems found</h3><h2>Summary</h2><section><table><caption>By priority</caption><thead><tr><th>Count</th><th>%</th><th>Priority</th></tr></thead><tr> <td class='t-cnt'>2</td> <td class='t-pct'>100.0 %</td> <th class='t-n'>Moderate (3)<div class="t-bar" style="width: 100%; opacity: 1.00"></div></th> </tr></table></section>
<section><table><caption>By rule set</caption><thead><tr><th>Count</th><th>%</th><th>Rule set</th></tr></thead><tr> <td class='t-cnt'>2</td> <td class='t-pct'>100.0 %</td> <th class='t-n'>Unused Code Rules<div class="t-bar" style="width: 100%; opacity: 1.00"></div></th> </tr></table></section>
<section><table><caption>By name</caption><thead><tr><th>Count</th><th>%</th><th>Rule name</th></tr></thead><tr> <td class='t-cnt'>2</td> <td class='t-pct'>100.0 %</td> <th class='t-n'>UnusedPrivateField<div class="t-bar" style="width: 100%; opacity: 1.00"></div></th> </tr></table></section>
<h2 style='page-break-before: always'>Details</h2>
            <a
                id='details-link'
                class='info-lnk blck'
                href='#'
                onclick='toggle("details-link"); toggle("details-wrapper"); return false;'
            >
            Show details &#x25BC;
        </a><div id='details-wrapper' class='hidden'> <section class='prb' id='p-0'> <header> <h3> <a href='#p-0' class='indx'>#1</a> Avoid unused private fields such as <span class='hlt-info quoted'>'$FOO'</span>. <a class='info-lnk' href='https://phpmd.org/rules/unusedcode.html#unusedprivatefield' target='_blank'>(help)</a> <span class='prio'>Moderate (3)</span> </h3> </header> <div class='sub-info'><b>File:</b> <a href='file:///vagrant_data/php/Something.class.php' target='_blank'>/vagrant_data/php/<span class='path-basename'>Something.class.php</span></a> <a class='info-lnk blck' href='#' onclick='toggle("p-0-code"); return false;'> Show code &#x25BC; </a></div> <ul class='code hidden' id='p-0-code'><li><di</div></li></ul> </section>no'>7</div><div class='cd'>    private $j = 6; // Unusedc $FOO = 2; // Unused
<section class='prb' id='p-1'> <header> <h3> <a href='#p-1' class='indx'>#2</a> Avoid unused private fields such as <span class='hlt-info quoted'>'$i'</span>. <a class='info-lnk' href='https://phpmd.org/rules/unusedcode.html#unusedprivatefield' target='_blank'>(help)</a> <span class='prio'>Moderate (3)</span> </h3> </header> <div class='sub-info'><b>File:</b> <a href='file:///vagrant_data/php/Something.class.php' target='_blank'>/vagrant_data/php/<span class='path-basename'>Something.class.php</span></a> <a class='info-lnk blck' href='#' onclick='toggle("p-1-code"); return false;'> Show code </div></li></ul> </section>no'>8</div><div class='cd'>    public function addOne() 5; // Unused='cd'>{
</div></body></html>

json

json形式の表示。
htmlよりは、こちらの方が利用しやすいですね。

{
    "version": "@package_version@",
    "package": "phpmd",
    "timestamp": "2020-12-27T03:33:56+00:00",
    "files": [
        {
            "file": "Something.class.php",
            "violations": [
                {
                    "beginLine": 5,
                    "endLine": 5,
                    "package": null,
                    "function": null,
                    "class": null,
                    "method": null,
                    "description": "Avoid unused private fields such as \u0027$FOO\u0027.",
                    "rule": "UnusedPrivateField",
                    "ruleSet": "Unused Code Rules",
                    "externalInfoUrl": "https:\/\/phpmd.org\/rules\/unusedcode.html#unusedprivatefield",
                    "priority": 3
                },
                {
                    "beginLine": 6,
                    "endLine": 6,
                    "package": null,
                    "function": null,
                    "class": null,
                    "method": null,
                    "description": "Avoid unused private fields such as \u0027$i\u0027.",
                    "rule": "UnusedPrivateField",
                    "ruleSet": "Unused Code Rules",
                    "externalInfoUrl": "https:\/\/phpmd.org\/rules\/unusedcode.html#unusedprivatefield",
                    "priority": 3
                }
            ]
        }
    ]
}

text

ansiとほぼ同じです。

Something.class.php:5 Avoid unused private fields such as '$FOO'.
Something.class.php:6 Avoid unused private fields such as '$i'.

xml

xml形式の表示です。
xmlは、もうjsonに取って代わられた感はあります。
個人的には、xml形式での出力はないでしょう。

<?xml version="1.0" encoding="UTF-8" ?><pmd version="@package_version@" timestamp="2020-12-27T03:40:31+00:00">  <file name="Something.class.php">    <violation beginline="5" endline="5" rule="UnusedPrivateField" ruleset="Unused Code Rules" externalInfoUrl="https://phpmd.org/rules/unusedcode.html#unusedprivatefield" priority="3">      Avoid unused private fields such as '$FOO'.    </violation>    <violation beginline="6" endline="6" rule="UnusedPrivateField" ruleset="Unused Code Rules" externalInfoUrl="https://phpmd.org/rules/unusedcode.html#unusedprivatefield" priority="3">      Avoid unused private fields such as '$i'.    </violation>  </file></pmd>

ruleset

解析する際に用いるルールです。
そのルールを指定します。
カンマ区切りの文字列で複数指定も可能です。

指定できるのは、ルールのファイル名です。
そのルールが記載されたファイルは、デフォルトで以下の分だけ用意されています。

  • cleancode
  • codesize
  • controversial
  • design
  • naming
  • unusedcode

それぞれの意味に関しては、公式サイトで詳しく解説されています。
https://phpmd.org/rules/index.html

また、これらのファイルは以下の場所に存在しています。

$ ls ./vendor/phpmd/phpmd/src/main/resources/rulesets/
cleancode.xml  codesize.xml  controversial.xml  design.xml  naming.xml  unusedcode.xml

あと、このルールファイルは自分で作成可能です。
自作したルールを適用する場合には、ファイルパスを指定します。

phpmd /path/to/source text codesize,controversial,/my/rules.xml

公式サイトでは、ルールファイルの作成方法も解説されています。
「ルールを自分で作ってみるか」と思う方は、公式サイトをご覧ください。

タイトルとURLをコピーしました