目的
Laravel Sail環境でのCIの設定はプロジェクトの中ではとても重要です。
CI上でSail自体をビルドすると多くの時間を要するため、より効率的な方法を共有します。
(Laravel Sailと銘打ちましたが、結局Laravel SailはGitHub Actions上で使わないほうが高速でした)
関連記事
.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
ワークフロー解説
設定の羅列だけでは、何をしているかわからないと思うので各行それぞれ解説します。
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上の画面からワークフローを手動実行できる
- 動作確認したい時にはあると便利です
- workflow_dispatch があるとGitHub上の画面からワークフローを手動実行できる
- env: 今回はMySQLの接続情報を使うために利用
MySQLパスワードはCI実行で使うものなので平文で良いかなと思いました。
ポイントは DB_HOST: 127.0.0.1
です。
SailのDBではなくGitHub Actions上のDBコンテナを指すためです。
jobs:
ci-backend:
# ...
ci-frontend:
# ...
PHPとNodeが必要なものと分かれるので、並列で実行してCI時間の短縮を図っています。
ci-backendのジョブから解説します。
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のジョブステップは多いので分割して解説します。
- 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自体をビルドすると時間がかかってしまうからです。
- 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を設定- https://docs.github.com/ja/actions/writing-workflows/choosing-what-your-workflow-does/passing-information-between-jobs
-
jobs.<job_id>.outputs
といった感じで取得できる
- 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: 色付きの出力を無効
- 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.json
と composer.lock
の記述が正しいか検証しています。
- 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
を行なっています。
- 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のラッパーライブラリのコードフォーマッターです。
- 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が提供されているのでこちらを利用しています。
- 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が提供されているのでこちらを利用しています。
- 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
その他
いつか書きます。
需要がありそうなら早めに書きます。