2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Laravel Sail と GitHub Actions で効率的なビルド環境を手に入れろ!

Last updated at Posted at 2024-09-27

目的

Laravel Sail環境でのCIの設定はプロジェクトの中ではとても重要です。
CI上でSail自体をビルドすると多くの時間を要するため、より効率的な方法を共有します。
(Laravel Sailと銘打ちましたが、結局Laravel SailはGitHub Actions上で使わないほうが高速でした)

関連記事

.github/workflows/ci.yaml

次のファイルを配置します。

.github/workflows/ci.yaml
name: Continuous Integration
on:
  push:
    branches:
      - 'main'
  pull_request:
  workflow_dispatch:
env:
  DB_CONNECTION: mysql
  DB_HOST: 127.0.0.1
  DB_PORT: 3306
  DB_DATABASE: laravel
  DB_USERNAME: sail
  DB_PASSWORD: password
jobs:
  ci-backend:
    runs-on: ubuntu-latest
    services:
      db:
        image: mysql/mysql-server:8.0
        ports:
          - 3306:3306
        env:
          MYSQL_DATABASE: 'laravel'
          MYSQL_USER: 'sail'
          MYSQL_PASSWORD: 'password'
          MYSQL_ROOT_PASSWORD: 'password'
          MYSQL_ALLOW_EMPTY_PASSWORD: 1
        options: >-
          --health-cmd "mysqladmin ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    steps:
      - uses: actions/checkout@v4
      - name: Setup PHP with composer v2
        uses: shivammathur/setup-php@v2
        with:
          php-version: 8.3
          tools: composer:v2
      - name: Cache Vendor
        id: cache-vendor
        uses: actions/cache@v4
        with:
          path: ./vendor
          key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
          restore-keys: ${{ runner.os }}-composer-
      - name: Install Dependencies
        if: steps.cache-vendor.outputs.cache-hit != 'true'
        run: composer install --quiet --prefer-dist --no-progress --no-interaction --no-scripts --no-ansi
      - name: Laravel Setting
        run: |
          cp .env.example .env
          php artisan optimize
          git config --local core.fileMode false
          chmod -R 777 storage bootstrap/cache
      - name: PHP Version
        run: php --version
      - name: Composer Version
        run: composer --version
      - name: Laravel Version
        run: php artisan --version
      - name: Composer Validate
        run: composer validate
      - name: Run Migrate
        run: php artisan migrate
      - name: Run Migrate Refresh
        run: php artisan migrate:refresh
      - name: Run Seeding
        run: php artisan db:seed
      - name: Run IDE Helper Models
        run: |
          php artisan ide-helper:models --write --reset
          ./vendor/bin/pint app/Models
          if ! git diff --exit-code; then
            echo "Error: The phpdoc for the model ide-helper is not updated!"
            echo "Run: php artisan ide-helper:models --write --reset"
            exit 1
          fi
      - name: Cache Pint
        uses: actions/cache@v4
        with:
          path: ./.pint.cache
          key: ${{ runner.os }}-pint-${{ hashFiles('**/composer.lock') }}
          restore-keys: ${{ runner.os }}-pint-
      - name: Run Pint
        run: ./vendor/bin/pint --test
      - name: Cache Rector
        uses: actions/cache@v4
        with:
          path: ./storage/rector/cache
          key: ${{ runner.os }}-rector-${{ hashFiles('**/composer.lock') }}
          restore-keys: ${{ runner.os }}-rector-
      - name: Run Rector
        run: ./vendor/bin/rector process --dry-run
      - name: Cache PHPStan
        uses: actions/cache@v4
        with:
          path: ./storage/phpstan
          key: ${{ runner.os }}-phpstan-${{ hashFiles('**/composer.lock') }}
          restore-keys: ${{ runner.os }}-phpstan-
      - name: Run PHPStan
        run: ./vendor/bin/phpstan analyze
      - name: Cache Pest
        uses: actions/cache@v4
        with:
          path: ./storage/pest/cache
          key: ${{ runner.os }}-pest-${{ hashFiles('**/composer.lock') }}
          restore-keys: ${{ runner.os }}-pest-
      - name: Run Pest
        env:
          SESSION_DRIVER: array
          DB_CONNECTION: sqlite
          DB_DATABASE: ":memory:"
        run: ./vendor/bin/pest --parallel --cache-directory storage/pest/cache
  ci-frontend:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '>=20.17.0'
      - name: Cache Node Modules
        id: cache-node-modules
        uses: actions/cache@v4
        with:
          path: ./node_modules
          key: ${{ runner.os }}-node-modules-${{ hashFiles('**/package-lock.json') }}
          restore-keys: ${{ runner.os }}-node-modules-
      - name: Install Dependencies
        if: steps.cache-node-modules.outputs.cache-hit != 'true'
        run: npm install
      - name: Run Build
        run: npm run build

ワークフロー解説

