RitoLabo

LaravelにPHP_CodeSnifferを導入しコーディング規約(PSR)に沿った記述を行う

  • 公開:
  • カテゴリ: PHP Laravel
  • タグ: PHP,Laravel,PSR-1,PSR-2,Parsing,PHP_CodeSniffer,PSR-12

LaravelなどPHPを用いてWebアプリケーションを開発している際になるべくきれいなコードを書こうと努めていると思いますが、それは設計だけでなく、構文の細かな部分にも気を配りたいところです。

今回は構文チェックを行う事のできるPHP_CodeSnifferをLaravelに導入し、漏れなくコーディング規約に沿った記述を行えるようにしていきます。

アジェンダ
  1. 開発環境
  2. PHP_CodeSniffer
  3. インストール
  4. 使用できるコーディング規約を確認する
  5. phpcs.xml
  6. 構文チェック実行
  7. コマンド登録
  8. 動作確認
  9. 結果をファイルへ出力する
  10. テストケースクラスのメソッド名を除外する
  11. コーディング標準の違反を自動的に修正する

開発環境

今回の開発環境は以下の通りです。

  • PHP 7.3
  • Laravel 5.8
  • Composer 1.8
  • PHP_CodeSniffer 3.4.2

Laravelのルートディレクトリを「laravel/」としています。

PHP_CodeSniffer

PHP_CodeSnifferは、コーディング規約(PSRなど)に沿った構文チェックを行う事のできるパッケージです。

PHP_CodeSniffer
https://github.com/squizlabs/PHP_CodeSniffer

PHP_CodeSnifferには2つの機能があります。

  • 定義されたコーディング標準(規約)の違反を検出する、phpcsスクリプト
  • コーディング標準(規約)の違反を自動的に修正するphpcbfスクリプト

もちろんLaravel専用ではないので、無印のPHPにも利用でき、PHP 5.4.0から使用できます。

インストール

以下のcomposerコマンドを叩いてインストールできます。

# PHP_CodeSniffer インストール
composer require --dev squizlabs/php_codesniffer

使用できるコーディング規約を確認する

以下のコマンドで、使用できるコーディング規約を確認できます。

$ ./vendor/bin/phpcs -i
The installed coding standards are PEAR, Zend, PSR2, MySource, Squiz, PSR1 and PSR12

色々ありますがPSRに準拠させたいので、選択肢としてはPSR1PSR2・PSR12になります。

今回はPSR12を適用させていきます。(PSR12はPSR1を含み、PSR2の拡張版{PHP7対応etc}です。)

2019年8月現在、まだレビューフェーズですが、そのうち正式に承認PSRになるでしょう。

phpcs.xml

まずは基本となるルールを作成します。プロジェクトルートにphpcs.xmlを作成し、ルールや設定を定義します。

laravel/phpcs.xml
<?xml version="1.0"?>
<ruleset name="PSR12/Laravel">
<description>PSR12 compliant rules and settings for Laravel</description>

<arg name="extensions" value="php" />

<!-- 適用コーディング規約の指定 -->
<rule ref="PSR12" />

<!-- 出力に色を適用 -->
<arg name="colors" />

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

<!-- 除外ディレクトリ -->
<exclude-pattern>/bootstrap/</exclude-pattern>
<exclude-pattern>/config/</exclude-pattern>
<exclude-pattern>/database/</exclude-pattern>
<exclude-pattern>/node_modules/</exclude-pattern>
<exclude-pattern>/public/</exclude-pattern>
<exclude-pattern>/resources/</exclude-pattern>
<exclude-pattern>/routes/</exclude-pattern>
<exclude-pattern>/storage/</exclude-pattern>
<exclude-pattern>/vendor/</exclude-pattern>
<exclude-pattern>/server.php</exclude-pattern>
<exclude-pattern>/app/Console/Kernel.php</exclude-pattern>
<exclude-pattern>/tests/CreatesApplication.php</exclude-pattern>
</ruleset>
  • コーディング規約としてPSR12を指定しています。
  • エラーレポート出力時に色付けする指定を行っています。
  • オプションとして、エラー表示時にルールを表示する事、進捗を表示する事を指定しています。
  • 除外するディレクトリやファイルを指定しています。今回はLaravelのコアソースをなるべく除外し、appとtestsディレクトリ内をチェックするようにしています。

構文チェック実行

最低限の設定を行ったので、一度PHP_CodeSnifferを実行してみます。以下のコマンドを叩きます。

# プロジェクトルートへ移動
cd /path/to/laravel

