# Laravel Models

> Eloquent model patterns and database layer. Use when creating or modifying models, relationships, casts, or observers.

- **Type:** Skill
- **Install:** `agentstack add skill-noppu-labs-ai-toolkit-laravel-models`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [noppu-labs](https://agentstack.voostack.com/s/noppu-labs)
- **Installs:** 0
- **Category:** [Databases](https://agentstack.voostack.com/c/databases)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [noppu-labs](https://github.com/noppu-labs)
- **Source:** https://github.com/noppu-labs/ai-toolkit/tree/main/laravel/skills/laravel-models
- **Website:** https://noppu-labs.github.io/ai-toolkit/

## Install

```sh
agentstack add skill-noppu-labs-ai-toolkit-laravel-models
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Laravel Models

Models represent database tables and domain entities.

**Related guides:**
- [Query Builders](../laravel-query-builders/SKILL.md) - Custom query builders (not scopes)
- [DTOs](../laravel-dtos/SKILL.md) - Casting model JSON columns to DTOs

## Philosophy

Models should:
- Use **custom query builders** (not local scopes) - see [Query Builders](../laravel-query-builders/SKILL.md)
- Define relationships
- Define casts
- Contain simple accessors/mutators
- **NOT contain business logic** (that belongs in Actions)
- **Prefer PHP attributes** over properties/methods where available (Laravel 12+ for `#[UseEloquentBuilder]`, Laravel 13+ for `#[Table]`, `#[ObservedBy]`, `#[UsePolicy]`, `#[UseFactory]`, etc.)

## Basic Model Structure

```php
 OrderStatus::class,
            'total' => 'integer',
        ];
    }

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

    public function items(): HasMany
    {
        return $this->hasMany(OrderItem::class);
    }
}
```

## Casts

**Define casts for type safety:**

```php
protected function casts(): array
{
    return [
        'status' => OrderStatus::class,         // Enum
        'total' => 'integer',                   // Integer
        'is_paid' => 'boolean',                 // Boolean
        'metadata' => OrderMetadataData::class, // DTO
        'completed_at' => 'datetime',           // Carbon
        'tags' => 'array',                      // JSON array
    ];
}
```

## Model Methods

**Simple helper methods** are acceptable:

```php
class Order extends Model
{
    public function isPending(): bool
    {
        return $this->status === OrderStatus::Pending;
    }

    public function isCompleted(): bool
    {
        return $this->status === OrderStatus::Completed;
    }

    public function canBeCancelled(): bool
    {
        return $this->isPending() || $this->status === OrderStatus::Processing;
    }
}
```

**But NOT business logic:**

```php
// ❌ Bad - business logic in model
class Order extends Model
{
    public function cancel(): void
    {
        DB::transaction(function () {
            $this->update(['status' => OrderStatus::Cancelled]);
            $this->refundPayment();
            $this->notifyCustomer();
        });
    }
}

// ✅ Good - business logic in action
class CancelOrderAction
{
    public function __invoke(Order $order): Order
    {
        return DB::transaction(function () use ($order) {
            $order->update(['status' => OrderStatus::Cancelled]);
            resolve(RefundPaymentAction::class)($order);
            resolve(NotifyCustomerAction::class)($order);
            return $order;
        });
    }
}
```

## Model Observers

**For model lifecycle hooks:**

```php
uuid) {
            $order->uuid = Str::uuid();
        }
    }

    public function created(Order $order): void
    {
        // Dispatch event, queue job, etc.
    }

    public function updating(Order $order): void
    {
        // Before update
    }

    public function updated(Order $order): void
    {
        // After update
    }

    public function deleted(Order $order): void
    {
        // After delete
    }
}
```

**Register in AppServiceProvider:**

```php
use App\Models\Order;
use App\Observers\OrderObserver;

public function boot(): void
{
    Order::observe(OrderObserver::class);
}
```

## Model Concerns (Traits)

**Extract reusable behavior:**

**[View full implementation →](references/HasUuid.php)**

**Use in models:**

```php
class Order extends Model
{
    use HasUuid;
}
```

## Route Model Binding

### Implicit Binding

```php
// Route
Route::get('/orders/{order}', [OrderController::class, 'show']);

// Controller - automatically receives Order model
public function show(Order $order) { }
```

### Custom Key

```php
Route::get('/orders/{order:uuid}', [OrderController::class, 'show']);
```

### Custom Resolution

```php
public function resolveRouteBinding($value, $field = null)
{
    return $this->where($field ?? 'id', $value)
        ->where('is_active', true)
        ->firstOrFail();
}
```

## Mass Assignment Protection

**Every model defines an explicit `$fillable` allowlist.** Never call `Model::unguard()` and never use `$guarded = []` — both allow attackers to set fields like `is_admin`, `role`, or `user_id` by adding extra request parameters. See [sec-injection-prevention](../laravel-owasp-security/rules/sec-injection-prevention.md).

### Model Configuration

Prefer the `#[Fillable]` attribute; the `$fillable` property works the same way:

```php
// ✅ Good - explicit allowlist via attribute
#[Fillable([
    'status',
    'total',
    'notes',
])]
class Order extends Model
{
    protected function casts(): array
    {
        return [
            'status' => OrderStatus::class,
        ];
    }
}

// ✅ Also good - explicit allowlist via property
class Order extends Model
{
    protected $fillable = [
        'status',
        'total',
        'notes',
    ];
}

// ❌ Bad - everything becomes mass assignable
class Order extends Model
{
    protected $guarded = [];
}

// ❌ Bad - Model::unguard() in a service provider disables protection globally
```

### What Belongs in $fillable

Only fields the user may submit. Ownership and system-controlled fields (`user_id`, `is_admin`, `published_at`) stay **out** of `$fillable` and are set explicitly:

```php
Order::create([
    ...$request->validated(),
    'user_id' => auth()->id(), // set explicitly, never mass-assigned
]);
```

### Why Explicit $fillable?

- **Security**: The allowlist is the last line of defense when validation misses a field
- **Auditability**: The model documents exactly which fields accept user input
- **Defense in depth**: Pair with `$request->validated()` — never pass `$request->all()` to `create()`/`fill()`/`update()`

Factories and seeders are unaffected — Laravel factories bypass mass assignment protection internally.

## Model Organization

```
app/Models/
├── Order.php
├── User.php
├── Concerns/
│   ├── HasUuid.php
│   ├── BelongsToTenant.php
│   └── Searchable.php
└── Contracts/
    └── Searchable.php
```

## Testing Models

```php
it('only mass assigns fillable attributes', function () {
    $order = Order::factory()->create();

    $order->fill([
        'total' => 1000,
        'user_id' => 999, // not in $fillable — must be ignored
    ]);

    expect($order->total)->toBe(1000)
        ->and($order->user_id)->not->toBe(999);
});

it('casts status to enum', function () {
    $order = Order::factory()->create(['status' => 'pending']);

    expect($order->status)->toBeInstanceOf(OrderStatus::class);
});

it('has user relationship', function () {
    $order = Order::factory()->create();

    expect($order->user)->toBeInstanceOf(User::class);
});
```

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [noppu-labs](https://github.com/noppu-labs)
- **Source:** [noppu-labs/ai-toolkit](https://github.com/noppu-labs/ai-toolkit)
- **License:** MIT
- **Homepage:** https://noppu-labs.github.io/ai-toolkit/

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-noppu-labs-ai-toolkit-laravel-models
- Seller: https://agentstack.voostack.com/s/noppu-labs
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
