本文へ移動
cccskills
無料GitHub で公開

eloquent-patterns

Eloquent ORM best practices: query builders, scopes, relations, N+1 prevention, batch operations, soft deletes, model events, raw queries when needed. Apply when: writing or reviewing Eloquent queries and model interactions. Activated automatically by laravel-plugin/stack.md as a convention skill for the development phase.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md7.0 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Eloquent Patterns

Patterns for working with Eloquent that catch the common pitfalls (N+1, mass assignment, race conditions on counts, raw SQL injection).

1. N+1 prevention

The single most common Eloquent performance bug.

Problem

$users = User::all();
foreach ($users as $user) {
    echo $user->subscription->plan;  // 1 query per user → N+1
}

Solution: eager loading

$users = User::with('subscription')->get();
foreach ($users as $user) {
    echo $user->subscription?->plan;  // No extra queries
}

Nested eager loading

$users = User::with('subscription.invoices')->get();

Selective columns (further optimization)

$users = User::with(['subscription:id,user_id,plan,status'])->get();

Detection in code review

Look for any foreach or array_map over an Eloquent collection followed by ->relation access without with() upstream. That's N+1 90% of the time.

2. Scopes for reusable query logic

Encapsulate common query fragments as model scopes.

class Subscription extends Model
{
    public function scopeActive(Builder $query): void
    {
        $query->where('status', 'active')
              ->where('ends_at', '>=', now());
    }

    public function scopeForUser(Builder $query, User $user): void
    {
        $query->where('user_id', $user->id);
    }
}

// Usage:
$activeForUser = Subscription::active()->forUser($user)->get();

Benefits:

  • DRY — definition lives once.
  • Testable — scopes can be unit-tested.
  • Self-documenting — Subscription::active() reads better than where('status', 'active')->where(...).

3. Mass assignment safety

class Subscription extends Model
{
    protected $fillable = [
        'user_id',
        'plan',
        'status',
        'starts_at',
    ];
}

Then:

Subscription::create($request->validated());  // ✅ safe

Never:

class Subscription extends Model
{
    protected $guarded = [];  // ❌ — opens door to mass-assigning is_admin, etc.
}

4. Relations: declare return types

public function user(): BelongsTo
{
    return $this->belongsTo(User::class);
}

public function invoices(): HasMany
{
    return $this->hasMany(Invoice::class);
}

public function paymentMethod(): MorphOne
{
    return $this->morphOne(PaymentMethod::class, 'payable');
}

Return types let the IDE / static analyzers understand the relation, and they catch errors at boot time, not runtime.

5. Counts and aggregates

Lazy count (incurs a query each time)

$count = $user->subscriptions()->count();  // 1 query

Eager count

$users = User::withCount('subscriptions')->get();
foreach ($users as $user) {
    echo $user->subscriptions_count;  // 0 extra queries
}

Eager sum / avg / max

$users = User::withSum('subscriptions', 'amount')
             ->withMax('invoices', 'created_at')
             ->get();

6. Batch operations

Inserting many rows

// Slow: triggers events per row
foreach ($rows as $row) {
    Model::create($row);
}

// Fast: single query, NO events fired
Model::insert($rows);

// Compromise: chunked + events
Model::query()->chunkById(500, function ($chunk) {
    // process
});

If you need events (timestamps, observers), use Model::insert() only when you've consciously accepted that timestamps and events won't fire.

Updating many rows

// One query, no events
Subscription::where('status', 'pending')
    ->where('created_at', '<', now()->subDays(7))
    ->update(['status' => 'expired']);

// Per-row with events (slower)
Subscription::where(...)->each(function ($s) {
    $s->update(['status' => 'expired']);
});

7. Soft deletes

use Illuminate\Database\Eloquent\SoftDeletes;

class Subscription extends Model
{
    use SoftDeletes;
}

Implications:

  • Migration must have $table->softDeletes();.
  • Queries auto-exclude soft-deleted: Subscription::all().
  • To include them: Subscription::withTrashed()->get().
  • To get only soft-deleted: Subscription::onlyTrashed()->get().
  • To restore: $subscription->restore().
  • Force delete: $subscription->forceDelete().

Watch out: relations don't auto-cascade soft deletes. Use observers or explicit logic.

8. Race conditions on counts/aggregates

Concurrent updates to a counter cause classic race bugs. Use atomic operations.

Wrong

$user = User::find(1);
$user->credits = $user->credits + 10;  // race
$user->save();

Right (atomic via SQL)

User::where('id', 1)->increment('credits', 10);

Or for a balance debit with overflow protection:

$affected = User::where('id', 1)
    ->where('credits', '>=', 10)
    ->decrement('credits', 10);

if ($affected === 0) {
    throw new InsufficientCreditsException();
}

9. Raw queries — when and how

Sometimes Eloquent isn't enough (CTEs, window functions, complex aggregations). Use raw queries with bindings.

Right

DB::select(
    'SELECT * FROM subscriptions WHERE user_id = ? AND status = ?',
    [$userId, 'active']
);

Wrong (SQL injection)

DB::select("SELECT * FROM subscriptions WHERE user_id = $userId");  // ❌
DB::raw("user_id = $userId")  // ❌

Right with whereRaw + bindings

Subscription::whereRaw('amount > ?', [100])->get();

Right with selectRaw for aggregates

