phpmdとは
PHPコードの静的解析ツールです。バッドプラクティスな箇所を指摘してくれます。どこからリファクタリングすればいいかの目安にもなります。
ソースを書いた人も、レビューする人にも便利なツールです。
古くは、2012年に1.4.0で。php: >= 5.3.0。
最新は、2020-09-23に、2.9.1。php: >= 5.3.9。
インストール
composerでインストールします。
$ composer require --dev phpmd/phpmdCode language: Bash (bash)
使い方
使い方は、ファイルやディレクトリをカンマ区切りで指定します。ワイルドカードは指定できません。
$ ./vendor/bin/phpmd app/a.php text cleancode,codesize,controversial,design,naming,unusedcodeCode language: Bash (bash)
複数ファイルを指定する場合は、カンマ区切りでファイルを並べます。
$ ./vendor/bin/phpmd app/a.php,app/b.php text cleancode,codesize,controversial,design,naming,unusedcodeCode language: Bash (bash)
ディレクトリも指定できます。
$ ./vendor/bin/phpmd app text cleancode,codesize,controversial,design,naming,unusedcodeCode language: Bash (bash)
app下のvendor、tmpを除外したい場合は
$ ./vendor/bin/phpmd app text cleancode,codesize,controversial,design,naming,unusedcode --exclude "*/vendor/*,*/tmp/*"Code language: Bash (bash)
composer.jsonに登録しておきます。
"scripts": {
"phpmd": [
"./vendor/bin/phpmd app text cleancode,codesize,controversial,design,naming,unusedcode --exclude \"*/vendor/*,*/tmp/*\"",
]
}
Code language: JSON / JSON with Comments (json)
composer phpmd で全対象ファイルをphpmdにかけます。
$ composer phpmdCode language: JSON / JSON with Comments (json)
ファイル1つをphpmdしたいとき、長いコマンドを打つのは面倒なので、シェルスクリプト ./bin/phpmd.sh を作っておきます。
#!/bin/sh -eu
./vendor/bin/phpmd $1 text cleancode,codesize,controversial,design,naming,unusedcodeCode language: Bash (bash)
chmod +x しておきます。
$ chmod +x bin/phpmd.sh
$ ./bin/phpmd.sh app/a.php
Code language: Bash (bash)
ルールのカスタマイズ
How to create a custom rule set
プロジェクトの全ファイルで、ElseExpressionやCamelCaseXXXを抑制したい場合は、カスタムルールxmlを作成します。
カスタムルールxmlの例
<?xml version="1.0"?>
<ruleset name="My first PHPMD rule set"
xmlns="http://pmd.sf.net/ruleset/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0
http://pmd.sf.net/ruleset_xml_schema.xsd"
xsi:noNamespaceSchemaLocation="
http://pmd.sf.net/ruleset_xml_schema.xsd"> <description>
My custom rule set that checks my code...
</description>
<rule ref="rulesets/cleancode.xml">
<exclude name="ElseExpression" />
<exclude name="StaticAccess" />
<exclude name="MissingImport" />
</rule>
<rule ref="rulesets/codesize.xml">
<exclude name="TooManyPublicMethods" />
</rule>
<rule ref="rulesets/controversial.xml">
<exclude name="CamelCaseClassName" />
<exclude name="CamelCasePropertyName" />
<exclude name="CamelCaseMethodName" />
<exclude name="CamelCaseParameterName" />
<exclude name="CamelCaseVariableName" />
</rule>
<rule ref="rulesets/design.xml">
<exclude name="NumberOfChildren" />
</rule>
<rule ref="rulesets/naming.xml">
<exclude name="ShortVariable" />
</rule>
<rule ref="rulesets/unusedcode.xml">
<exclude name="UnusedLocalVariable" />
<exclude name="UnusedFormalParameter" />
</rule>
</ruleset>Code language: HTML, XML (xml)
このようなxmlを作っておきます。ファイル名は任意ですが、phpmd.xml、phpmd_myproject.xml、myproject_phpmd.xmlのようにしておくとわかりやすいです。
phpmdコマンドのcleancode,codesizeなどを指定する箇所で、カスタムxmlを指定します。
$ ./vendor/bin/phpmd src text ./phpmd.xmlCode language: Bash (bash)
シェルスクリプトを作るなら、
#!/bin/bash -eu
cwd=$(dirname $0)
$cwd/../vendor/bin/phpmd $1 text $cwd/../phpmd.xmlCode language: Bash (bash)
警告の一覧
cleancode
| @SuppressWarnings | メッセージ例 |
|---|---|
PHPMD.BooleanArgumentFlag | The method bar has a boolean flag argument $flag, which is a certain sign of a Single Responsibility引数のbool変数でロジックを振り分けているなら、2つのメソッドに分けよう。 |
PHPMD.ElseExpression | The method bar uses an else expression. Else clauses are basically not necessary and you can simplify the code by not using them.(ヌルチェック、値の範囲チェックなど、前処理のifは)elseを使わず、アーリーリターンしよう。 |
PHPMD.IfStatementAssignment | Avoid assigning values to variables in if clauses and the like (line '8', column '13')【重要】if条件内で代入禁止。 ==のタイプミス? |
PHPMD.StaticAccess | Avoid using static access to class 'Bar' in method 'bar'.クラス名::staticメソッドのアクセス禁止 |
PHPMD.DuplicatedArrayKey | Duplicated array key xxx, first declared at line 8.【重要】配列で同じキーが登録されているよ。(一つめの値は、2つめの値で上書きされる) |
PHPMD.ErrorControlOperator | Remove error control operator '@' on line 8.エラー抑制子@を削除しよう。 |
PHPMD.MissingImport | Missing class import via use statement (line '8', column '13').(classに対応した)useをしていないよ。 |
PHPMD.UndefinedVariable | Avoid using undefined variables such as '$xxx' which will lead to PHP notices.【重要】値が未設定の変数は禁止。 |

