Claude CodeのWordPress規約をphpcsのhookで直させる設定
PHPを編集した直後にphpcsをWordPress規約で走らせ、違反の一覧をClaude Codeへ返すPostToolUse hookの書き方です。phpcs.xml.distの置き方と終了コード、phpcbfの使いどころを扱います。
WordPressのプラグインやテーマをClaude Codeに書かせると、動くコードは出てきても、WordPressコーディング規約(WPCS)には合わないことがあります。規約は、インデントにタブを使う、配列はarray( … )の長い書き方にする、条件式はYoda記法にする、といった細かい約束の集まりです。PHP全般の流儀と食い違う箇所が多く、レビューで同じ指摘が繰り返されがちです。
編集の直後にphpcsを走らせ、違反の一覧をClaude Codeに返せば、指摘を受けたその場で直させられます。この記事では、PostToolUse hookでWPCSの検査を回す設定を、導入から終了コードの扱いまで順に組みます。
WPCSとphpcsの関係
phpcs(PHP_CodeSniffer)は、PHPのコードを規則の集まりで検査するコマンドです。WordPressCS(WPCS)はその規則集のひとつで、--standard=WordPressで呼び出します。
standardの名前は4つあります。
| 名前 | 内容 |
|---|---|
WordPress | 内容プロジェクトにある全sniff(個別の検査規則)をまとめた完全版 |
WordPress-Core | 内容WordPress本体のコーディング規約 |
WordPress-Docs | 内容インラインドキュメントの規約 |
WordPress-Extra | 内容コア規約に含まれないベストプラクティスを足したもの。WordPress-Coreを含む |
規約そのものの説明ではなく、検査の道具としての話なので、最初は完全版のWordPressで十分です。違反が多すぎて手に負えないときだけWordPress-Coreに絞る、という順で調整します。
WPCSの要件は、PHP 7.2以上とComposerです。WordPressCS 3.0.0以降は、Composer経由のインストールだけが対応する方法になっています。プロジェクトに開発依存として入れる手順は次のとおりです。
composer config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true
composer require --dev wp-coding-standards/wpcs:"^3.0"
vendor/bin/phpcs -ps . --standard=WordPress最後の行が、WPCSの利用例として示されている形です。-pは進行表示、-sは違反ごとにsniffのコードを表示するオプションです。
Claude Codeに返る出力の形
hookに載せる前に、phpcsが何を出力するかを押さえます。WPCSのREADMEにある実行例は、次のような表形式です。
--------------------------------------------------------------------------------
FOUND 6 ERRORS AND 4 WARNINGS AFFECTING 5 LINES
--------------------------------------------------------------------------------
36 | WARNING | error_reporting() can lead to full path disclosure.
75 | ERROR | Overriding WordPress globals is prohibited. Found assignment
| | to $path
78 | ERROR | $_SERVER['REQUEST_URI'] not unslashed before sanitization. Use
| | wp_unslash() or similar
104 | ERROR | All output should be run through an escaping function (see the
| | Security sections in the WordPress Developer Handbooks), found
| | '$die'.
--------------------------------------------------------------------------------(上は実行例から数行を抜粋したものです。)
行番号、重大度、メッセージが並ぶので、そのままClaude Codeに渡して成立します。注意したいのは、書式の違反だけでなく、エスケープやサニタイズといったセキュリティ系の指摘も同じ表に混ざる点です。「出力をescaping関数に通せ」という指摘は、空白を整えるのとは意味が違います。この違いは後半のCLAUDE.mdの節で扱います。
hookが返す情報は終了コード2とstderrで届く
PostToolUseは、ツールが成功した直後に動くイベントです。入力のJSONにはtool_input(ツールに渡した引数)とtool_response(返った結果)が入り、Writeならtool_input.file_pathに編集したファイルの絶対パスが入ります。
ここで重要なのは、終了コードの扱いです。
PostToolUse hookの終了コードの扱い
終了コード0
成功です。stderrはClaudeには見えません。検査で違反が出ても0で終わると、指摘は届きません。
終了コード2
PostToolUseではstderrがClaudeに表示されます。ツールはすでに実行済みなので、編集は取り消されません。
その他の終了コード
非ブロックのエラーです。処理は続き、stderrの最初の行が通知に出ます。
phpcsは違反があると0以外で終わります。そのまま素通しにすると非ブロックのエラー扱いになり、Claudeが受け取るのはstderrの先頭行だけです。違反の全体を渡すには、スクリプトで終了コードを2に読み替える必要があります。
PostToolUseはツール実行の後なので、「止める」ことはできません。ファイルはもう書かれています。hookの役割は、書かれたあとに違反を知らせて、次の1手で直させることです。
設定の全体像
構成は3つのファイルです。
phpcs.xml.dist— どのstandardを、どの設定値で使うか.claude/hooks/wpcs-check.sh— 編集されたファイルをphpcsにかけ、結果を返すスクリプト.claude/settings.json— hookの登録
phpcs.xml.dist
ファイル名が.phpcs.xml、phpcs.xml、.phpcs.xml.dist、phpcs.xml.distのいずれかで、実行ディレクトリかその上位にあれば、--standardを省いてもphpcsが自動で見つけます。複数あれば、この並びの順で1つだけが使われます。
<?xml version="1.0"?>
<ruleset name="my-plugin">
<description>my-pluginのコーディング規約</description>
<file>.</file>
<exclude-pattern>*/vendor/*</exclude-pattern>
<exclude-pattern>*/node_modules/*</exclude-pattern>
<arg name="extensions" value="php"/>
<rule ref="WordPress"/>
<rule ref="WordPress.WP.I18n">
<properties>
<property name="text_domain" type="array">
<element value="my-plugin"/>
</property>
</properties>
</rule>
<rule ref="WordPress.NamingConventions.PrefixAllGlobals">
<properties>
<property name="prefixes" type="array">
<element value="my_plugin"/>
</property>
</properties>
</rule>
</ruleset>2つの<rule>ブロックには理由があります。WordPress.WP.I18nのtext domain検査は、有効なtext domainを設定しないとスキップされます。PrefixAllGlobalsも、prefixesを設定しないとsniff自体が動きません。つまり、何も設定しないWordPressでは、翻訳関数の第2引数や関数名の接頭辞に関する違反は拾われません。Claudeが新しい関数を書いたときに接頭辞を付け忘れるのを防ぎたいなら、この設定が要ります。
コマンドラインから一時的に渡すこともできます。
vendor/bin/phpcs -p . --standard=WordPress --runtime-set text_domain my-pluginhookスクリプト
#!/bin/bash
# .claude/hooks/wpcs-check.sh
input=$(cat)
file=$(jq -r '.tool_input.file_path // empty' <<<"$input")
cwd=$(jq -r '.cwd // empty' <<<"$input")
# PHPファイル以外は対象外
case "$file" in
*.php) ;;
*) exit 0 ;;
esac
[ -f "$file" ] || exit 0
cd "${cwd:-.}" || exit 0
[ -x vendor/bin/phpcs ] || exit 0
out=$(vendor/bin/phpcs -s "$file" 2>&1)
status=$?
case "$status" in
0)
exit 0
;;
1|2|3|5|6|7)
{
echo "phpcs(WordPress規約)の指摘です: $file"
echo "$out" | head -n 60
} >&2
exit 2
;;
*)
echo "phpcsを実行できませんでした(終了コード $status)" >&2
echo "$out" | head -n 20 >&2
exit 1
;;
esacポイントを順に挙げます。
- JSONの解析に
jqを使います。jqがPATHに必要です。 - 編集対象が
.phpでなければ、すぐ0で抜けます。MatcherはEditとWriteの全ファイルに反応するので、JSやCSSの編集で毎回phpcsが起動するのを防ぎます。 --standardは渡していません。phpcs.xml.distが使われます。- 違反があるときの終了コードは、PHP_CodeSniffer 4.0.0では累積値です。
1が自動修正可能な違反のみ、2が自動修正できない違反あり、3が両方の混在です。3.xでは1が違反あり、2が修正可能な違反あり、3が処理エラーという別の意味でした。上のスクリプトは1〜3を違反として扱い、4.0.0の16(処理エラー)や64(実行要件の不足)は環境の問題として別枝に分けています。3.xの3はこの枝に入り、違反として扱われますが、出力にはエラー文が入るのでClaudeには内容が伝わります。 head -n 60で出力を切ります。違反が数百行になっても、Claudeの文脈を埋めないための上限です。
実行権限を付けるのを忘れないでください。
chmod +x .claude/hooks/wpcs-check.shsettings.json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/wpcs-check.sh",
"timeout": 30
}
]
}
]
}
}ifフィールドで"Edit(*.php)"のように絞る方法もあります。ただしifには1つのルールしか書けず、1つのルールは1つのツールにしか当たりません。EditとWriteの両方に.phpの絞り込みを付けるなら、ハンドラを2つに分けることになります。スクリプト側で拡張子を見る今の形のほうが、設定は短く済みます。
PostToolUseの全体像と、入力JSONの各項目はPostToolUse hookとはで扱っています。言語別のlintコマンドを並べた早見表はPostToolUse hookのLint自動修正にあります。
phpcbfに任せる範囲を決める
phpcs出力の[x]は、phpcbf(PHP Code Beautifier and Fixer)で自動修正できる違反の印です。[ ]が付いたものは手で直すしかありません。公式の出力例では、空白の数や型宣言の大文字小文字、true/false/nullの小文字化が[x]で、行の長さの超過は[ ]でした。
修正の流れは3通りあります。
phpcbfの使い方3案
報告だけ
hookは検査と報告だけを行い、直すのはClaudeです。ファイルはClaudeの手でしか変わらないため、何が起きたか追いやすい形です。
hookでphpcbfを先に実行
スクリプトが検査の前にphpcbfを走らせ、残った違反だけを返します。Claudeの出力は減りますが、書いたコードがhookに書き換えられます。
Claudeにphpcbfを実行させる
CLAUDE.mdに「[x]付きの違反はphpcbfで直す」と書いておく方法です。判断はClaudeに残り、自動修正の差分も会話に出ます。
phpcbfで書き換えるのは、先に報告だけの運用を回してから決めても遅くありません。報告だけで違反が減らないなら、自動修正を先行させる価値があります。
なお、PostToolUse hookが動くのは、EditやWriteなどのツールがファイルを書いたときです。BashのコマンドでPHPファイルが書き換わったときは、Edit|Writeに一致するPostToolUse hookは動きません。ClaudeがBashからphpcbfを実行した場合、その書き換えの検査はhookでは行われません。
CLAUDE.mdに書く方針
hookは「違反がある」ことしか伝えません。どう直すかの方針はCLAUDE.mdに置きます。WPCSに特有の約束を、短く書いておくと効きます。
## PHPのコーディング規約
- WordPressコーディング規約(WPCS)に従う。インデントはタブ、配列は array( ) の長い書き方。
- 編集のたびにphpcsの指摘がhookで返る。指摘が返ったら、その回のうちに直す。
- `[x]` が付いた違反は `vendor/bin/phpcbf <ファイル>` で直してよい。
- エスケープ・サニタイズ・nonceに関する指摘は、コードを直して解消する。
`// phpcs:ignore` を付けて消さない。
- `phpcs:ignore` が本当に必要なときは、理由を同じ行のコメントに書く。最後の2項目は、エラーを消す最短の手段がphpcs:ignoreコメントだからです。phpcsには--ignore-annotationsというオプションがあり、コード中のphpcs:...コメントを無視して検査できます。hookのコマンドに付ければ、無効化コメントで違反が隠れることはなくなります。その代わり、人間が理由を書いて残した正当なignoreも違反として返ります。どちらが合うかは、既存コードにignoreがどれだけ残っているかで決まります。
セキュリティ系の指摘は、書式の指摘と同じ重みで直さないほうが安全です。$_SERVER['REQUEST_URI']にwp_unslash()を付ける、出力にesc_html()を通す、といった修正は挙動を変えます。どの関数を選ぶかは文脈次第なので、機械的に空白を揃える修正とは別に、差分を読んで確かめる対象です。
プラグインを作る手順そのものはClaude CodeでWordPressプラグインを作るにあります。ここで扱ったのは、作ったあとに規約へ合わせ続ける仕組みです。
うまく動かないとき
hookが返らないときの切り分け
- QPHPファイルをEditかWriteで編集しましたか?答えがBashで書き換えたのなら、hookは動きません答えがはい
次の確認へ進みます。
- Qスクリプトに実行権限が付いていますか?答えがchmod +x を実行します答えがはい
次の確認へ進みます。
- Qvendor/bin/phpcs がカレントディレクトリにありますか?答えがcwdがComposerのルートと違う可能性があります。スクリプトのcdを書き換えます答えがはい
次の確認へ進みます。
それでも返らないとき
スクリプトに手で入力を渡して終了コードを確かめます。echo '{"tool_input":{"file_path":"/絶対パス/a.php"},"cwd":"/プロジェクト"}' | .claude/hooks/wpcs-check.sh; echo $?の結果が2なら、スクリプトは正しく動いています。
違反は出ているのにClaudeが反応しない場合は、終了コードが0になっていないかを見ます。0で終わったhookのstderrは、Claudeには見えません。
規約違反の山に埋もれないために
既存のプラグインに途中から入れると、最初のphpcsは大量の違反を返します。次の順で、出力を現実的な量に落とします。
-nを付けて警告を外し、エラーだけにする(警告を含めない設定です)--exclude=で、すぐには直さないsniffを外す- 編集したファイルだけを検査する(今回のスクリプトは編集ファイル1つだけに限っています)
- 全体の一括整形は、hookではなく別のコミットで
phpcbfを使って行う
phpcsには、Gitで変更されたファイルだけを検査する--filter=GitModifiedというフィルタもあります。CIや手動の確認で使えます。コミット時の規約チェックを別の形で組む例はClaude Codeのcommitlint設定にあります。
まとめ
PostToolUse hookでphpcsを回すときの要点は、スクリプトで終了コードを2に読み替えることと、phpcs.xml.distにtext domainと接頭辞を設定することの2つです。前者がないとClaudeは指摘を受け取れず、後者がないと翻訳関数と命名の検査が動きません。
運用は報告だけから始め、[x]の違反が目立ってきたらphpcbfの自動実行を足す順が無理がありません。セキュリティ系の指摘だけは、phpcs:ignoreで消さずに差分を読んで確かめる対象として分けておくと、規約チェックが形だけになるのを防げます。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。