0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Laravel Pint を用いた環境において make:●● の後の pint の実行し忘れを防ぐ方法を考える

0
Posted at

表題の通りですが、Laravel 向けの PHP-CS-Fixer ラッパーこと Laravel Pint を使用したプログラムのフォーマットについて Artisan CLI からの新規のクラス作成後に実行し忘れがちょくちょく起きるのでこれを改善する方法を考えてみました。

前置き

Laravel Pint についてプロジェクト固有の設定(pint.json)を作ってない状態では laravel というプリセットが適用されます。
プリセットなどデフォルトのまま使っている場合は make:●● のコマンド実行によって生成されるファイルについて Pint の修正が発生するという問題自体が起きないかもしれません?

今回は laravel ではなく psr12 というプリセットを使いつつ、3つほど適当なルールを追加した設定ファイルを作成しました

pint.json
{
    "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

↑のコマンドによって作成されるマイグレーションファイルは以下の通り

database/migrations/2025_02_22_123409_create_examples_table.php
<?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 

以下のようにフォーマットできました

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つ作成し、その中に丸ごとコードを書いてしまいます。

app/Providers/ArtisanAutoFormatServiceProvider.php
<?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:migrationmake:modelmake:controllermake:request の4つをサポートしています
    • 最初は $event からコマンドによって生成されたファイルのパス情報を取り出して生成したファイルだけに Pint を実行することも考えたのですが断念しました...
  • CommandFinished イベントの終了コードが 0 かつ、上記マッピング配列のキーに合致するコマンドが実行されていたときに Pint のフォーマットを実行します

作成したサービスプロバイダについて AppServiceProvider.php の中で APP_ENV 環境変数が local かつコンソールコマンド実行中のときだけ読み込まれるような登録を実施

app/Providers/AppServiceProvider.php
<?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 コマンドから新規のマイグレーションファイルを作成してみます

image.png

マイグレーションファイル作成の後処理としてリスナー内のコードが実行できていそうな出力が得られました

image.png

生成されたマイグレーションファイルについて declare(strict_types=1); の宣言や、無名クラスの宣言などが適切に修正されていました:relaxed:

(備考) GitHub Actions を用いた Pint 実行について

CommandFinished イベントのリスナー追加によって make:●● を実行した後の Pint の実行し忘れを防ぐことはできましたが、手作業によるファイル変更後の Pint の実行し忘れを防ぐといったことは達成できません...:sob:

この件について調べたところ GitHub Actions のワークフローでの Pint 実行を行うアクションが提供されていたのでこちらを活用するのも良さげでした。

以下で紹介するワークフローは Laravel 用のパッケージ開発を行いやすくする spatie/package-skeleton-laravel というスケルトンリポジトリ内に含まれていたものになります。

.github/workflows/fix-php-code-style-issues.yaml
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 というコミットメッセージと共に修正をプッシュしてくれました。

image.png

(備考) 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 に以下のようなスクラップも作成しました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?