プルリク時 Github Actions で PHPStan PHPUnit を走らせる

エンジニアの柿添です。

全国的に猛暑日が続き、夏本番がやってきたんじゃないでしょうか。
学生の頃の楽しい思い出をたくさん思い出す季節ですね、
行き先決めずにドライブに行き、ぼーっと入道雲を眺めていたいです。
温泉も良いですね、黒川温泉に行きたい。

さて、 前回の記事 では pre-commit側で

  • PHPStan による静的解析
  • PHPUnit による自動テスト
  • PHP-CS-Fixerによるコード整形

などを行いました。

commit を打つ前にコードを整形したりなんやらで品質を上げるのが狙いでした。

今回は Github Actions 側で PHPStan を走らせる記事になります。

Github Actions って何?

そもそも何なの?という話ですが、
公式の GitHub Actions を理解する が分かりやすいかと思います。

Github Actions 要約

  • GitHub Actionsは、ビルド、テスト、デプロイの流れを自動化するCI/CDプラットフォーム
  • プルリクエストのビルドやテスト、運用環境へのデプロイなどのワークフローが可能
  • リポジトリの様々なイベントに反応してワークフローを実行でき、例えば新しいissueの作成時に自動でラベルを付けることもできる
  • Linux、Windows、macOSの仮想マシンがワークフロー実行のために提供されている
  • 独自のデータセンターやクラウドでセルフホストランナーを利用することも可能

たくさんの トリガー からワークフローを実行することができる素晴らしいサービスです。

無料枠 もありますので要確認です!

プロジェクトに Github Actions の workflow を仕込もう

前回プロジェクトの構成を変更したと書きましたが、以下のように .github ディレクトリにワークフローの設定ファイルを配置します。
php_checks.yml という(安易な)名前にしております。

├── .githooks
│   ├── pre-commit
├── .github
│   └── workflows
│       └── php_checks.yml # これです!
├── infrastructure
│   ├── docker
│   │   ├── db
│   │   ├── php
│   ... ...
├── src
...

PHPStan PHPUnit 考慮の workflow php_checks.yml

# GitHub Actions Workflow 名
name: PHP Checks

on:
  # 以下のイベントでワークフローが実行されるように指定
  # push:
  #    branches:
  #      - main
  #      - master
  #      - develop
  pull_request:
    types:
      - opened
      - synchronize
      - reopened

