laravel-best-practices について
Laravel 13 アプリケーションを開発・保守する人向けのプラクティス集です。アーキテクチャ、Eloquent、コントローラー、バリデーション、セキュリティのルールを例とともに紹介します。
主な機能
- 01アーキテクチャと構成の指針
- 02Eloquent とデータベースの実践
- 03コントローラーとルーティングのパターン
- 04バリデーションとリクエストのルール
- 05一括代入のセキュリティ指針
活用シーン
- 01Laravel アプリの構成設計
- 02N+1 データベースクエリの削減
- 03フォームリクエストのバリデーション実装
- 04モデルの一括代入リスクへの対処
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ asyrafhussin/ agent-skills --skill laravel-best-practicesインストール後、Claude Code で/laravel-best-practicesを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
AGENTS.mdmetadata.jsonREADME.mdrules/_sections.md_template.mdarch-action-classes.mdarch-dto-pattern.mdarch-event-driven.mdarch-feature-folders.mdarch-queue-routing.mdarch-repository-pattern.mdarch-service-classes.mdarch-value-objects.mdcontroller-api-resources.mdcontroller-dependency-injection.mdcontroller-form-requests.mdcontroller-middleware.mdcontroller-resource-controllers.mdcontroller-resource-methods.mdcontroller-single-action.mdeloquent-accessors-mutators.mdeloquent-casts.mdeloquent-chunking.mdeloquent-eager-loading.mdeloquent-model-events.mdeloquent-pruning.mdeloquent-query-scopes.mdeloquent-relationships.mdeloquent-soft-deletes.mdeloquent-vector-search.md詳細
Laravel 13 のベストプラクティス
Laravel 13 アプリケーション向けの包括的なベストプラクティスガイドです。スケーラブルで保守しやすい Laravel アプリケーションを構築するための、7 つのカテゴリにわたる 31 のルールを収録しています。
適用する場面
以下を行う際に、これらのガイドラインを参照してください。
- コントローラー、モデル、サービスの作成
- マイグレーションとデータベースクエリの作成
- バリデーションとフォームリクエストの実装
- Laravel を使った API の構築
- Laravel アプリケーションの構成
優先度別のルールカテゴリ
| 優先度 | カテゴリ | 影響度 | プレフィックス |
|---|---|---|---|
| 1 | アーキテクチャと構造 | CRITICAL | arch- |
| 2 | Eloquent とデータベース | CRITICAL | eloquent- |
| 3 | コントローラーとルーティング | HIGH | controller-, ctrl- |
| 4 | バリデーションとリクエスト | HIGH | validation-, valid- |
| 5 | セキュリティ | HIGH | sec- |
| 6 | パフォーマンス | MEDIUM | perf- |
| 7 | API 設計 | MEDIUM | api- |
クイックリファレンス
1. アーキテクチャと構造(CRITICAL)
arch-service-classes- ビジネスロジックをサービスに切り出すarch-action-classes- 単一の目的を持つアクションクラスarch-repository-pattern- リポジトリを使うべき場面arch-dto-pattern- データ転送オブジェクトarch-value-objects- ドメイン概念をカプセル化するarch-event-driven- イベントとリスナーで疎結合にするarch-feature-folders- ドメイン/機能ごとに整理するarch-queue-routing- ジョブキューのルーティングを一元化する(Laravel 13 以降)
2. Eloquent とデータベース(CRITICAL)
eloquent-eager-loading- N+1 クエリを防ぐeloquent-chunking- 大規模なデータセットを処理するeloquent-query-scopes- 再利用可能なクエリロジックeloquent-model-events- 副作用にはオブザーバーを使うeloquent-relationships- リレーションシップを適切に定義するeloquent-casts- 属性を自動的にキャストするeloquent-accessors-mutators- 属性を変換するeloquent-soft-deletes- 復元可能な安全な削除eloquent-pruning- 古いレコードを自動的にクリーンアップするeloquent-vector-search- pgvector によるセマンティック検索(Laravel 13 以降)
3. コントローラーとルーティング(HIGH)
controller-resource-controllers- リソースコントローラーを使うcontroller-single-action- 単一アクションの呼び出し可能なコントローラーcontroller-resource-methods- RESTful なリソースメソッドcontroller-form-requests- フォームリクエストを使うcontroller-api-resources- API レスポンスを変換するcontroller-middleware- ミドルウェアを適切に適用するcontroller-dependency-injection- 依存関係を注入する
4. バリデーションとリクエスト(HIGH)
validation-form-requests- フォームリクエストクラスを使うvalidation-custom-rules- カスタムルールを作成するvalidation-conditional-rules- 条件付きバリデーションvalidation-array-validation- ネストされた配列を検証するvalidation-after-hooks- 複雑なバリデーションロジック
5. セキュリティ(HIGH)
sec-mass-assignment- 一括代入から保護する
6. パフォーマンス(MEDIUM)
このカテゴリには、まだルールファイルがありません。
7. API 設計(MEDIUM)
このカテゴリには、まだルールファイルがありません。
重要なパターン
フォームリクエストを使用するコントローラー
<?php
namespace App\Http\Controllers;
use App\Http\Requests\StorePostRequest;
use App\Http\Requests\UpdatePostRequest;
use App\Models\Post;
use Illuminate\Http\RedirectResponse;
class PostController extends Controller
{
public function store(StorePostRequest $request): RedirectResponse
{
// Validation happens automatically
$validated = $request->validated();
$post = Post::create($validated);
return redirect()
->route('posts.show', $post)
->with('success', 'Post created successfully.');
}
public function update(UpdatePostRequest $request, Post $post): RedirectResponse
{
$post->update($request->validated());
return redirect()
->route('posts.show', $post)
->with('success', 'Post updated successfully.');
}
}
フォームリクエストクラス
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StorePostRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user()->can('create', Post::class);
}
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:255'],
'body' => ['required', 'string', 'min:100'],
'category_id' => ['required', 'exists:categories,id'],
'tags' => ['nullable', 'array'],
'tags.*' => ['exists:tags,id'],
'published_at' => ['nullable', 'date', 'after:now'],
];
}
public function messages(): array
{
return [
'body.min' => 'The post body must be at least 100 characters.',
];
}
}
サービスクラスのパターン
<?php
namespace App\Services;
use App\Models\User;
use App\Models\Post;
use App\Events\PostPublished;
use Illuminate\Support\Facades\DB;
class PostService
{
public function __construct(
private readonly NotificationService $notifications,
) {}
public function publish(Post $post): Post
{
return DB::transaction(function () use ($post) {
$post->update([
'published_at' => now(),
'status' => 'published',
]);
event(new PostPublished($post));
$this->notifications->notifyFollowers($post->author, $post);
return $post->fresh();
});
}
}
Eloquent モデル
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Builder;
class Post extends Model
{
use HasFactory;
protected $fillable = [
'title',
'slug',
'body',
'category_id',
'published_at',
];
protected $casts = [
'published_at' => 'datetime',
];
// Relationships
public function author(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
public function category(): BelongsTo
{
return $this->belongsTo(Category::class);
}
public function tags(): BelongsToMany
{
return $this->belongsToMany(Tag::class)->withTimestamps();
}
// Scopes
public function scopePublished(Builder $query): Builder
{
return $query->whereNotNull('published_at')
->where('published_at', '<=', now());
}
public function scopeByCategory(Builder $query, int $categoryId): Builder
{
return $query->where('category_id', $categoryId);
}
// Accessors & Mutators
protected function title(): Attribute
{
return Attribute::make(
set: fn (string $value) => ucfirst($value),
);
}
}
マイグレーションのベストプラクティス
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->foreignId('category_id')->constrained()->cascadeOnDelete();
$table->string('title');
$table->string('slug')->unique();
$table->text('body');
$table->timestamp('published_at')->nullable();
$table->timestamps();
// Indexes for common queries
$table->index(['user_id', 'published_at']);
$table->index('category_id');
});
}
public function down(): void
{
Schema::dropIfExists('posts');
}
};
イーガーローディング
// N+1 Problem
$posts = Post::all();
foreach ($posts as $post) {
echo $post->author->name; // Query per post
}
// Eager loading — only 3 queries total
$posts = Post::with(['author', 'category', 'tags'])->get();
foreach ($posts as $post) {
echo $post->author->name; // No additional queries
}
// Nested eager loading
$posts = Post::with([
'author.profile',
'comments.user',
'tags',
])->get();
// Constrained eager loading
$posts = Post::with([
'comments' => fn ($query) => $query->latest()->limit(5),
])->get();
使用方法
詳しい説明とコード例については、個々のルールファイルをお読みください。
rules/arch-service-classes.md
rules/eloquent-eager-loading.md
rules/validation-form-requests.md
rules/_sections.md
各ルールファイルには、以下が含まれています。
- メタデータ(タイトル、影響度、タグ)を含む YAML フロントマター
- 重要である理由の簡単な説明
- 解説付きの悪い例
- 解説付きの良い例
- Laravel 13 と PHP 8.3 に固有の背景情報および参考資料
コンパイル済みドキュメント全文
すべてのルールを展開した完全なガイドについては、AGENTS.md をご覧ください。
コメント
Markdown 対応