if条件内の代入防止のため、if ('hoge' == $s)
のように左右逆に書く人もいるわ
codesize【重要】
| @SuppressWarnings | メッセージ例 |
|---|---|
PHPMD.CyclomaticComplexity | The xx yy() has a Cyclomatic Complexity of 50. The configured cyclomatic complexity threshold is 10.【重要】メソッドが複雑すぎ。循環的複雑度が11をこえている。リファクタして、ifやwhile、for、caseを減らそう。 |
PHPMD.NPathComplexity | The xx yy() has an NPath complexity of 512. The configured NPath complexity threshold is 200.【重要】メソッドが複雑すぎ。コードの実行パス数が200をこえている。リファクタして、ifやwhile、for、caseを減らそう。 |
PHPMD.ExcessiveMethodLength | The xx yy() has 200 lines of code. Current threshold is set to 100. Avoid really long methods.【重要】メソッドが長すぎ、100行以内におさめよう。空行もカウントします。メソッドに分けよう。 |
PHPMD.ExcessiveClassLength | The class xx has 2000 lines of code. Current threshold is 1000. Avoid really long classes.【重要】クラスが長すぎ。1000行以内におさめよう。空行もカウントします。クラスを分けよう。 |
PHPMD.ExcessiveParameterList | The xx yy has 20 parameters. Consider reducing the number of parameters to less than 10.メソッドの引数が多すぎ。10以内にしよう。 |
PHPMD.ExcessivePublicCount | The xx yy has 100 public methods and attributes. Consider reducing the number of public items to less than 45.publicメソッド+publicプロパティが多すぎ。45以内にしよう。(get/setメソッド含む) |
PHPMD.TooManyFields | The xx yy has 30 fields. Consider redesigning yy to keep the number of fields under 15.クラスのプロパティが多すぎ。15以内にしよう。例えば、city、state、zipプロパティは、addressクラスにまとめたらどう? |
PHPMD.TooManyMethods | The xx yy has zz non-getter- and setter-methods. Consider refactoring yy to keep number of methods under 25.メソッドが多すぎ。25以内にしよう。(get/setメソッドを除く) |
PHPMD.TooManyPublicMethods | The xx yy has 20 public methods. Consider refactoring yy to keep number of public methods under 10.publicメソッドが多すぎ。10以内にしよう。(get/setメソッドを除く) |
PHPMD.ExcessiveClassComplexity | The class xx has an overall complexity of 60 which is very high. The configured complexity threshold is 50.メソッド数やメソッド複雑度から総合的に判断すると、クラスが複雑すぎ。WMC(Weighted Method Count)が50をこえている。 |

何千行もあるクラス、何百行もあるメソッドなんてふつうにあるよ

聞こえない...
controversial
| @SuppressWarnings | メッセージ例 |
|---|---|
PHPMD.Superglobals | xx accesses the super-global variable $_SESSION.(メソッド内で)$_GET、$_SESSIONなどのスーパーグローバル変数にアクセスしないほうがいいよ。 |
PHPMD.CamelCaseClassName | The class xx is not named in CamelCase.クラス名をキャメルケースにしよう。 |
PHPMD.CamelCasePropertyName | The property xx is not named in camelCase.プロパティ名をキャメルケースにしよう。 |
PHPMD.CamelCaseMethodName | The method xx is not named in camelCase.メソッド名をキャメルケースにしよう。 |
PHPMD.CamelCaseParameterName | The parameter xx is not named in camelCase.引数名をキャメルケースにしよう。 |
PHPMD.CamelCaseVariableName | The variable xx is not named in camelCase.変数名をキャメルケースにしよう。 |