jobs:
  php_checks:
    # ジョブの名前
    name: PHP Checks (PHP ${{ matrix.php-versions }} on ${{ matrix.operating-system }})

    # 実行環境
    runs-on: ${{ matrix.operating-system }}
    services:
      # MySQL サービスの設定
      mysql:
        image: mysql:8.0
        env:
          MYSQL_ROOT_PASSWORD: (MySQLのパスワード)
          MYSQL_DATABASE: test_db
          MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
        ports:
          - 3306:3306
        options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3

    strategy:
      fail-fast: false
      # マトリックス戦略
      matrix:
        operating-system: [ ubuntu-latest ]
        php-versions: [ '8.1' ]

    steps:
      # 作業ディレクトリを設定
      - name: Set working directory
        run: echo "WORKING_DIRECTORY=src" >> $GITHUB_ENV

      # リポジトリのコードをチェックアウト
      - name: Checkout
        uses: actions/checkout@v3
        with:
          fetch-depth: 100
          ref: ${{ github.head_ref }}

      # jq をインストール
      - name: Install jq
        run: sudo apt-get install -y jq

      # 変更されたファイルのリストを取得
      - name: Get list of changed files
        id: get-changed-files
        run: |
          PR_NUMBER=$(echo $GITHUB_REF | awk 'BEGIN { FS = "/" } ; { print $3 }')
          FILES=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
                 "https://api.github.com/repos/${{ github.repository }}/pulls/$PR_NUMBER/files" | \
                 jq -r '.[].filename' | paste -sd "," -)
          echo "Changed files: $FILES"
          echo "FILES=$FILES" >> $GITHUB_ENV

      # 変更が含まれているファイルの中に PHP ファイルがあるか確認
      - name: Check for PHP files in the changes
        id: check-php
        run: |
          echo "FILES=${FILES}"
          if echo "${FILES}" | tr ',' '\n' | grep -qE '\.php$'; then
            echo "::set-output name=contains_php::true"
          else
            echo "::set-output name=contains_php::false"
          fi

      # .env ファイルのコピー
      - name: Copy .env
        if: steps.check-php.outputs.contains_php == 'true'
        run: php -r "file_exists('.env') || copy('.env.testing', '.env');"

      # PHP のセットアップ
      - name: Setup PHP
        if: steps.check-php.outputs.contains_php == 'true'
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ matrix.php-versions }}
          extensions: mbstring, dom, fileinfo, simplexml
          coverage: xdebug

      # composer のキャッシュディレクトリの取得
      - name: Get composer cache directory
        if: steps.check-php.outputs.contains_php == 'true'
        id: composer-cache
        run: echo "::set-output name=dir::$(composer config cache-files-dir)"
        working-directory: ${{ env.WORKING_DIRECTORY }}

      # composer の依存関係のキャッシュ
      - name: Cache composer dependencies
        if: steps.check-php.outputs.contains_php == 'true'
        uses: actions/cache@v3
        with:
          path: ${{ steps.composer-cache.outputs.dir }}
          key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
          restore-keys: ${{ runner.os }}-composer-

      # Composer 依存関係のインストール
      - name: Install Composer dependencies
        if: steps.check-php.outputs.contains_php == 'true'
        run: composer install --no-progress --prefer-dist --optimize-autoloader
        working-directory: ${{ env.WORKING_DIRECTORY }}

      # データベースのセットアップ
      - name: Setup Database
        if: steps.check-php.outputs.contains_php == 'true'
        run: |
          mysql -h 127.0.0.1 -u root -phogeh0gePass\& -e 'CREATE DATABASE IF NOT EXISTS test_db;'

      # PHPUnit のための DB 接続のセットアップ
      - name: Setup DB Connection for PHPUnit
        if: steps.check-php.outputs.contains_php == 'true'
        run: |
          echo "DB_HOST=127.0.0.1" >> $GITHUB_ENV
          echo "DB_PORT=3306" >> $GITHUB_ENV
          echo "DB_DATABASE=test_db" >> $GITHUB_ENV
          echo "DB_USERNAME=root" >> $GITHUB_ENV
          echo "DB_PASSWORD=(MySQLのパスワード)" >> $GITHUB_ENV

      # PHPStan の実行
      - name: Run PHPStan
        if: steps.check-php.outputs.contains_php == 'true'
        run: vendor/bin/phpstan --error-format=github --configuration=phpstan.neon
        working-directory: ${{ env.WORKING_DIRECTORY }}

      # PHPUnit テストの実行
      - name: Run PHPUnit Tests
        if: steps.check-php.outputs.contains_php == 'true'
        run: vendor/bin/phpunit
        working-directory: ${{ env.WORKING_DIRECTORY }}

ざっとした動きの解説

  • ワークフローは「PHP Checks」という名前
  • プルリクエストが開かれたとき、同期されたとき、再度開かれたときにトリガーされる
  • Ubuntuの最新バージョンでPHP 8.1を使用する想定
  • MySQL 8.0 イメージを使用して、MySQL サービスを起動(特定の環境変数とポート設定が含まれている)
  • 作業ディレクトリを設定し、ソースコードをチェックアウトする
  • 作業ディレクトリはプロジェクトの構造上 src を指定
  • jqをインストールし、JSONの操作に使用
  • GitHub APIを使用して、変更されたファイルのリストを取得
  • 変更が含まれているファイルの中にPHPファイルがあるかどうかを確認
  • PHPファイルが変更に含まれている場合のみ、次のステップが実行
    • .env ファイルのコピー
    • PHPのセットアップ
    • Composerの依存関係のキャッシュとインストール
    • データベースのセットアップ
    • PHPUnit のための DB 接続
    • PHPStanによる静的解析
    • PHPUnitによる自動テスト

これらを正常に通過しないと PR が merge できなくなります。

※ jq, GitHub API を用いて変更ファイル情報を取得しているのは、squash & force push した際に commit間の差分で変更ファイルを取得する記述 がしっかり動いてくれなかったためです。
※ 記述が甘そうなので要改良

まとめ

PHP-CS-Fixer, PHPStan, PHPUnitを有効活用しよう!の続きでした。
pre-commit でやるよりも導入はさらに容易で、すぐにでも始められます!
( 記述の甘い部分はシステム事業部の誰かがツッコミを入れてくれるはず )

Related article

おすすめ関連記事