表題の通りですが、Laravel 向けの PHP-CS-Fixer ラッパーこと Laravel Pint を使用したプログラムのフォーマットについて Artisan CLI からの新規のクラス作成後に実行し忘れがちょくちょく起きるのでこれを改善する方法を考えてみました。
前置き
Laravel Pint についてプロジェクト固有の設定(pint.json)を作ってない状態では laravel というプリセットが適用されます。
プリセットなどデフォルトのまま使っている場合は make:●● のコマンド実行によって生成されるファイルについて Pint の修正が発生するという問題自体が起きないかもしれません?
今回は laravel ではなく psr12 というプリセットを使いつつ、3つほど適当なルールを追加した設定ファイルを作成しました
{
"preset": "psr12",
"rules": {
"declare_strict_types": true, // php ファイルの先頭に declare(strict_types=1); を付ける
"trailing_comma_in_multiline": true, // ← 複数行の配列の末尾の要素の後ろにカンマを付ける
"single_line_empty_body": true // ← 処理の存在しない関数の中括弧を1行で書く
}
}
マイグレーションファイルを作成するコマンドを一例として考えてみます
$ php artisan make:migration create_examples_table
↑のコマンドによって作成されるマイグレーションファイルは以下の通り
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('examples', function (Blueprint $table) {
$table->id();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('examples');
}
};
Pint を手動実行
$ vendor/bin/pint database/migrations/2025_02_22_123409_create_examples_table.php
以下のようにフォーマットできました
<?php
+declare(strict_types=1);
+
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
-return new class extends Migration
-{
+return new class () extends Migration {
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('examples', function (Blueprint $table) {
$table->id();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('examples');
}
};
-
declare_strict_typesのルールによって php ファイルの先頭にdeclare(strict_types=1);が付くようになりました - PSR12 のプリセットに含まれる
new_with_parenthesesというルールよって無名クラスの宣言に修正が入りました
make:●● コマンド実行の後処理として Pint を自動実行する方法について
本題
まず始めに Artisan CLI から実行できるコンソールコマンドはいずれも実行終了時に Illuminate\Console\Events\CommandFinished というイベントを発信します(詳しくは以下)
このイベントをリッスンしたリスナーの中で Pint を実行することで実行し忘れを防ぐことができました。
今回はサンプルということでアプリケーション内にサービスプロバイダを1つ作成し、その中に丸ごとコードを書いてしまいます。
<?php
declare(strict_types=1);
namespace App\Providers;
use Illuminate\Console\Events\CommandFinished;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;
use Symfony\Component\Process\Process;
class ArtisanAutoFormatServiceProvider extends ServiceProvider
{
/**
* @var array<string, string>
*/
protected $supportedMakeCommands = [
'make:migration' => 'database/migrations',
'make:model' => 'app/Models',
'make:controller' => 'app/Http/Controllers',
'make:request' => 'app/Http/Requests',
];
public function boot(): void
{
Event::listen(CommandFinished::class, function (CommandFinished $event) {
if ($event->exitCode === 0 && array_key_exists($event->command, $this->supportedMakeCommands)) {
$targetDir = $this->supportedMakeCommands[$event->command];
$output = $event->output;
$output?->writeln('');
$output?->writeln("<info>Running pint on {$targetDir}</info>");
$process = new Process(['./vendor/bin/pint', $targetDir]);
$process->run(function ($type, $buffer) use ($output) {
if (Process::ERR === $type) {
$output?->writeln("<error>{$buffer}</error>");
} else {
$output?->write($buffer);
}
});
}
});
}
}
- コマンドごとに生成されるファイルのパスはおおよそ決まっているので、コマンドごとのフォーマット対象とするディレクトリのマッピング配列(
$supportedMakeCommands)を用意- ↑ の例では
make:migration、make:model、make:controller、make:requestの4つをサポートしています - 最初は
$eventからコマンドによって生成されたファイルのパス情報を取り出して生成したファイルだけに Pint を実行することも考えたのですが断念しました...
- ↑ の例では
- CommandFinished イベントの終了コードが 0 かつ、上記マッピング配列のキーに合致するコマンドが実行されていたときに Pint のフォーマットを実行します
作成したサービスプロバイダについて AppServiceProvider.php の中で APP_ENV 環境変数が local かつコンソールコマンド実行中のときだけ読み込まれるような登録を実施
<?php
declare(strict_types=1);
namespace App\Providers;
use Illuminate\Support\Facades\App;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
+ if (App::runningInConsole() && App::environment('local')) {
+ App::register(\App\Providers\ArtisanAutoFormatServiceProvider::class);
+ }
}
}
動作確認として make:migration コマンドから新規のマイグレーションファイルを作成してみます
マイグレーションファイル作成の後処理としてリスナー内のコードが実行できていそうな出力が得られました
生成されたマイグレーションファイルについて declare(strict_types=1); の宣言や、無名クラスの宣言などが適切に修正されていました![]()
(備考) GitHub Actions を用いた Pint 実行について
CommandFinished イベントのリスナー追加によって make:●● を実行した後の Pint の実行し忘れを防ぐことはできましたが、手作業によるファイル変更後の Pint の実行し忘れを防ぐといったことは達成できません...![]()
この件について調べたところ GitHub Actions のワークフローでの Pint 実行を行うアクションが提供されていたのでこちらを活用するのも良さげでした。
以下で紹介するワークフローは Laravel 用のパッケージ開発を行いやすくする spatie/package-skeleton-laravel というスケルトンリポジトリ内に含まれていたものになります。
name: Fix PHP code style issues
on:
push:
paths:
- '**.php'
permissions:
contents: write
jobs:
php-code-styling:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
- name: Fix PHP code style issues
uses: aglipanci/laravel-pint-action@2.5
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: Fix styling
php ファイルのコミットをトリガーとして稼働し、修正が発生したときに Fix styling というコミットメッセージと共に修正をプッシュしてくれました。
(備考) Laravel パッケージを自作した件について
この記事で紹介した Pint を後処理として実行する方法について PHP8.3 以上かつ Laravel11.x 以上のアプリケーションに composer でインストール可能なパッケージとして作成してみました。
- ↑ の方に書いた spatie/package-skeleton-laravel をベースとして開発しました
- パッケージディスカバリ の機能によって composer でインストールするだけで機能が有効になります
- こちらは Pint の他、IDE Helper Generator for Laravel の自動実行も行えるように開発しています
-
Larvel Pintがインストールされている場合、make:●●コマンド実行後に Pint のフォーマット処理が走ります -
IDE Helper Generator for Laravelがインストールされている場合、make:modelコマンド実行後にide-helper:modelsコマンドが走ります
-
その他 spatie/package-skeleton-laravel を用いた Laravel のパッケージ開発自体が初めてのことだったので備忘録として Zenn に以下のようなスクラップも作成しました。


