---
model_tier: medium
name: eloquent
description: "Use when writing Eloquent models, relationships, scopes, or queries via Model:: — 'fetch users with their orders'. NOT for PHPStan output, non-Eloquent services, or raw SQL questions."
personas:
  - eloquent-tamer
domain: engineering
workspaces:
  - engineering
packs:
  - laravel
trust:
  level: professional
install:
  default: false
  removable: true
---

# eloquent

## When to use

Use when creating/modifying Eloquent models, relationships, scopes, or writing database queries.

Do NOT use when:
- Schema design or query optimization (use `database` skill)
- Creating migrations only (use `laravel-migration` skill)

## Procedure: Create or modify a model

### Step 0: Inspect

1. Check existing models — match property declarations, cast patterns, relationship style, scope naming.
2. Check for repositories — some projects use repository interfaces.
3. Understand the schema — check migrations for column names, types, constraints.
4. Check for multi-tenancy — which `$connection` to use?

### Step 1: Read attribute access style

Read `eloquent.access_style` from `.agent-settings.yml`. Default: `getters_setters`.

| Value | Inside model | Outside model |
|---|---|---|
| `getters_setters` | `getAttribute()` / `setAttribute()` | Typed getters/setters |
| `get_attribute` | `getAttribute()` / `setAttribute()` | Same |
| `magic_properties` | `$this->column` | `$model->column` |

→ See guideline `php/eloquent.md` and `php-coding` rule for full conventions.

### Step 2: Build the model

1. Set `$connection`, `$table`, `$fillable`/`$guarded`, `$casts`, `$attributes` (defaults).
2. Add typed getters + fluent setters for each attribute (if `getters_setters` style).
3. Add relationship getter above each relationship method.
4. Add scopes for reusable query constraints.
5. Register Observer via `#[ObservedBy]` — never use `booted()`.

### Step 3: Write queries

- Always eager load relationships accessed in loops: `->with(['customer'])`.
- Always paginate list queries: `->paginate(15)`.
- Use `exists()` over `count() > 0`.
- Use `chunk()` / `lazy()` for large datasets.
- Constrain eager loads when only a subset is needed.

## Conventions

→ See guideline `php/eloquent.md` for getter/setter examples, relationship getters, observers, casts.
→ See guideline `php/database.md` for indexing, transactions, migrations.

### Validate

- Run PHPStan on model — must pass.
- Verify no N+1: check that all relationship access in loops uses eager loading.
- Confirm typed getters exist for every relationship.
- Run affected tests — must pass.

## Output format

1. Model class with typed getters/setters, relationships, and observer
2. Migration file if schema changes are needed

## Gotcha

- Never access relationships via magic properties — always use typed getter.
- N+1 queries are the #1 performance issue — always eager load.
- `getAttribute()` returns `mixed` — cast or type-check the result.
- `$model->save()` can silently fail without fillable/guarded config.

## Do NOT

- Do NOT put business logic in models — delegate to services.
- Do NOT access relationships in loops without eager loading.
- Do NOT use `Model::all()` without pagination on list endpoints.
- Do NOT use `booted()` for lifecycle hooks — use Observers with `#[ObservedBy]`.

## Auto-trigger keywords

- Eloquent
- model
- relationship
- scope
- accessor
- mutator