設定の羅列だけでは、何をしているかわからないと思うので各行それぞれ解説します。

.github/workflows/ci.yaml
name: Continuous Integration
on:
  push:
    branches:
      - 'main'
  pull_request:
  workflow_dispatch:
env:
  DB_CONNECTION: mysql
  DB_HOST: 127.0.0.1
  DB_PORT: 3306
  DB_DATABASE: laravel
  DB_USERNAME: sail
  DB_PASSWORD: password
  • name: ワークフロー名を設定します(任意の名前を付ける)
  • on: ワークフローが実行されるタイミング
    • workflow_dispatch があるとGitHub上の画面からワークフローを手動実行できる
      • 動作確認したい時にはあると便利です
  • env: 今回はMySQLの接続情報を使うために利用

MySQLパスワードはCI実行で使うものなので平文で良いかなと思いました。
ポイントは DB_HOST: 127.0.0.1 です。
SailのDBではなくGitHub Actions上のDBコンテナを指すためです。

.github/workflows/ci.yaml
jobs:
  ci-backend:
    # ...
  ci-frontend:
    # ...

PHPとNodeが必要なものと分かれるので、並列で実行してCI時間の短縮を図っています。
ci-backendのジョブから解説します。

.github/workflows/ci.yaml
  ci-backend:
    runs-on: ubuntu-latest
    services:
      mysql:
        image: mysql/mysql-server:8.0
        ports:
          - 3306:3306
        env:
          MYSQL_DATABASE: ${{ env.DB_DATABASE }}
          MYSQL_USER: ${{ env.DB_USERNAME }}
          MYSQL_PASSWORD: ${{ env.DB_PASSWORD }}
          MYSQL_ALLOW_EMPTY_PASSWORD: 1
        options: >-
          --health-cmd "mysqladmin ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

SailのDBではなく、GitHub Actions上で定義できるサービスにDB定義しています。
SailのDockerビルドを行うと時間がかかるためです。(5分くらい伸びる)
マイグレーションの箇所で落ちると困るので、ヘルスチェックコマンドを入れて起動確認しています。

ci-backendのジョブステップは多いので分割して解説します。

.github/workflows/ci.yaml
      - uses: actions/checkout@v4
      - name: Setup PHP with composer v2
        uses: shivammathur/setup-php@v2
        with:
          php-version: 8.3
          tools: composer:v2

shivammathur/setup-php アクションを使って、PHPとComposerをインストールしています。

これもSail自体をビルドすると時間がかかってしまうからです。

.github/workflows/ci.yaml
      - name: Cache Vendor
        id: cache-vendor
        uses: actions/cache@v4
        with:
          path: ./vendor
          key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
          restore-keys: ${{ runner.os }}-composer-
      - name: Install Dependencies
        if: steps.cache-vendor.outputs.cache-hit != 'true'
        run: composer install --quiet --prefer-dist --no-progress --no-interaction --no-scripts --no-ansi

actions/cache アクションを使って、 vendor ディレクトリをキャッシュしています。

  • id はステップ間で出力の値を利用するためidを設定
  • path: キャッシュしたいファイルorディレクトリ
  • key: キャッシュの保存と検索に利用されるキー
  • restore-keys: keyでキャッシュヒットしない場合にキャッシュの検索に利用されるキー。前方一致でキャッシュを検索
  • if: steps.cache-vendor.outputs.cache-hit
    • ステップを実行するかの判定条件を書けます。キャッシュがあった場合に実行する
  • run: composer install --quiet --prefer-dist --no-progress --no-interaction --no-scripts --no-ansi
    • --quiet: 通常のログ出力を非表示
    • --prefer-dist: Gitクローンの代わりに圧縮されたzipでダウンロード(通信量が減り、高速化に繋がる)
    • --no-progress: 進捗バーを非表示(CIで表示する必要がないので)
    • --no-interaction: 対話式プロンプトを無効
    • --no-scripts: Composerスクリプトを無効
    • --no-ansi: 色付きの出力を無効
.github/workflows/ci.yaml
      - name: Laravel Setting
        run: |
          cp .env.example .env
          php artisan optimize
          git config --local core.fileMode false
          chmod -R 777 storage bootstrap/cache
      - name: PHP Version
        run: php --version
      - name: Composer Version
        run: composer --version
      - name: Laravel Version
        run: php artisan --version
      - name: Composer Validate
        run: composer validate

Laravelの設定を行ってます。
必要に応じて .env.example の代わりに .env.ci みたいなファイルを用意して良いかもです。

optimize でConfigやRoutingのキャッシュを行ってます。
本番環境ではキャッシュした状態で動かすので、キャッシュがちゃんと動作するかを兼ねています。

storage と bootstrap/cache 配下は書き込みできる必要があるので、設定しています。
後のステップでファイル差分をチェックしているので、パーミッションの変更のファイル差分が出ないように無効化しています。

composer validate コマンドでは composer.jsoncomposer.lock の記述が正しいか検証しています。