# PHP_CodeSniffer実行
./vendor/bin/phpcs --standard=phpcs.xml ./

# 実行結果
............................ 28 / 28 (100%)

Time: 33.28 secs; Memory: 8MB

問題なく実行された事が確認出来ました。(この時点ではまだ何も実装していないので、ひとまずの動作確認です。)

コマンド登録

実行コマンドが長いので、composerコマンドで実行できるようにします。

laravel/composer.json
"scripts": {
//
// 省略
//

"sniffer": [
"./vendor/bin/phpcs --standard=phpcs.xml ./"
]
}

composerコマンドで再度実行してみます。

# PHP_CodeSniffer実行
composer sniffer

# 実行結果
> ./vendor/bin/phpcs --standard=phpcs.xml ./
............................ 28 / 28 (100%)

Time: 20.18 secs; Memory: 8MB

これで簡単に実行できるようになりました。

動作確認

実際にコードを書いて動作を確認してみます。

laravel/app/Http/Controllers/SampleController.php
<?php
declare(strict_types=1);

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class SampleController extends Controller
{
public function index(): \stdClass
{
$a = 1;
$b = 2;
if ($a < $b) {
$result = new \stdClass();
$result->message = '$b is larger than $a.';
return $result;
}
}
}

例えばこんなコードがあったとして(これ自体には何の実用性も無いコードです)、構文としてはPSR12には準拠しているのでエラーは出ず素直に通ります。

これを例えば、以下のようにして、ifの後のスペースを無くしてみます。

