# Laravel Expert

> Laravel/PHP gotchas and decision criteria. Covers N+1 prevention, Eloquent traps, and migration safety. Use when this capability is needed.

- Skill: `tomevault-io/laravel-expert` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/laravel-expert`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/laravel-expert/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/laravel-expert

---


> **AI-consumed reference.** Optimized for Claude to read during execution.
> Human-readable explanation: see [docs/architecture/HIERARCHICAL_PLANNING.md](../../../docs/architecture/HIERARCHICAL_PLANNING.md)
> or [docs/getting-started/](../../../docs/getting-started/) depending on topic.


# Laravel Expert — Gotchas & Decisions

Use Context7 for full Laravel docs.

## Key Decisions

```toon
decisions[4]{choice,use_when}:
  Form Request vs inline,"Form Request for reusable validation. Inline for one-off simple checks"
  Service vs Action class,"Service for stateless business logic. Action (single __invoke) for discrete operations"
  Event/Listener vs direct,"Events for decoupling (notifications/logging). Direct for tightly coupled ops"
  Eloquent vs Query Builder,"Eloquent for domain models with relationships. Query Builder for reports/bulk ops"
```

## Gotchas

- N+1: always `with()` eager load. Use `preventLazyLoading()` in AppServiceProvider to catch in dev
- `readonly` DTOs (PHP 8.2+): `readonly class UserData { public function __construct(public string $name) {} }`
- `updateOrCreate` is NOT atomic — race condition possible. Use DB transaction for critical ops
- `$model->save()` returns bool, not the model — don't chain. `Model::create()` returns the model
- Mass assignment: `$fillable` whitelist or `$guarded = []` (never use `$guarded = []` with user input)
- `firstOrFail()` throws `ModelNotFoundException` (404). `first()` returns null silently
- Queue jobs: always implement `ShouldQueue`. Without it, runs synchronously despite `dispatch()`
- `Carbon::now()` in tests: use `$this->travel()` or `Carbon::setTestNow()` for deterministic tests
- Migrations: never edit a deployed migration. Create new migration for changes

---
> Source: [nguyenthienthanh/aura-frog](https://github.com/nguyenthienthanh/aura-frog) — distributed by [TomeVault](https://tomevault.io).
<!-- tomevault:4.0:skill_md:2026-05-26 -->