.github/workflows/ci.yaml
      - name: Run Migrate
        run: php artisan migrate
      - name: Run Migrate Refresh
        run: php artisan migrate:refresh
      - name: Run Seeding
        run: php artisan db:seed
      - name: Run IDE Helper Models
        run: |
          php artisan ide-helper:models --write --reset
          ./vendor/bin/pint app/Models
          if ! git diff --exit-code; then
            echo "Error: The phpdoc for the model ide-helper is not updated!"
            echo "Run: php artisan ide-helper:models --write --reset"
            exit 1
          fi

Laravelのデータベースマイグレーションが実行できることを確認しています。
ロールバックテストもCIに含めておかないと壊れていることに気付けないのでテストするようにしています。

php artisan ide-helper:models --write --reset を実行すると
モデルファイルのphpdocなどにIDE補完情報が追記されます。

この時は実際にデータベースを読みにいくので、データベースマイグレーションを実行した後に行なっています。
コードスタイルルールに沿わない形式で出力されるため、Pintを実行させてからファイル差分がないか git diff を行なっています。

.github/workflows/ci.yaml
      - name: Cache Pint
        uses: actions/cache@v4
        with:
          path: ./.pint.cache
          key: ${{ runner.os }}-pint-${{ hashFiles('**/composer.lock') }}
          restore-keys: ${{ runner.os }}-pint-
      - name: Run Pint
        run: ./vendor/bin/pint --test

キャッシュ部分は上述で説明した通りなので省きますが、
Cache Vendor の時と違って、 Run Pint の時にifがないのはキャッシュファイルがある時はキャッシュを参照してコマンドを実行したいからです。

./vendor/bin/pint --test ここではコードスタイルのチェックを行っています。
PintはLaravel公式が提供しているphp-cs-fixerのラッパーライブラリのコードフォーマッターです。

.github/workflows/ci.yaml
      - name: Cache Rector
        uses: actions/cache@v4
        with:
          path: ./storage/rector/cache
          key: ${{ runner.os }}-rector-${{ hashFiles('**/composer.lock') }}
          restore-keys: ${{ runner.os }}-rector-
      - name: Run Rector
        run: ./vendor/bin/rector process --dry-run

キャッシュ部分は上述で説明した通りなので省きます。
./vendor/bin/rector process --dry-run ここではアップグレードとリファクタリングのチェックを行なっています。

RectorはPHPのコードをアップグレードとリファクタリングを行ってくれるツールです。
Laravel用にカスタマイズされたrector-laravelが提供されているのでこちらを利用しています。

.github/workflows/ci.yaml
      - name: Cache PHPStan
        uses: actions/cache@v4
        with:
          path: ./storage/phpstan
          key: ${{ runner.os }}-phpstan-${{ hashFiles('**/composer.lock') }}
          restore-keys: ${{ runner.os }}-phpstan-
      - name: Run PHPStan
        run: ./vendor/bin/phpstan analyze

キャッシュ部分は上述で説明した通りなので省きます。
./vendor/bin/phpstan analyze ここでは静的解析のチェックを行なっています。

PHPStanはPHPコードの静的解析ツールです。
Laravel用にカスタマイズされたlarastanが提供されているのでこちらを利用しています。

.github/workflows/ci.yaml
      - name: Cache Pest
        uses: actions/cache@v4
        with:
          path: ./storage/pest/cache
          key: ${{ runner.os }}-pest-${{ hashFiles('**/composer.lock') }}
          restore-keys: ${{ runner.os }}-pest-
      - name: Run Pest
        env:
          SESSION_DRIVER: array
          DB_CONNECTION: sqlite
          DB_DATABASE: ":memory:"
        run: ./vendor/bin/pest --parallel --cache-directory storage/pest/cache

キャッシュ部分は上述で説明した通りなので省きます。
./vendor/bin/pest --parallel --cache-directory storage/pest/cache では自動テストを行っています。

Pestはシンプルにテストを書けるテスティングフレームワークです。
PHPUnitのラッパーライブラリなため、PHPUnitのまま書き方でもテストは書けるので移行しやすいです。

並列テストが標準搭載されていたり、アーキテクチャテストを行える点が気に入っています。

また、2.0から数多くの便利オプションが提供されています。

実行速度

Laravelプロジェクトインストール時点だと、
キャッシュがない場合のci-backendは80秒ほどで完了します。
キャッシュがある場合のci-backendは50秒ほどで完了します。
キャッシュがない場合のci-frontendは15秒ほどかかります。
キャッシュがある場合のci-frontendは10秒ほどで完了します。

ci-backendとci-frontendを並列で実行させていますが、プロジェクトの初期状態だとあまり効果的ではないです。
ci-frontendが育ってくると効果を発揮していくでしょう。

各種ツールの設定ファイル等

Pint

その他

いつか書きます。
需要がありそうなら早めに書きます。

2
1
7

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
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?