phpcs.xmlとVSCodeのフォーマット環境

# 概要
- [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer)の設定ファイルについて
- Laravelのプロジェクトを想定
- [PSR-12](https://www.php-fig.org/psr/psr-12/)をベースにする
- 暫定版


# phpcs.xml

```phpcs.xml
<?xml version="1.0"?>
<ruleset name="TPF-Format">
    <!-- 拡張子が php のものにだけ適用 -->
    <arg name="extensions" value="php" />

    <!-- オプション p:進捗表示  s:エラー表示時にルールを表示 -->
    <arg value="ps" />

    <!-- ルール全て除外 -->
    <exclude-pattern>*/vendor/*</exclude-pattern>
    <exclude-pattern>*/migrations/*</exclude-pattern>
    <exclude-pattern>*/bootstrap/cache/*</exclude-pattern>
    <exclude-pattern>*/public/*</exclude-pattern>
    <exclude-pattern>*/storage/*</exclude-pattern>
    <exclude-pattern>*.blade.php</exclude-pattern>

    <rule ref="PSR12" />

    <!-- メソッド名のキャメルケース -->
    <rule ref="PSR1.Methods.CamelCapsMethodName.NotCamelCaps">
        <!-- ルールを除外 -->
        <exclude-pattern>*/tests/*</exclude-pattern>
    </rule>

    <!-- 配列要素のインデントを4つにする -->
    <rule ref="Generic.Arrays.ArrayIndent" />

    <!-- メソッドチェーンやプロパティのインデントを4つにする -->
    <rule ref="PEAR.WhiteSpace.ObjectOperatorIndent"/>

    <!-- 短い配列構文にする -->
    <rule ref="Generic.Arrays.DisallowLongArraySyntax" />

    <!-- 制御構造の条件内での変数の代入を検出する（三項演算以外） -->
    <rule ref="Generic.CodeAnalysis.AssignmentInCondition" />

    <!-- 常にtrue/falseになる制御構造を検出する -->
    <rule ref="Generic.CodeAnalysis.UnconditionalIfStatement" />

    <!-- ヨーダ条件式を検出する -->
    <rule ref="Generic.ControlStructures.DisallowYodaConditions" />

    <!-- 非推奨のPHP関数を使わない -->
    <rule ref="Generic.PHP.DeprecatedFunctions" />

    <!-- コメントがフォーマットされているか -->
    <rule ref="Generic.Commenting.DocComment" />
    <rule ref="Generic.Commenting.DocComment.NonParamGroup">
        <severity>0</severity>
    </rule>
    <rule ref="Generic.Commenting.DocComment.ShortNotCapital">
        <severity>0</severity>
    </rule>
    <rule ref="Generic.Commenting.DocComment.TagValueIndent">
        <severity>0</severity>
    </rule>
    <rule ref="Generic.Commenting.DocComment.MissingShort">
        <severity>0</severity>
    </rule>

    <!-- 変数とメンバー変数の名前を検査する -->
    <rule ref="Squiz.NamingConventions.ValidVariableName" />

    <!-- メンバー変数のキャメルケース以外を許可する -->
    <rule ref="Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps">
        <severity>0</severity>
    </rule>

    <!-- private変数のアンダースコアなしを許可する -->
    <rule ref="Squiz.NamingConventions.ValidVariableName.PrivateNoUnderscore">
        <severity>0</severity>
    </rule>

    <!-- 論理演算子and/orを検出する -->
    <rule ref="Squiz.Operators.ValidLogicalOperators" />

    <!-- メソッド同士の間隔を設定する -->
    <rule ref="Squiz.WhiteSpace.FunctionSpacing">
        <properties>
            <property name="spacing" value="1" />
            <property name="spacingBeforeFirst" value="0" />
            <property name="spacingAfterLast" value="0" />
        </properties>
    </rule>

    <!-- セミコロン前の空白を検出する -->
    <rule ref="Squiz.WhiteSpace.SemicolonSpacing" />

    <!-- ""が不要な場合は''に統一する -->
    <rule ref="Squiz.Strings.DoubleQuoteUsage" />

    <!-- ""内に変数があることを許可する -->
    <rule ref="Squiz.Strings.DoubleQuoteUsage.ContainsVar">
        <severity>0</severity>
    </rule>

    <!-- 配列のフォーマット -->
    <rule ref="Squiz.Arrays.ArrayDeclaration" />
    <rule ref="Squiz.Arrays.ArrayDeclaration.KeyNotAligned">
        <severity>0</severity>
    </rule>

    <rule ref="Squiz.Arrays.ArrayDeclaration.MultiLineNotAllowed">
        <severity>0</severity>
    </rule>

    <rule ref="Squiz.Arrays.ArrayDeclaration.ValueNotAligned">
        <severity>0</severity>
    </rule>

    <rule ref="Squiz.Arrays.ArrayDeclaration.CloseBraceNotAligned">
        <severity>0</severity>
    </rule>

    <rule ref="Squiz.Arrays.ArrayDeclaration.CloseBraceNewLine">
        <severity>0</severity>
    </rule>

    <rule ref="Squiz.Arrays.ArrayDeclaration.DoubleArrowNotAligned">
        <severity>0</severity>
    </rule>
</ruleset>
```

# フォーマット試しファイル
元はLaravelのControllerファイル。
このファイルに対してphpcsのフォーマットを実行すると、直せるところはフォーマットされる。

```HogeFugaController.php
<?php

namespace App\Http\Controllers;

use Hoge;


/** phpcsチェック
 * コメントフォーマット
 *
 */

class HogeFugaController extends Controller
{
    public $_hoge; // PublicHasUnderscore
    private $fuga; // OK
    private $_hoge_fuga; // MemberNotCamelCaps

    private function get_huga() { }
    private function fugaga($array,$b) {
        $int = (integer) '123'; // Short form type keywords must be used. Found: (integer)
        $bool = (boolean) 1; // Short form type keywords must be used.

        new \DateTime;

        $array = array();
        $array2 = [1,
         2,3];

        if ($aaa = 1) {
        }

        if (1 == $aaa) {
            echo 'yoda';
        }

        $aaa = ! false;

        if ($aaa === 1) echo 'aaa is 1';

        if ($aaa and false) {
            echo 'ValidLogicalOperators';
        }

        sprintf( '%s %s', 'hoge', 'fuga');

        if(false) return false;

        echo "hoge" ;

        $hoge    = "space";
    }
    public function get-fuga()
    {
        $validated = $request->validated();

        $client = new Client();

        $filename  = uniqid();

        $cmd = $client->getCommand('Hogee', [
            'Key' => $filename
        ]);
    }
}
```


# VSCodeとphpcs
開発環境がDockerならDockerコンテナ内のPHPを使うとPHP本体のエラーも検出できて便利、Remote Containerを使う。

今回は元々あるDockerコンテナでなく編集用のコンテナを新たに作る。
その理由は以下が主なこと。
- docker composeで複数コンテナを起動するのが一般的だと思うが、PHPの編集のためだけにdocker compose upするのは面倒
- Gitの履歴を.dockerignoreしているケースなどはVSCode上でGit情報が取れないなどの不便もある
- .devcontainerにextensionsの設定も記録できない

導入はこちらの記事を参考に。
https://qiita.com/yoskeoka/items/01c52c069123e0298660

編集用のコンテナについてはこちらの記事を参考に。
https://zenn.dev/niisan/articles/9abd372ae86fc1

## モノレポの場合
同じPHPのバージョンを使っているならまとめた方が便利。
今回は `hoge1-be` `hoge2-be` がPHPを使用している。
それぞれについて.devcontainer.jsonを作成（hoge1-beの文字列はシステムごとに変える）。

```.devcontainer.json
{
  "name": "hoge1-be",
  "dockerComposeFile": "../../docker/vscode/docker-compose-php.yml",
  "service": "editor",
  "workspaceFolder": "/app",
  "settings": {
    "php.validate.executablePath": "/usr/local/bin/php",
    "phpSniffer.autoDetect": true,
    "phpSniffer.executablesFolder": "/app/hoge1-be/vendor/bin",
    "phpSniffer.standard": "/app/hoge1-be/phpcs.xml",
    "[php]": {
      "editor.defaultFormatter": "wongjn.php-sniffer"
    }
  },
  "extensions": [
    "wongjn.php-sniffer"
  ],
  "shutdownAction": "stopCompose"
}
```

databaseフォルダは共通なのでシステムそれぞれに設定、編集中にGit履歴を見たいので.gitも突っ込む。

volumesにある `hoge1-be_vendor_volume` `hoge2-be_vendor_volume` は開発用のdocker-compose.ymlでcomposerのパッケージをVolume trickしているものを再利用している。
ホスト側にvendorフォルダの中身がある場合はこれらに関する行は不要。
```docker/vscode/docker-compose-php.yml
version: "3"

services:
    editor:
        build: php
        command: sleep infinity
        volumes:
            - ../../hoge1-be:/app/hoge1-be
            - hoge1-be_vendor_volume:/app/hoge1-be/vendor
            - ../../database:/app/hoge1-be/database
            - ../../hoge2-be:/app/hoge2-be
            - hoge2-be_vendor_volume:/app/hoge2-be/vendor
            - ../../database:/app/hoge2-be/database
            - ../../.git:/app/.git
volumes:
  hoge1-be_vendor_volume:
    external: true
  hoge2-be_vendor_volume:
    external: true
```

Dockerfileは最低限で。
```docker/vscode/php/Dockerfile
FROM php:8.0-fpm-alpine as dev

RUN mkdir /app
RUN mkdir /app/.git
WORKDIR /app

RUN apk update && \
    apk add git

COPY --from=composer /usr/bin/composer /usr/bin/composer
```

VSCodeでプロジェクトフォルダを開き、.devcontainerフォルダを含むフォルダを開くとRemote Container使うかというメッセージが表示されるようになる。
コンテナに `wongjn.php-sniffer` などphpcs用のextensionsを導入をし、保存時にフォーマットする設定をオンにすればPHPファイル保存時にルールに基づいた設定でフォーマットされる。


# 参考
- https://github.com/squizlabs/PHP_CodeSniffer/tree/master/src/Standards
    - ルール一覧が本家にないのでコードを見るのが早い
- PHP_CodeSnifferのルールまとめ
    - https://qiita.com/blue32a/items/db049e091d4e7a469e6d#squizcommentingclasscomment
- Customisable Sniff Properties
    - https://github.com/squizlabs/PHP_CodeSniffer/wiki/Customisable-Sniff-Properties
- PSRの誤解
    - https://qiita.com/tadsan/items/942a381e952e12a8fa5a