// if ($a < $b) {
if($a < $b) {

この状態でチェックを走らせると、エラーとなります。

FILE: /path/to/laravel/app/Http/Controllers/SampleController.php
----------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------
15 | ERROR | [x] Expected 1 space(s) after IF keyword; 0 found
| |
(Squiz.ControlStructures.ControlSignature.SpaceAfterKeyword)
----------------------------------------------------------------------
PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY
----------------------------------------------------------------------

if文のスペースについての規約はPSR2からあるものなので、折角なので別のところでもエラーを出してみます。

// $result = new \stdClass();
$result = new \stdClass;

PSR12では、クラスのインスタンス化の際にカッコを省略しないでね。という規約があるので、例えばこんな記述をすると、チェック時にそれを知らせてくれます。

FILE: /path/to/laravel/app/Http/Controllers/SampleController.php
----------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------
16 | ERROR | [x] Parentheses must be used when instantiating a new
| | class
| |
(PSR12.Classes.ClassInstantiation.MissingParentheses)
----------------------------------------------------------------------
PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY
----------------------------------------------------------------------

(つまりphpcs.xmlでのルール指定をPSR2にするとここは違反にはなりません)

こんな感じで、定義したコーディング規約に沿っていつでも構文チェックを走らせる事が出来ます。

結果をファイルへ出力する

構文チェックの結果をファイルへ出力する事も可能です。

出力できるレポートのタイプは以下から確認できます。
https://github.com/squizlabs/PHP_CodeSniffer/wiki/Reporting

例えば、XMLファイルとCSVファイルの場合は以下のコマンドで出力出来ます。

# 結果をXMLファイルとして出力
./vendor/bin/phpcs --standard=phpcs.xml --report=xml --report-file=./sniffer-reports/report.xml ./

# 結果をCSVファイルとして出力
./vendor/bin/phpcs --standard=phpcs.xml --report=csv --report-file=./sniffer-reports/report.csv ./

出力のタイプと出力先を指定しています。(ディレクトリは予め作成しておく)

composerコマンドとして使用するなら以下のようにしておけばいつでも使えます。

laravel/composer.json
"scripts": {
#
# 省略
#

"sniffer": [
"./vendor/bin/phpcs --standard=phpcs.xml ./"
],
"sniffer-report-xml": [
"./vendor/bin/phpcs --standard=phpcs.xml --report=xml --report-file=./sniffer-reports/report.xml ./"
],
"sniffer-report-csv": [
"./vendor/bin/phpcs --standard=phpcs.xml --report=csv --report-file=./sniffer-reports/report.csv ./"
]
}

それぞれの出力ファイルは以下のようになります。

laravel/sniffer-reports/report.xml
<?xml version="1.0" encoding="UTF-8"?>
<phpcs version="3.4.2">
<file name="/var/www/html/laravel/app/Http/Controllers/SampleController.php" errors="2" warnings="0" fixable="2">
<error line="14" column="9" source="Squiz.ControlStructures.ControlSignature.SpaceAfterKeyword" severity="5" fixable="1">Expected 1 space(s) after IF keyword; 0 found</error>
<error line="16" column="23" source="PSR12.Classes.ClassInstantiation.MissingParentheses" severity="5" fixable="1">Parentheses must be used when instantiating a new class</error>
</file>
</phpcs>
laravel/sniffer-reports/report.csv
File,Line,Column,Type,Message,Source,Severity,Fixable
"/var/www/html/laravel/app/Http/Controllers/SampleController.php",14,9,error,"Expected 1 space(s) after IF keyword; 0 found",Squiz.ControlStructures.ControlSignature.SpaceAfterKeyword,5,1
"/var/www/html/laravel/app/Http/Controllers/SampleController.php",16,23,error,"Parentheses must be used when instantiating a new class",PSR12.Classes.ClassInstantiation.MissingParentheses,5,1

テストケースクラスのメソッド名を除外する

ユニットテストを定義している際に、メソッド名を日本語で宣言する場合は規約に引っかかるのでエラーが出ます。こんな感じのやつです。

/**
* @test
*/
public function 私たちの未来がいつまでも明るく、開かれていること()
// ← メソッド名が日本語
{
$this->assertTrue(true);
}

メソッド名はキャメルケース(methodName)で記述しましょうとPSR1で定義されているのでエラーになります。

とはいえここは不用意な違反ではないので、ここはルール単位で除外してあげる事でエラーを回避出来ます。

laravel/phpcs.xml
<?xml version="1.0"?>
<ruleset name="PSR12/Laravel">
//
// 省略
//


<!-- testsディレクトリ配下はメソッド名のキャメルケースチェックを除外する -->
<rule ref="PSR1.Methods.CamelCapsMethodName.NotCamelCaps">
<
exclude-pattern>*/tests/*</exclude-pattern>
</
rule>
</
ruleset>

これでエラーとみなされなくなります。

コーディング標準の違反を自動的に修正する

構文チェックで違反箇所が解ったら都度修正を行えば良いわけですが、自動で修正する事も可能です。

その場合に、全ての修正を行えるわけではなく、
PHPCBF CAN FIX THE x MARKED SNIFF VIOLATIONS AUTOMATICALLY
と表示されている部分に対して、自動での修正が可能です。

FILE: /path/to/laravel/app/Http/Controllers/SampleController.php
----------------------------------------------------------------------
FOUND 2 ERRORS AFFECTING 2 LINES
----------------------------------------------------------------------
14 | ERROR | [x] Expected 1 space(s) after IF keyword; 0 found
| |
(Squiz.ControlStructures.ControlSignature.SpaceAfterKeyword)
16 | ERROR | [x] Parentheses must be used when instantiating a new
| | class
| |
(PSR12.Classes.ClassInstantiation.MissingParentheses)
----------------------------------------------------------------------
PHPCBF CAN FIX THE 2 MARKED SNIFF VIOLATIONS AUTOMATICALLY ← この表示がある部分
----------------------------------------------------------------------

以下のコマンドで自動修正を行う事が出来ます。

# phpcbf 構文自動修正
./vendor/bin/phpcbf --standard=phpcs.xml ./

実行結果は以下になります。

# 実行結果
....................F........ 29 / 29 (100%)

PHPCBF RESULT SUMMARY
------------------------------------------------------------------------------------
FILE FIXED REMAINING
------------------------------------------------------------------------------------
/path/to/laravel/app/Http/Controllers/SampleController.php 2 0
------------------------------------------------------------------------------------
A TOTAL OF 2 ERRORS WERE FIXED IN 1 FILE
------------------------------------------------------------------------------------

Time: 17.71 secs; Memory: 8MB

対象のファイルを見てみると、構文が修正されている事が確認できます。

もちろん、これもcomposer.jsonに登録しておけばいつでも簡単に呼び出せます。

laravel/composer.json
"scripts": {
#
# 省略
#

"sniffer-rewrite": [
"./vendor/bin/phpcbf --standard=phpcs.xml ./"
]
}

まとめ

以上で作業は終了です。構文は実行時にそれだけではエラーにならなかったりするので、人によって書き方に偏りがあったりします。

こういった仕組みがあると、チームで開発していても同じ記述が出来るようになるし、個人で開発していても、一定の指針が出来て結構安心できます。

コードをクリーンで一貫性のあるものにする為に、心意気だけでなく、こういったものも取り入れてスマートに開発していきたいですね。

サンプルソース