controversialは「物議をかもす」って意味だね

反対意見も多いってことね、スネークケースのほうが読みやすくて好みっていう人も多いわ
design
| @SuppressWarnings | メッセージ例 |
|---|---|
PHPMD.ExitExpression | The xx yy() contains an exit expression.exit()禁止。テストできないよ。 |
PHPMD.EvalExpression | The xx yy() contains an eval expression.eval()禁止。テストできないし、セキュリティリスクも高い。 |
PHPMD.GotoStatement | The xx yy() utilizes a goto statement.goto禁止。 |
PHPMD.NumberOfChildren | The xx yy has zz children. Consider to rebalance this class hierarchy to keep number of children under 15.extendsした子クラスが多すぎ、15以下におさえよう。 |
PHPMD.DepthOfInheritance | The xx yy has zz parents. Consider to reduce the depth of this class hierarchy to under 6.extends階層が深すぎ。6以下におさえよう。 |
PHPMD.CouplingBetweenObjects | The class xx has a coupling between objects value of 20. Consider to reduce the number of dependencies under 13.使っているクラスが多すぎ。13以下にしよう。 |
PHPMD.DevelopmentCodeFragment | The xx yy() calls the typical debug function var_dump() which is mostly only used during development.var_dump()やprint_r()などの開発コードが残っているよ。本番コードでは削除しよう。 |
PHPMD.EmptyCatchBlock | Avoid using empty try-catch blocks in xx.catchが空だよ。 |
PHPMD.CountInLoopExpression | Avoid using count() function in yy loops.ループ内でcount()しているよ。 例: for ($i = 0; $i <= count($arr); $i += 1) |

for文でcount()使っているけど、何が悪いの?

たいがい foreach で置き換えできるでしょ。

それに、カウンタを使っている場合、複数の配列を操作していることが多くて、理解しづらいの。リファクタしたほうがいいかもれないから、確認したほうがいいってことね。
naming
| @SuppressWarnings | メッセージ例 |
|---|---|
PHPMD.LongClassName | Avoid excessively long class names like xx. Keep class name length under 40.クラス名が長すぎ。40文字以下にしよう。 |
PHPMD.ShortClassName | Avoid classes with short names like xx. Configured minimum length is 3.クラス名が短すぎ。3文字以上にしよう。 |
PHPMD.ShortVariable | Avoid variables with short names like $x. Configured minimum length is 3.変数名が短すぎ。3文字以上にしよう。forカウンタは除く。 |
PHPMD.LongVariable | Avoid excessively long variable names like xx. Keep variable name length under 20.プロパティ、引数、ローカル変数名が長すぎ。20文字以内にしよう。 |
PHPMD.ShortMethodName | Avoid using short method names like xx::yy(). The configured minimum method name length is 3.メソッド名が短すぎ。3文字以上にしよう。 |
PHPMD.ConstructorWithNameAsEnclosingClass | Classes should not have a constructor method with the same name as the classコンストラクタは __constructメソッドを使うこと。PHP4スタイルのクラス名と同名メソッドは禁止。 |
PHPMD.ConstantNamingConventions | Constant xx should be defined in uppercaseクラスやinterfaceの定数名は、大文字にすること。 |
PHPMD.BooleanGetMethodName | The 'xx()' method which returns a boolean should be named 'is…()' or 'has…()'戻り値がbool型のメソッドは、 'getX()' より、'isX()' や 'hasX()' のほうがいいよ。 |

ローカル変数で1文字の $x、$y、2文字の $x1、$x2 とか使っているよ

そういう場合は警告抑止してもいいわね
unusedcode
| @SuppressWarnings | メッセージ例 |
|---|---|
PHPMD.UnusedPrivateField | Avoid unused private fields such as 'xx'.privateプロパティが使われていない。 |
PHPMD.UnusedLocalVariable | Avoid unused local variables such as 'xx'.ローカル変数が使われていない。 |
PHPMD.UnusedPrivateMethod | Avoid unused private methods such as 'xx'.privateメソッドが使われていない。 |
PHPMD.UnusedFormalParameter | Avoid unused parameters such as 'xx'.引数が使われていない。 |

当初は引数を使っていたんだけど、今は使わなくなった引数も警告されちゃうんだよね

すでにリリース済みのpublicやprotectedメソッドの場合は、リファクタしないほうがいいわね。

警告を抑止して、未使用になった理由やIssue番号をコメントに残しておいたらどうかしら。


