🧹

Laravel Pint + GitHub Actions を導入して開発体験向上してみた

2023/12/24に公開

概要

先日入社した会社が運用するLaravelプロジェクトのCIを一通り改善するチャレンジをやりました。
その時に導入したツールや設定等を共有していきます。

今回は「Laravel Pint」の導入編です。

背景

入社してコードを読んでみると、以下のような問題がありました。

  • インデントが 4 だったり 8 だったりとバラバラ。
  • メソッド名の後ろや if の後ろのスペースを付けるか付けないかバラバラ。
  • 未使用のimport文が残っている。
  • 必要なのかわからない謎の大量改行がある。
  • ...etc

上記に取り上げたのは一例で、他にも大量に問題を抱えています。

これらは動作上問題は有りません。
ただ単純に読みづらく、個人によって正解が異なります。
またその個人でも、状況によってなのか感情によってなのか書きっぷりが変わってきます。

これをコーディング規約を定めてレビューで指摘し続けて修正していく、といった人力手法だと改善&維持は完全に不可能だと考えました。
そこで今回は、「Laravel Pint」を導入して機械的にコードを修正/維持することを決めました。

Laravel Pint とは?

Laravel Pint とは、コードの自動整形をしてくれるツールです。

https://readouble.com/laravel/9.x/ja/pint.html

他言語の有名どころでいうとイメージが付きやすいかもしれません。

JavaScript だと、「ESLint + Prettier」、Ruby だと、「RuboCop」のようなものです。

どういうルールでフォーマットするか?をymlファイル形式で設定できるため、その設定をベースに機械的にコードの検査/修正が実行できます。

人力だと見落としてしまったり、コードレビューで見つけて大量にコメントをするという手間が発生したり、その指摘が実装者の考えと反している場合は不快な気持ちにさせてしまったりと、色々と問題が多い部分ですが、機械的にそれを修正してくれるのであればそういった問題は基本起きません。

本当にありがたいツールですね。

Laravel Pintの 採用理由

他の選択肢として有名どころだと、PHP CodeSniffer が候補に挙がりました。
少し調べて見る限り、PHP CodeSnifferのほうがより詳細に検知させることが可能のようです。

それでも Laravel Pint を今回作用した理由は、以下です。

  • Laravel9 からデフォルトで入っているプラグインであり、今後も継続的にサポートが受けれるのでは?と予想した
  • PHP Formatter で有名な PHP-CS-Fixer をラッパーしているもので、よりLaravelに特化した自動整形を期待できる
  • これまで何もルールが統一されていなかったシステムに自動整形ツールがとりあえず導入されるだけでも十分な効果を得られると思ったので、例えLaravel Pintのほうが自動整形精度が低くても必要十分であると感じた

今回のゴール

  • Larevel Pint を導入すること
  • Laravel Pint を GitHub Actions Workflowsに追加すること

本編

開発環境

  • PHP 8.1
  • Laravel 9

インストール

laravel9 以降を使用している場合、既にインストール済の可能性もあります。
まずは composer.json を見て、laravel/pint が作成されている状態かどうかを確認してみましょう。

もし確認できなかった場合、以下のコマンドを実行してインストールします。

composer require --dev laravel/pint

設定ファイルの作成

Pintは基本的に設定は不要です。
既にlaravelに適した設定になっているからです。

ただ、各プロジェクトによってはルールが少し異なることもあると思いますので、カスタマイズできるように設定ファイルを作成しておきましょう。

プロジェクトのルートディレクトリに、pint.json ファイルを作成します。

pint.json
{
    "preset": "laravel" // 他のプリセットだと、psr12, symfony があります
}

他に設定をカスタマイズしたい場合は、PHP-CS-Fixerの設定を参照して好きな設定にカスタマイズしてください。

https://mlocati.github.io/php-cs-fixer-configurator/#version:3.41

カスタマイズしたい項目は、rules の中に列挙します。

pint.json
{
    "preset": "laravel",
    "rules": {
      // null を返す単純化された return ステートメントを推奨する。
        "simplified_null_return": true,
        // ... etc
    }
}

その他にも特定のフォルダを対象外とする "exclude": {} などもありますが、今回は自分が使用している部分だけの紹介とさせていただきます。

基本的にこだわりがあまり強くない場合は、Laravel の標準プリセットに従っておくのがベターなのと、可能な限りフォーマットは揃えたほうが良いかな?と思っているタイプなので、除外せず全部見るぐらいの気持ちでも良いのかなと思っています。(※個人の見解)

実行

問題なく機能するか、実行してみましょう。
laravelのプロジェクトルートに移動し、以下のコマンドを実行します。

./vendor/bin/pint --test

--test オプションは、ファイルの自動修正をさせず、スタイルエラーだけを検査させたい場合に使用します。
今回はお試し実行ということで、自動修正を掛けずに検査だけさせます。

このようにエラーが検知されました。
ただ、どういうエラーを吐いているのかはこのままだと読み取れません。
そこで、詳細にログを吐いてもらうためのオプションである -v を付けます。

出ましたね。
使用されていない use Tests\TestCase; というimport文を見つけて削除しようとしてくれています。

--test を外すと、実際に自動修正してくれます。

実際に自動修正させた後にもう一度実行してみると、エラーが消えていることを確認できます。

また、さり気なくやってましたが、一番後ろにファイル名を指定することで特定ファイルのみPintを実行させることができます。

# app/Models/User.php のみ Pintをかける
./vendor/bin/pint --test -v app/Models/User.php

# 半角スペース区切りで複数ファイルを指定することも可能
./vendor/bin/pint --test -v app/Models/User.php app/Models/Todo.php