$stats = Subscription::query()
    ->selectRaw('plan, COUNT(*) as count, AVG(amount) as avg_amount')
    ->groupBy('plan')
    ->get();

10. Model events and observers

For cross-cutting behaviors (audit logs, cache invalidation, notifications), use observers — not boot methods inline.

class SubscriptionObserver
{
    public function created(Subscription $subscription): void
    {
        AuditLog::record('subscription.created', $subscription);
    }

    public function deleting(Subscription $subscription): void
    {
        // Cleanup before deletion
    }
}

Register in AppServiceProvider::boot():

Subscription::observe(SubscriptionObserver::class);

11. Checklist before merging Eloquent changes

  • No N+1: every foreach over an Eloquent collection has with() upstream
  • $fillable set on every new/modified model
  • All datetime/decimal columns in $casts
  • No DB::raw() with string concatenation
  • Atomic operations (increment, decrement) for counters under concurrency
  • Soft deletes set up correctly (migration + trait) if used
  • Observers used for cross-cutting concerns instead of model boot methods
  • Eager-loaded relations used in views are declared in with() of the controller/action

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Angular 18-21 project structure, standalone components vs NgModule, control flow (@if/@for/@switch + *ngIf/*ngFor legacy), decorators, dependency injection (inject() function), lifecycle hooks, pipes, Angular Universal SSR pointer. Use this skill to: - Detect project style (standalone vs NgModule) and apply matching patterns. - Pick correct decorators and DI approach. - Use modern control flow (@if/@for/@switch) in Angular 17+ projects. - Apply `inject()` function over constructor injection where appropriate. - Wire bootstrap correctly (bootstrapApplication for standalone, AppModule for legacy). Do NOT use this skill for: - State management (see angular-state-and-rx). - Routing (see angular-routing). - Forms (see angular-forms). - Testing (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Angular forms: Reactive Forms (preferred — typed FormGroup/FormControl since Angular 14, FormBuilder, custom + async validators, FormArray, multi-step) and Template-driven (`[(ngModel)]` + FormsModule). Validation strategies, server error mapping, accessibility. Use this skill to: - Build Reactive Forms with typed FormGroup/FormControl. - Use FormBuilder для concise syntax. - Implement custom synchronous and async validators. - Wire FormArray for dynamic field lists. - Map server errors back to form fields. - Pick Reactive vs Template-driven (prefer Reactive). Do NOT use this skill for: - General conventions (see angular-conventions). - State management beyond forms (see angular-state-and-rx). - Routing (see angular-routing). - Testing forms (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Angular Router (built-in `@angular/router`) — route configuration for standalone and NgModule projects, functional guards (Angular 14.1+), lazy loading, route resolvers, typed params via signals/observables, programmatic navigation, route data and meta. Use this skill to: - Configure routes (standalone-style or NgModule-style). - Use functional guards (canActivate as function, preferred over class-based in 17+). - Lazy-load components or feature modules. - Implement auth guards via route meta + functional guards. - Read params/queries via `inject(ActivatedRoute)` + signals or RxJS. Do NOT use this skill for: - General conventions (see angular-conventions). - State management (see angular-state-and-rx). - Forms (see angular-forms). - Testing routes (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

State management for Angular 18-21: signals (signal/computed/effect), services-as-state, NgRx Store + Effects + Selectors, NgRx Component Store, NgRx Signals (newer signal-based store). RxJS essentials — operators, async pipe, takeUntilDestroyed, signal/observable interop. Use this skill to: - Pick the right state tool (signals / services / NgRx variant / vue-query equivalent). - Use signals correctly (signal/computed/effect — when each). - Build a Pinia-style service-as-state singleton. - Set up NgRx Store + Effects + Selectors. - Use RxJS without leaking subscriptions (async pipe, takeUntilDestroyed, Subject patterns). - Bridge signals ↔ observables via toSignal / toObservable. Do NOT use this skill for: - General Angular conventions (see angular-conventions). - Routing state (see angular-routing). - Form state (see angular-forms). - Testing state (see angular-testing).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Testing Angular 18-21: TestBed, component harnesses (@angular/cdk/testing), Karma+Jasmine (default historical) vs Jest (jest-preset-angular, modern), Angular Testing Library (RTL-style). HttpClient mocking via HttpTestingController. NgRx Effects testing. Cypress / Playwright e2e. Use this skill to: - Detect runner (Karma+Jasmine vs Jest) and configure correctly. - Write component tests with TestBed. - Use component harnesses for Material / custom UI components. - Mock HttpClient via provideHttpClientTesting + HttpTestingController. - Test signal-based inputs with componentRef.setInput(). - Test NgRx Effects with provideMockActions. Do NOT use this skill for: - General Angular conventions (see angular-conventions). - Routing patterns broadly (see angular-routing — covers testing routes briefly). - Form patterns broadly (see angular-forms).

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

Shared conventions for every SDLC development-phase architect agent: hard rules, code quality bar, workflow steps (superpowers invocation, spec reading, codebase exploration, verification), and the report/compact-summary contract. Architects load this skill first, then apply their stack-specific instructions on top.

日本語の概要は準備中です。原文の説明を表示しています。

AratKruglik/claude-sdlc362026年9月21日 更新

AratKruglik のスキルをすべて見る

このスキルの問題を報告する