Laravelで最小CRUDを作る までは進められたが、その次に何を自動化すべきかで止まりやすい読者向けです。
ゴールは、前記事の Book CRUD に Feature Test、Larastan、Pint、GitHub Actions を足し、composer qa を日常の入口にすることです。
Larastan は Laravel 向けの静的解析拡張、Pint は Laravel 標準のコード整形ツールです。この記事では PHPUnit と合わせて、ローカルと GitHub Actions の両方で回る最小の品質ゲートをそろえます。
補助導線として先に読んでおくと入りやすい記事:
前提環境
- Windows 11
- WSL2(Ubuntu)
- VS Code(Remote - WSL)
- Docker Desktop(WSL 連携有効)
- GitHub.com アカウント(リポジトリは 6 章で作成)
以降のコマンドは、特記がない限り WSL 側ターミナルで実行します。
開始位置は、前記事で作った laravel-minimal-crud-demo です。
1. ゴールと非対象
この記事で到達する状態:
BookCRUD に対する Feature Test を置けるLarastanとPintをローカル品質ゲートへ追加できるcomposer qaで整形チェック、静的解析、テストをまとめて回せる- GitHub Actions で同じ gate を自動実行できる
今回は「最初の品質ゲートを敷く」ことに絞ります。
coverage、baseline、Pest への移行、docker compose をそのまま使う CI は今回の外側です。
ここで扱わない内容:
- Pest への移行
baseline/ignoreErrorsの本格運用- coverage や Mutation Testing
- PostgreSQL などのサービス付き CI
- 複数 PHP バージョン matrix
- 認可、N+1、トランザクション設計の深掘り
2. 前記事の CRUD プロジェクトを起点にする
開始位置は前記事のディレクトリです。
cd ~/projects/laravel-minimal-crud-demo
code .
docker compose up -d
新しく Laravel を作り直す話ではありません。
追加対象は tests/、phpstan.neon、.github/workflows/ci.yml、composer scripts です。
Laravel 13 の新規アプリには、最初から phpunit/phpunit と laravel/pint が入っています。
今回あとから追加する中心は Larastan です。既存の 12 -> 13 アップグレード案件なら、phpunit/phpunit:^12.0 まで追従しているかも composer.json で確認してください。
前記事どおり Route::redirect('/', '/books') にしているなら、既定の tests/Feature/ExampleTest.php はそのままでは合いません。
次に差し替える対象が、この tests/Feature/ExampleTest.php です。
3. まず Feature Test を置く
品質ゲートの土台は Feature Test。
Laravel の phpunit.xml には DB_CONNECTION=sqlite と DB_DATABASE=:memory: が入っているので、RefreshDatabase を使ってもローカルの database/database.sqlite は使いません。
先に削除するのは、意味の薄いダミー Unit Test です。
rm tests/Unit/ExampleTest.php
touch tests/Unit/.gitkeep
.gitkeep を置くのは、ファイルがなくなったディレクトリを git が追跡しなくなり、CI で tests/Unit が存在しないとして PHPUnit が止まるのを防ぐためです。
tests/Feature/ExampleTest.php は / の redirect 確認用に置き換えます。
tests/Feature/ExampleTest.php:
<?php
namespace Tests\Feature;
use Tests\TestCase;
class ExampleTest extends TestCase
{
public function test_the_application_redirects_to_books(): void
{
$response = $this->get('/');
$response->assertRedirect('/books');
}
}
続いて追加するのが、CRUD の主要導線を守る BookCrudTest です。
tests/Feature/BookCrudTest.php:
<?php
namespace Tests\Feature;
use App\Models\Book;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class BookCrudTest extends TestCase
{
use RefreshDatabase;
public function test_books_index_displays_saved_books(): void
{
Book::query()->create([
'title' => 'Laravel実践',
'author' => 'Sato',
'price' => 2800,
]);
$response = $this->get('/books');
$response->assertOk();
$response->assertSee('Laravel実践');
}
public function test_store_validates_and_redirects_back_on_error(): void
{
$response = $this->from('/books/create')->post('/books', [
'title' => '',
'author' => 'Sato',
'price' => -1,
]);
$response->assertRedirect('/books/create');
$response->assertSessionHasErrors(['title', 'price']);
}
public function test_store_update_and_destroy_work(): void
{
$storeResponse = $this->post('/books', [
'title' => 'Laravel入門',
'author' => 'Suzuki',
'price' => 2400,
]);
$storeResponse->assertRedirect('/books');
$this->assertDatabaseHas('books', [
'title' => 'Laravel入門',
'author' => 'Suzuki',
'price' => 2400,
]);
$book = Book::query()->firstOrFail();
$updateResponse = $this->patch("/books/{$book->id}", [
'title' => 'Laravel入門 改訂版',
'author' => 'Suzuki',
'price' => 2600,
]);
$updateResponse->assertRedirect('/books');
$this->assertDatabaseHas('books', [
'id' => $book->id,
'title' => 'Laravel入門 改訂版',
'price' => 2600,
]);
$deleteResponse = $this->delete("/books/{$book->id}");
$deleteResponse->assertRedirect('/books');
$this->assertDatabaseMissing('books', [
'id' => $book->id,
]);
}
}
ここで 1 回テストを流します。
docker compose exec app composer test
4 本通れば、ひとまず既知の CRUD 導線は守れている状態。
次に足すのが静的解析です。
4. Larastan を追加して静的解析を通す
Laravel アプリに素の PHPStan をそのまま入れるより、Laravel 向け拡張を含む Larastan のほうが入りやすいです。
まずは dev 依存へ追加します。
docker compose exec app composer require --dev larastan/larastan:^3.0
このコマンドで composer.json と composer.lock が両方更新されます。
6 章でリポジトリを push するとき、この 2 つも一緒にコミットしてください。
次に phpstan.neon を作ります。
phpstan.neon:
includes:
- vendor/larastan/larastan/extension.neon
parameters:
level: 5
paths:
- app
- routes
- tests
解析を流します。
docker compose exec app vendor/bin/phpstan analyse --memory-limit=1G
次の章で composer.json の scripts を整えると、composer analyse として呼べるようになります。
ここで最初に引っかかりやすいのが、Laravel 既定の tests/Unit/ExampleTest.php です。
assertTrue(true) だけのダミーテストは、Larastan から見ると「常に true で意味が薄い」ため、3 章で先に消しました。
app だけでなく routes と tests も含めているのは、実際に壊れやすいのがその 3 つの境目だからです。
Controller の返し方、route の結び方、Feature Test の前提がずれたときに気づきやすくなります。
5. Pint と composer scripts で品質ゲートをまとめる
Pint は Laravel 13 skeleton の既定 dev 依存です。
ここでは composer.json の scripts を足し、毎回同じ入口で回せるようにします。
composer.json の scripts ブロックに次の 4 エントリを追加します。
test と laravel/pint は skeleton に既にあるため追加不要です。larastan/larastan は 4 章で追加済みです。
"analyse": "vendor/bin/phpstan analyse --memory-limit=1G",
"format": "vendor/bin/pint",
"format:check": "vendor/bin/pint --test",
"qa": [
"@composer format:check",
"@composer analyse",
"@composer test"
]
format:check は CI 用で、差分を書き換えずにいまのコードが Pint ルールに合っているかだけを確認します。
最初の確認コマンド:
docker compose exec app composer format:check
前記事どおりに手で BookController を書いていると、最初は ordered_imports で落ちることがあります。
その場合は自動整形を 1 回。
docker compose exec app composer format
次に、整形チェック、静的解析、テストをまとめて流します。
docker compose exec app composer qa
composer qa が通れば、ローカル側の最小品質ゲートは一旦完成です。
壊れたらまずここで止まり、直してからレビューへ回す流れにできます。
Pint が not writable で止まるときは、前記事の LOCAL_UID / LOCAL_GID 設定を見直してください。
プロジェクトを WSL ホーム配下に置いていない場合も、権限ずれが起きやすくなります。
6. GitHub Actions で同じゲートを自動実行する
ローカルで通した gate は、そのまま GitHub Actions へ載せます。
今回は DB サービスを増やさず、GitHub-hosted runner の PHP で回す最小構成です。Feature Test が :memory: の SQLite を使うため、この形で十分です。
まだ GitHub 上にリポジトリを作成していない場合は、gh コマンドで作成して origin を設定します。
git init
git branch -M main
gh repo create laravel-minimal-crud-demo --public
git remote add origin https://github.com/<ユーザー名>/laravel-minimal-crud-demo.git
gh が入っていない場合は、GitHub のウェブ画面からリポジトリを作成し、表示される git remote add コマンドをそのまま実行してください。
以降は、git push origin main でリモートへ送れる状態を前提に進めます。
まずディレクトリを作成します。
mkdir -p .github/workflows
.github/workflows/ci.yml:
name: ci
on:
push:
pull_request:
permissions:
contents: read
jobs:
qa:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
extensions: mbstring, sqlite3, pdo_sqlite
coverage: none
- name: Copy environment file
run: cp .env.example .env
- name: Install dependencies
run: composer install --no-interaction --prefer-dist
- name: Generate app key
run: php artisan key:generate
- name: Run Pint check
run: composer format:check
- name: Run Larastan
run: composer analyse
- name: Run PHPUnit
run: composer test
ポイント:
extensions: sqlite3, pdo_sqliteは Feature Test がメモリ上の SQLite を使うために必要です。欠けているとcomposer testで DB 接続時に止まりますcp .env.example .envは Artisan コマンドがAPP_KEYなどを読む前提で.envを要求するためですphp artisan key:generateは.envのAPP_KEYが空の状態だと暗号化処理で止まるため、依存インストールの直後に実行しますcomposer install --prefer-distはcomposer.lockを基準に依存を再現します。composer.lockは必ず一緒にコミットしてくださいphp-versionはチームで固定している運用版に合わせてください。ここでは Laravel 13 の最低要件にそろえて8.3を基準にしています
ファイルを追加したら、関連ファイルをまとめてコミットして push します。
git add .
git commit -m "Add GitHub Actions CI workflow"
git push origin main
GitHub の Actions タブを開き、ci workflow が起動していることを確認します。各 step が緑になれば完了です。
CI が落ちた場合は、赤くなっている step を開いてログを読みます。step 名とローカル再実行コマンドの対応は次のとおりです。
| step 名 | ローカルで再実行するコマンド |
|---|---|
Run Pint check | docker compose exec app composer format:check |
Run Larastan | docker compose exec app vendor/bin/phpstan analyse --memory-limit=1G |
Run PHPUnit | docker compose exec app composer test |
7. どこまで自動化し、どこから人間レビューに残すか
今回の gate で先に落とせるもの:
- import 順や空白崩れなど、Pint で拾える整形差分
- Laravel 文脈の明らかな型ずれや無意味なテスト
- CRUD の主要導線が壊れたときの振る舞い崩れ
人間レビューへ残すもの:
- 認可や policy の抜け
- N+1、index 設計、トランザクション境界
- バリデーションメッセージや UI 文言の妥当性
- 例外処理、ログ、監視、運用時の戻し方
機械で拾えるものを先に落とすと、人間レビューは「この設計でよいか」「副作用は無いか」に時間を使いやすくなります。
品質ゲートはレビューの代替ではなく、レビューを設計寄りに寄せる前処理です。
8. よくある詰まりと次の一歩
composer format が書き込めないとき:
- プロジェクトが WSL ホーム配下にあるか確認する
LOCAL_UID/LOCAL_GIDを export してからdocker compose up -dしたか確認する
composer analyse でダミーテストが引っかかるとき:
tests/Unit/ExampleTest.phpが残っていないか確認するcomposer dump-autoloadを実行してからもう一度流す
GitHub Actions で SQLite 周りのエラーが出るとき:
setup-phpのextensionsにsqlite3とpdo_sqliteが入っているか確認するcp .env.example .envとphp artisan key:generateを飛ばしていないか確認する