※注意点: ./vendor/bin/pint を実行すると全ファイルを対象に検査/自動修正が走り出すので、大量の差分が発生する恐れがあります。気をつけましょう。

【応用編】より簡単に実行できるようにカスタマイズ

毎回 ./vendor/bin/pint -v./vendor/bin/pint -v --test と書くのは面倒なので、composer.json に Pint を実行する script を追加します。

composer.json
{
    // ...その他の設定,
    "scripts": {
        // ...その他のscripts,
        "pint": [
            "@php ./vendor/bin/pint -v"
        ],
        "dry-pint": [
            "@php ./vendor/bin/pint --test -v"
        ],
        // ...その他のscripts,
    },
    // ...その他の設定,
}

以下のコマンドで実行できます。

# 自動修正するPint
composer pint

# 自動修正しない検査のみのPint
composer dry-pint

GitHub Actions の設定

CIである、GitHub Actions の設定です。
PR作成時、PR作成後の追加プッシュの際に、差分箇所の中でフォーマットから外れている部分を検知させるようにします。

GitHub Actionsを動かすためには、プロジェクトルート配下の .github/workflows/ の中に設定ファイルを作成する必要があります。

今回はこの中に、 pint.yml を追加します。

ディレクトリ構成として、laravel/ というプロジェクト配下に apptests といったLaravelプロジェクトの中身が入っている構成です。
LaravelをAPIサーバ、他にNext.jsやNuxt.jsといったフロントエンドフレームワークを使用する構成ではよく見られるかも?という前提です。

pint.yml
name: Pint

on:
  pull_request:
    paths:
      - '**.php'

jobs:
  pint:
    runs-on: ubuntu-latest

    defaults:
      run:
        working-directory: laravel

    steps:
      # リポジトリをチェックアウト
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          # GitHub上でPATを生成して環境変数を設定してください
          token: ${{ secrets.GITHUB_TOKEN }}

      # 現在動いているワークフローをキャンセル
      - name: Cancel Previous Runs
        uses: styfle/cancel-workflow-action@0.8.0
        with:
          access_token: ${{ github.token }}

      # PHPのインストール
      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.1'
          extensions: intl, zip, pcntl, bcmath, gd, pdo_mysql, exif, redis, imagick, soap, xsl, gmp, opcache, sockets, memcached, apcu, mongodb, swoole, xdebug
          coverage: pcov

      # Composer インストール
      - name: Install dependencies
        run: composer install --prefer-dist --no-progress --no-suggest --ansi

      # PRの差分ファイル名のみを抽出
      - name: Get changed files
        id: changed-files
        uses: tj-actions/changed-files@v40
        with:
          files: |
            **/*.php
          separator: " "
          base_sha: 'develop # 差分の比較先(マージ先のブランチを指定してください)

      # 差分ファイルのパスが、 working-directoryを無視して laravel/* と始まってしまうため、 laravel/ を削除
      - name: Remove 'laravel/' from paths
        id: remove-laravel-path
        run: |
          changed_files="${{ steps.changed-files.outputs.all_changed_files }}"
          modified_files=$(echo "$changed_files" | sed 's|laravel/||g')
          echo "Modified PHP files: $modified_files"
          echo "::set-output name=modified_files::$modified_files"

      # PHPファイルが変更されていない場合は、Pintをスキップさせる
      - name: Check if PHP files have changed
        id: check-changed
        run: |
          if [ -z "${{ steps.remove-laravel-path.outputs.modified_files }}" ]; then
            echo "No PHP files have changed"
            echo "::set-output name=skip::true"
          else
            echo "Changed PHP files: ${{ steps.remove-laravel-path.outputs.modified_files }}"
            echo "::set-output name=skip::false"
          fi

      # Pintを実行
      - name: Run pint for Changed Files
        if: ${{ steps.check-changed.outputs.skip == 'false' }}
        run: vendor/bin/pint -v ${{ steps.remove-laravel-path.outputs.modified_files }}

こちらを設定した状態でPRを作成すると、CIが起動して検査してくれます。

【応用編】修正箇所を自動コミットする

「フォーマットが揃っていない箇所があったらそれを知らせるだけOK」というのが僕の好みなのですが、あまり自動整形周りに慣れていない人や億劫な方であれば、「自動修正したら勝手にコミットしてほしい」という要望もあるかもしれません。

その場合は、自動コミット&プッシュをしてくれる用に設定しましょう。

pint.yml に以下を追加しましょう。

pint.yml

# Pint を実行
# ---------------------------

# 自動コミット
- name: Auto Commit
  uses: stefanzweifel/git-auto-commit-action@v4
  with:
    commit_message: Pintによる自動コミット

これを使用することで、差分があった場合はそれらをaddしてコミットしてくれます。
commit_message にはお好みのコメントを設定しましょう。

終わりに

統一感のない読みづらいコードが多かったのですが、こちらを導入してだいぶ読みやすくなりました。

動作的な問題は無いにしても、日々開発者が読むコードなので、ストレス無く読みやすいコードを維持したいですよね。

これを導入するだけで自然とその問題を解消できてしまうので、本当におすすめです。

先日公開した Larastan の記事と併せて、是非導入してほしいツールです。

https://zenn.dev/tatsuyaaa/articles/9ab20a08b07488#github-actions-の設定

参考文献

https://qiita.com/ucan-lab/items/8807a092eb6d87ddfe34?utm_campaign=post_article&utm_medium=twitter&utm_source=twitter_share

GitHubで編集を提案

Discussion