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/phpmd
Code language: Bash (bash)
使い方
使い方は、ファイルやディレクトリをカンマ区切りで指定します。ワイルドカードは指定できません。
$ ./vendor/bin/phpmd app/a.php text cleancode,codesize,controversial,design,naming,unusedcode
Code language: Bash (bash)
複数ファイルを指定する場合は、カンマ区切りでファイルを並べます。
$ ./vendor/bin/phpmd app/a.php,app/b.php text cleancode,codesize,controversial,design,naming,unusedcode
Code language: Bash (bash)
ディレクトリも指定できます。
$ ./vendor/bin/phpmd app text cleancode,codesize,controversial,design,naming,unusedcode
Code 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 phpmd
Code language: JSON / JSON with Comments (json)
ファイル1つをphpmdしたいとき、長いコマンドを打つのは面倒なので、シェルスクリプト ./bin/phpmd.sh
を作っておきます。
#!/bin/sh -eu
./vendor/bin/phpmd $1 text cleancode,codesize,controversial,design,naming,unusedcode
Code 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.xml
Code language: Bash (bash)
シェルスクリプトを作るなら、
#!/bin/bash -eu
cwd=$(dirname $0)
$cwd/../vendor/bin/phpmd $1 text $cwd/../phpmd.xml
Code 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番号をコメントに残しておいたらどうかしら。