Laravel Operations
Facts verified as of 2026-07.
Authoritative reference for Laravel 11+ development: architecture decisions, Eloquent patterns, authentication strategies, queue configuration, and testing approaches.
Architecture Decision Tree
What type of application?
│
├─ Full-stack web (HTML responses)
│ ├─ Simple CRUD, small team → Monolith (Blade + Eloquent directly)
│ │ └─ Use action classes for business logic over 20 lines
│ ├─ Rich interactivity needed → Livewire (server-driven reactivity)
│ │ └─ Add Alpine.js for client-side micro-interactions
│ └─ SPA-like feel, React/Vue team → Inertia.js
│ └─ Keep server-side routing, dump client-side routing overhead
│
├─ API backend (JSON responses)
│ ├─ Single consumer (mobile/SPA) → API-only with Sanctum SPA auth
│ ├─ Multiple consumers / public → RESTful API with token auth
│ └─ Complex graph queries → Consider GraphQL (lighthouse-php/lighthouse)
│
├─ Large team / complex domain
│ ├─ Domain-driven → Modular monolith (app/Modules/{Domain}/)
│ │ ├─ Each module: Models, Actions, Events, Jobs, Http/
│ │ └─ Shared: app/Shared/ for cross-cutting concerns
│ └─ Independent deployability needed → Microservices
│ └─ Use Laravel Octane for high-throughput services
│
└─ What business logic pattern?
├─ Simple CRUD, < 20 lines → Direct Eloquent in controller
├─ Reusable operation (create order, send invoice) → Action class
│ └─ Single public handle() or execute() method
├─ Complex queries, multiple data sources → Repository pattern
│ └─ Interface + Eloquent implementation (enables swapping)
└─ Cross-cutting operations (audit, caching) → Service class
└─ Inject via constructor, bind in ServiceProvider
Action Class vs Repository vs Service
| Pattern |
Use When |
Example |
| Action class |
Single, reusable business operation |
CreateOrderAction, SendInvoiceAction |
| Repository |
Abstract data access, multiple sources |
OrderRepository with EloquentOrderRepository |
| Service |
Orchestrate multiple actions/repos |
OrderService combining payment + inventory |
| Direct Eloquent |
Simple CRUD, < 5 lines in controller |
User::create($data) |
Eloquent Quick Reference
Relationships
| Relationship |
Method |
Foreign Key Convention |
hasOne |
return $this->hasOne(Profile::class) |
profiles.user_id |
hasMany |
return $this->hasMany(Post::class) |
posts.user_id |
belongsTo |
return $this->belongsTo(User::class) |
posts.user_id |
belongsToMany |
return $this->belongsToMany(Role::class) |
role_user pivot |
hasManyThrough |
return $this->hasManyThrough(Post::class, User::class) |
Country → User → Post |
morphTo |
return $this->morphTo() |
{col}_type, {col}_id |
morphMany |
return $this->morphMany(Comment::class, 'commentable') |
Polymorphic |
morphToMany |
return $this->morphToMany(Tag::class, 'taggable') |
Polymorphic pivot |
Eager Loading
// Prevent N+1: always eager load in controllers
$posts = Post::with(['author', 'comments.author', 'tags'])->paginate(15);
// Conditional eager loading (load after retrieval)
$user->load('posts.comments');
$user->loadMissing('posts'); // only if not already loaded
// Eager load counts (no SELECT *)
$posts = Post::withCount('comments')->get();
// Constrained eager loading
$posts = Post::with(['comments' => fn($q) => $q->approved()->latest()])->get();
Query Scopes
// Local scope (reusable query constraint)
public function scopeActive(Builder $query): void
{
$query->where('status', 'active');
}
// Usage: User::active()->get()
// Dynamic scope
public function scopeOfType(Builder $query, string $type): void
{
$query->where('type', $type);
}
// Usage: User::ofType('admin')->get()
Mass Assignment
// Fillable (allowlist - preferred)
protected $fillable = ['name', 'email', 'password'];
// Guarded (denylist - use [] only if you trust all input)
protected $guarded = ['id', 'is_admin'];
// Never set guarded = [] in production code
Artisan Command Cheat Sheet
| Command |
Purpose |
Common Options |
make:model Post -mfs |
Model + migration + factory + seeder |
-c controller, -r resource |
make:controller PostController -r |
Resource controller (7 methods) |
--api skips create/edit |
make:request StorePostRequest |
Form request for validation |
|
make:job ProcessPayment |
Queueable job class |
--sync for sync job |
make:event OrderPlaced |
Event class |
|
make:listener SendOrderConfirmation -e OrderPlaced |
Listener for event |
--queued |
make:notification InvoicePaid |
Notification class |
|
make:policy PostPolicy -m Post |
Policy with model |
|
make:middleware EnsureUserIsAdmin |
HTTP middleware |
|
make:command SendDailyReport |
Custom Artisan command |
|
migrate |
Run pending migrations |
--step for individual |
migrate:rollback |
Roll back last batch |
--step=5 |
migrate:fresh --seed |
Drop all + re-migrate + seed |
|
db:seed |
Run all seeders |
--class=UserSeeder |
tinker |
REPL with app context |
|
route:list |
Show all routes |
--name=api filter |
route:cache |
Cache routes for production |
|
config:cache |
Cache config for production |
|
view:cache |
Pre-compile Blade templates |
|
optimize |
Run all cache commands |
optimize:clear to reset |
queue:work |
Process queue jobs |
--queue=high,default |
queue:listen |
Work + auto-reload on code change |
|
queue:failed |
List failed jobs |
|
queue:retry all |
Retry all failed jobs |
|
schedule:run |
Run due scheduled tasks |
|
schedule:work |
Run scheduler every minute (dev) |
|
key:generate |
Generate APP_KEY |
|
test |
Run PHPUnit/Pest tests |
--filter=UserTest |
test --parallel |
Run tests in parallel |
--processes=4 |
vendor:publish |
Publish package assets/config |
--tag=config |
Authentication Decision Tree
What do you need?
│
├─ SPA (Vue/React) + Laravel API backend
│ └─ Sanctum SPA authentication
│ ├─ Cookie-based (same domain or subdomain)
│ ├─ Csrf-cookie endpoint: GET /sanctum/csrf-cookie
│ └─ No tokens in localStorage (XSS safe)
│
├─ Mobile app or third-party API consumers
│ └─ Sanctum API tokens (Bearer tokens)
│ ├─ createToken($name, $abilities)
│ ├─ Token abilities for fine-grained control
│ └─ Token expiration with token:prune schedule
│
├─ Traditional web app (server-rendered)
│ ├─ Just need auth pages quickly → Breeze
│ │ ├─ Minimal, educational, Blade or Inertia stack
│ │ └─ Install: composer require laravel/breeze --dev
│ ├─ Need teams, 2FA, profile management → Jetstream
│ │ ├─ Livewire or Inertia stack
│ │ └─ Install: composer require laravel/jetstream
│ └─ Need headless auth (API + custom UI) → Fortify
│ ├─ Actions in app/Actions/Fortify/
│ └─ Customize: CreateNewUser, UpdateUserPassword
│
└─ Custom / enterprise
├─ LDAP/SAML → socialiteproviders/saml2
├─ OAuth social login → laravel/socialite
└─ Custom guard → Implement Guard + UserProvider contracts
Sanctum Quick Setup
// config/sanctum.php - stateful domains for SPA
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', 'localhost')),
// API token creation
$token = $user->createToken('mobile-app', ['orders:read', 'orders:write']);
return ['token' => $token->plainTextToken];
// Check token ability
Route::get('/orders', function (Request $request) {
$request->user()->tokenCan('orders:read'); // bool
});
// Protect routes
Route::middleware('auth:sanctum')->group(function () {
// authenticated routes
});
Queue Decision Tree
Queue driver selection:
│
├─ Development / testing
│ └─ sync driver (executes immediately, no worker needed)
│ QUEUE_CONNECTION=sync
│
├─ Small app, no Redis available
│ └─ database driver
│ ├─ php artisan queue:table && migrate
│ ├─ Works fine for < 100 jobs/min
│ └─ QUEUE_CONNECTION=database
│
├─ Medium-high throughput, self-hosted
│ └─ Redis driver (via predis or phpredis)
│ ├─ QUEUE_CONNECTION=redis
│ ├─ Laravel Horizon for monitoring
│ └─ Supports priorities, pausing, metrics
│
└─ AWS infrastructure / massive scale
└─ SQS driver
├─ QUEUE_CONNECTION=sqs
├─ Managed, auto-scaling
└─ Use with Laravel Vapor for serverless
Job Patterns
// Basic job dispatch
ProcessPayment::dispatch($order);
ProcessPayment::dispatch($order)->onQueue('payments')->delay(now()->addMinutes(5));
// Chaining (sequential)
Bus::chain([
new ProcessPayment($order),
new SendInvoice($order),
new UpdateInventory($order),
])->dispatch();
// Batching (parallel + callback)
$batch = Bus::batch([
new ImportRow($row1),
new ImportRow($row2),
new ImportRow($row3),
])->then(fn(Batch $batch) => ImportComplete::dispatch())
->catch(fn(Batch $batch, Throwable $e) => Log::error($e))
->dispatch();
// Rate limiting (throttle to 5 per minute)
public function middleware(): array
{
return [new RateLimited('payments')];
}
// Unique jobs (prevent duplicate processing)
use Illuminate\Contracts\Queue\ShouldBeUnique;
class ProcessPayment implements ShouldQueue, ShouldBeUnique
{
public string $uniqueId => $this->order->id;
public int $uniqueFor = 3600; // seconds
}
// Retry configuration
public int $tries = 3;
public int $backoff = 60; // seconds between retries
public function retryUntil(): DateTime
{
return now()->addHours(24);
}
Task Scheduling
// routes/console.php (Laravel 11+)
Schedule::job(SendDailyReport::class)->dailyAt('08:00')->timezone('America/New_York');
Schedule::command('backup:run')->daily()->runInBackground()->emailOutputOnFailure('ops@app.com');
Schedule::call(fn() => Cache::flush())->weekly()->sundays()->at('00:00');
// Prevent overlap (long-running tasks)
Schedule::job(ProcessImport::class)->everyFiveMinutes()->withoutOverlapping();
// Run on one server only (requires Redis/database cache driver)
Schedule::job(SendNewsletters::class)->daily()->onOneServer();
Testing Quick Reference
Test Types
| Type |
Class extends |
Database |
Purpose |
| Feature test |
Tests\TestCase |
Yes (with trait) |
HTTP endpoints, full stack |
| Unit test |
PHPUnit\Framework\TestCase |
No |
Pure logic, no app boot |
| Browser test |
Laravel\Dusk\TestCase |
Yes |
Real browser via ChromeDriver |
Database Traits
use Illuminate\Foundation\Testing\RefreshDatabase; // migrate fresh each test (slower)
use Illuminate\Foundation\Testing\DatabaseTransactions; // rollback each test (faster)
Pest Syntax (preferred in Laravel 11+)
describe('User authentication', function () {
beforeEach(function () {
$this->user = User::factory()->create();
});
it('allows login with valid credentials', function () {
$response = $this->post('/login', [
'email' => $this->user->email,
'password' => 'password',
]);
$response->assertRedirect('/dashboard');
$this->assertAuthenticatedAs($this->user);
});
it('rejects invalid credentials')->todo();
});
Common Assertions
// HTTP response
$response->assertStatus(200);
$response->assertOk(); // 200
$response->assertCreated(); // 201
$response->assertNoContent(); // 204
$response->assertUnauthorized(); // 401
$response->assertForbidden(); // 403
$response->assertNotFound(); // 404
$response->assertRedirect('/home');
// JSON responses
$response->assertJson(['status' => 'ok']);
$response->assertJsonPath('data.email', 'user@example.com');
$response->assertJsonCount(3, 'data');
$response->assertJsonStructure(['data' => ['id', 'name', 'email']]);
$response->assertJsonMissing(['password']);
// Database
$this->assertDatabaseHas('users', ['email' => 'user@example.com']);
$this->assertDatabaseMissing('users', ['email' => 'deleted@example.com']);
$this->assertDatabaseCount('posts', 5);
$this->assertSoftDeleted('posts', ['id' => $post->id]);
Common Gotchas
| Gotcha |
Why |
Fix |
| N+1 queries on relationships |
Eloquent lazy-loads by default |
Use with() eager loading; enable Model::preventLazyLoading() in AppServiceProvider during development |
| Mass assignment vulnerability |
$fillable = [] accepts all |
Always define $fillable; never use $guarded = [] in production |
created_at not updating on update() |
Only updated_at auto-sets |
Use $model->touch() or timestamps = true (default) |
| Queue job fails on model serialization |
Model state may change between dispatch and processing |
Use SerializesModels trait; re-fetch from DB in handle() if needed |
| Timezone mismatch in scheduled tasks |
Server tz != app tz |
Set APP_TIMEZONE in .env; use ->timezone() on schedule entries |
| Middleware order matters |
Auth middleware must run before policies |
Global → route group → route. Auth before throttle check or vice versa changes 401 vs 429 |
| Route model binding skips soft-deleted records |
RouteServiceProvider ignores trashed() |
Extend binding: Route::bind('post', fn($id) => Post::withTrashed()->findOrFail($id)) |
| Service container binding not auto-resolved |
Interface not bound to implementation |
Register in AppServiceProvider::register(): $this->app->bind(Interface::class, Implementation::class) |
| Migration foreign key order |
Must create referenced table first |
Run migrate:fresh to verify; use Schema::disableForeignKeyConstraints() in tests |
| CSRF protection blocks API routes |
VerifyCsrfToken runs on all web routes |
Register API routes in routes/api.php (uses api middleware group without CSRF) |
env() returns null after caching |
config:cache bakes env values |
Always access env via config() helper in app code; only use env() in config/ files |
Blade @stack renders in wrong order |
@push must appear after @stack in execution |
Use @prepend for scripts that must appear first |
| Event listener not firing |
Listener not registered or discovered |
Check EventServiceProvider::$listen; or enable Event::discover() in Laravel 11 |
Reference Files
| File |
Contents |
references/eloquent-queries.md |
Deep-dive: relationships, query builder, scopes, accessors, mutators, events, soft deletes, pagination, performance, collections, factories |
references/architecture.md |
Service container, providers, facades, middleware, events, notifications, jobs, scheduling, Blade components, Livewire, Inertia |
references/testing-auth.md |
PHPUnit/Pest setup, HTTP tests, database testing, fakes, Sanctum, Fortify, policies, form requests, Dusk |
See Also
sql-ops - Query optimization, indexing strategy, raw SQL patterns
postgres-ops - PostgreSQL-specific features, JSON columns, full-text search
testing-ops - General testing philosophy, TDD, CI integration
docker-ops - Containerizing Laravel apps, Docker Compose, production setup
Key External Resources
1---2name: laravel-ops3description: "Laravel framework patterns, Eloquent ORM, authentication, queues, and testing. Use for: laravel, eloquent, artisan, blade, php, sanctum, livewire, inertia, pest, phpunit, forge, vapor, queue, middleware, migration, factory, seeder."4when_to_use: "Use when building Laravel 11+ apps — e.g. 'write an Eloquent query or migration', 'set up Sanctum auth', 'configure queues and jobs', 'test with Pest or PHPUnit'. Covers artisan, Blade, Livewire/Inertia, Forge/Vapor, factories, and seeders."5license: MIT6allowed-tools: "Read Write Bash"7metadata:8 author: claude-mods9 related-skills: sql-ops, postgres-ops, testing-ops, docker-ops10---1112# Laravel Operations1314> Facts verified as of 2026-07.1516Authoritative reference for Laravel 11+ development: architecture decisions, Eloquent patterns, authentication strategies, queue configuration, and testing approaches.1718---1920## Architecture Decision Tree2122```23What type of application?24│25├─ Full-stack web (HTML responses)26│ ├─ Simple CRUD, small team → Monolith (Blade + Eloquent directly)27│ │ └─ Use action classes for business logic over 20 lines28│ ├─ Rich interactivity needed → Livewire (server-driven reactivity)29│ │ └─ Add Alpine.js for client-side micro-interactions30│ └─ SPA-like feel, React/Vue team → Inertia.js31│ └─ Keep server-side routing, dump client-side routing overhead32│33├─ API backend (JSON responses)34│ ├─ Single consumer (mobile/SPA) → API-only with Sanctum SPA auth35│ ├─ Multiple consumers / public → RESTful API with token auth36│ └─ Complex graph queries → Consider GraphQL (lighthouse-php/lighthouse)37│38├─ Large team / complex domain39│ ├─ Domain-driven → Modular monolith (app/Modules/{Domain}/)40│ │ ├─ Each module: Models, Actions, Events, Jobs, Http/41│ │ └─ Shared: app/Shared/ for cross-cutting concerns42│ └─ Independent deployability needed → Microservices43│ └─ Use Laravel Octane for high-throughput services44│45└─ What business logic pattern?46 ├─ Simple CRUD, < 20 lines → Direct Eloquent in controller47 ├─ Reusable operation (create order, send invoice) → Action class48 │ └─ Single public handle() or execute() method49 ├─ Complex queries, multiple data sources → Repository pattern50 │ └─ Interface + Eloquent implementation (enables swapping)51 └─ Cross-cutting operations (audit, caching) → Service class52 └─ Inject via constructor, bind in ServiceProvider53```5455### Action Class vs Repository vs Service5657| Pattern | Use When | Example |58|---------|----------|---------|59| Action class | Single, reusable business operation | `CreateOrderAction`, `SendInvoiceAction` |60| Repository | Abstract data access, multiple sources | `OrderRepository` with `EloquentOrderRepository` |61| Service | Orchestrate multiple actions/repos | `OrderService` combining payment + inventory |62| Direct Eloquent | Simple CRUD, < 5 lines in controller | `User::create($data)` |6364---6566## Eloquent Quick Reference6768### Relationships6970| Relationship | Method | Foreign Key Convention |71|-------------|--------|----------------------|72| `hasOne` | `return $this->hasOne(Profile::class)` | `profiles.user_id` |73| `hasMany` | `return $this->hasMany(Post::class)` | `posts.user_id` |74| `belongsTo` | `return $this->belongsTo(User::class)` | `posts.user_id` |75| `belongsToMany` | `return $this->belongsToMany(Role::class)` | `role_user` pivot |76| `hasManyThrough` | `return $this->hasManyThrough(Post::class, User::class)` | Country → User → Post |77| `morphTo` | `return $this->morphTo()` | `{col}_type`, `{col}_id` |78| `morphMany` | `return $this->morphMany(Comment::class, 'commentable')` | Polymorphic |79| `morphToMany` | `return $this->morphToMany(Tag::class, 'taggable')` | Polymorphic pivot |8081### Eager Loading8283```php84// Prevent N+1: always eager load in controllers85$posts = Post::with(['author', 'comments.author', 'tags'])->paginate(15);8687// Conditional eager loading (load after retrieval)88$user->load('posts.comments');89$user->loadMissing('posts'); // only if not already loaded9091// Eager load counts (no SELECT *)92$posts = Post::withCount('comments')->get();9394// Constrained eager loading95$posts = Post::with(['comments' => fn($q) => $q->approved()->latest()])->get();96```9798### Query Scopes99100```php101// Local scope (reusable query constraint)102public function scopeActive(Builder $query): void103{104 $query->where('status', 'active');105}106107// Usage: User::active()->get()108109// Dynamic scope110public function scopeOfType(Builder $query, string $type): void111{112 $query->where('type', $type);113}114// Usage: User::ofType('admin')->get()115```116117### Mass Assignment118119```php120// Fillable (allowlist - preferred)121protected $fillable = ['name', 'email', 'password'];122123// Guarded (denylist - use [] only if you trust all input)124protected $guarded = ['id', 'is_admin'];125126// Never set guarded = [] in production code127```128129---130131## Artisan Command Cheat Sheet132133| Command | Purpose | Common Options |134|---------|---------|----------------|135| `make:model Post -mfs` | Model + migration + factory + seeder | `-c` controller, `-r` resource |136| `make:controller PostController -r` | Resource controller (7 methods) | `--api` skips create/edit |137| `make:request StorePostRequest` | Form request for validation | |138| `make:job ProcessPayment` | Queueable job class | `--sync` for sync job |139| `make:event OrderPlaced` | Event class | |140| `make:listener SendOrderConfirmation -e OrderPlaced` | Listener for event | `--queued` |141| `make:notification InvoicePaid` | Notification class | |142| `make:policy PostPolicy -m Post` | Policy with model | |143| `make:middleware EnsureUserIsAdmin` | HTTP middleware | |144| `make:command SendDailyReport` | Custom Artisan command | |145| `migrate` | Run pending migrations | `--step` for individual |146| `migrate:rollback` | Roll back last batch | `--step=5` |147| `migrate:fresh --seed` | Drop all + re-migrate + seed | |148| `db:seed` | Run all seeders | `--class=UserSeeder` |149| `tinker` | REPL with app context | |150| `route:list` | Show all routes | `--name=api` filter |151| `route:cache` | Cache routes for production | |152| `config:cache` | Cache config for production | |153| `view:cache` | Pre-compile Blade templates | |154| `optimize` | Run all cache commands | `optimize:clear` to reset |155| `queue:work` | Process queue jobs | `--queue=high,default` |156| `queue:listen` | Work + auto-reload on code change | |157| `queue:failed` | List failed jobs | |158| `queue:retry all` | Retry all failed jobs | |159| `schedule:run` | Run due scheduled tasks | |160| `schedule:work` | Run scheduler every minute (dev) | |161| `key:generate` | Generate APP_KEY | |162| `test` | Run PHPUnit/Pest tests | `--filter=UserTest` |163| `test --parallel` | Run tests in parallel | `--processes=4` |164| `vendor:publish` | Publish package assets/config | `--tag=config` |165166---167168## Authentication Decision Tree169170```171What do you need?172│173├─ SPA (Vue/React) + Laravel API backend174│ └─ Sanctum SPA authentication175│ ├─ Cookie-based (same domain or subdomain)176│ ├─ Csrf-cookie endpoint: GET /sanctum/csrf-cookie177│ └─ No tokens in localStorage (XSS safe)178│179├─ Mobile app or third-party API consumers180│ └─ Sanctum API tokens (Bearer tokens)181│ ├─ createToken($name, $abilities)182│ ├─ Token abilities for fine-grained control183│ └─ Token expiration with token:prune schedule184│185├─ Traditional web app (server-rendered)186│ ├─ Just need auth pages quickly → Breeze187│ │ ├─ Minimal, educational, Blade or Inertia stack188│ │ └─ Install: composer require laravel/breeze --dev189│ ├─ Need teams, 2FA, profile management → Jetstream190│ │ ├─ Livewire or Inertia stack191│ │ └─ Install: composer require laravel/jetstream192│ └─ Need headless auth (API + custom UI) → Fortify193│ ├─ Actions in app/Actions/Fortify/194│ └─ Customize: CreateNewUser, UpdateUserPassword195│196└─ Custom / enterprise197 ├─ LDAP/SAML → socialiteproviders/saml2198 ├─ OAuth social login → laravel/socialite199 └─ Custom guard → Implement Guard + UserProvider contracts200```201202### Sanctum Quick Setup203204```php205// config/sanctum.php - stateful domains for SPA206'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', 'localhost')),207208// API token creation209$token = $user->createToken('mobile-app', ['orders:read', 'orders:write']);210return ['token' => $token->plainTextToken];211212// Check token ability213Route::get('/orders', function (Request $request) {214 $request->user()->tokenCan('orders:read'); // bool215});216217// Protect routes218Route::middleware('auth:sanctum')->group(function () {219 // authenticated routes220});221```222223---224225## Queue Decision Tree226227```228Queue driver selection:229│230├─ Development / testing231│ └─ sync driver (executes immediately, no worker needed)232│ QUEUE_CONNECTION=sync233│234├─ Small app, no Redis available235│ └─ database driver236│ ├─ php artisan queue:table && migrate237│ ├─ Works fine for < 100 jobs/min238│ └─ QUEUE_CONNECTION=database239│240├─ Medium-high throughput, self-hosted241│ └─ Redis driver (via predis or phpredis)242│ ├─ QUEUE_CONNECTION=redis243│ ├─ Laravel Horizon for monitoring244│ └─ Supports priorities, pausing, metrics245│246└─ AWS infrastructure / massive scale247 └─ SQS driver248 ├─ QUEUE_CONNECTION=sqs249 ├─ Managed, auto-scaling250 └─ Use with Laravel Vapor for serverless251```252253### Job Patterns254255```php256// Basic job dispatch257ProcessPayment::dispatch($order);258ProcessPayment::dispatch($order)->onQueue('payments')->delay(now()->addMinutes(5));259260// Chaining (sequential)261Bus::chain([262 new ProcessPayment($order),263 new SendInvoice($order),264 new UpdateInventory($order),265])->dispatch();266267// Batching (parallel + callback)268$batch = Bus::batch([269 new ImportRow($row1),270 new ImportRow($row2),271 new ImportRow($row3),272])->then(fn(Batch $batch) => ImportComplete::dispatch())273 ->catch(fn(Batch $batch, Throwable $e) => Log::error($e))274 ->dispatch();275276// Rate limiting (throttle to 5 per minute)277public function middleware(): array278{279 return [new RateLimited('payments')];280}281282// Unique jobs (prevent duplicate processing)283use Illuminate\Contracts\Queue\ShouldBeUnique;284285class ProcessPayment implements ShouldQueue, ShouldBeUnique286{287 public string $uniqueId => $this->order->id;288 public int $uniqueFor = 3600; // seconds289}290291// Retry configuration292public int $tries = 3;293public int $backoff = 60; // seconds between retries294295public function retryUntil(): DateTime296{297 return now()->addHours(24);298}299```300301### Task Scheduling302303```php304// routes/console.php (Laravel 11+)305Schedule::job(SendDailyReport::class)->dailyAt('08:00')->timezone('America/New_York');306Schedule::command('backup:run')->daily()->runInBackground()->emailOutputOnFailure('ops@app.com');307Schedule::call(fn() => Cache::flush())->weekly()->sundays()->at('00:00');308309// Prevent overlap (long-running tasks)310Schedule::job(ProcessImport::class)->everyFiveMinutes()->withoutOverlapping();311312// Run on one server only (requires Redis/database cache driver)313Schedule::job(SendNewsletters::class)->daily()->onOneServer();314```315316---317318## Testing Quick Reference319320### Test Types321322| Type | Class extends | Database | Purpose |323|------|--------------|----------|---------|324| Feature test | `Tests\TestCase` | Yes (with trait) | HTTP endpoints, full stack |325| Unit test | `PHPUnit\Framework\TestCase` | No | Pure logic, no app boot |326| Browser test | `Laravel\Dusk\TestCase` | Yes | Real browser via ChromeDriver |327328### Database Traits329330```php331use Illuminate\Foundation\Testing\RefreshDatabase; // migrate fresh each test (slower)332use Illuminate\Foundation\Testing\DatabaseTransactions; // rollback each test (faster)333```334335### Pest Syntax (preferred in Laravel 11+)336337```php338describe('User authentication', function () {339 beforeEach(function () {340 $this->user = User::factory()->create();341 });342343 it('allows login with valid credentials', function () {344 $response = $this->post('/login', [345 'email' => $this->user->email,346 'password' => 'password',347 ]);348349 $response->assertRedirect('/dashboard');350 $this->assertAuthenticatedAs($this->user);351 });352353 it('rejects invalid credentials')->todo();354});355```356357### Common Assertions358359```php360// HTTP response361$response->assertStatus(200);362$response->assertOk(); // 200363$response->assertCreated(); // 201364$response->assertNoContent(); // 204365$response->assertUnauthorized(); // 401366$response->assertForbidden(); // 403367$response->assertNotFound(); // 404368$response->assertRedirect('/home');369370// JSON responses371$response->assertJson(['status' => 'ok']);372$response->assertJsonPath('data.email', 'user@example.com');373$response->assertJsonCount(3, 'data');374$response->assertJsonStructure(['data' => ['id', 'name', 'email']]);375$response->assertJsonMissing(['password']);376377// Database378$this->assertDatabaseHas('users', ['email' => 'user@example.com']);379$this->assertDatabaseMissing('users', ['email' => 'deleted@example.com']);380$this->assertDatabaseCount('posts', 5);381$this->assertSoftDeleted('posts', ['id' => $post->id]);382```383384---385386## Common Gotchas387388| Gotcha | Why | Fix |389|--------|-----|-----|390| N+1 queries on relationships | Eloquent lazy-loads by default | Use `with()` eager loading; enable `Model::preventLazyLoading()` in AppServiceProvider during development |391| Mass assignment vulnerability | `$fillable = []` accepts all | Always define `$fillable`; never use `$guarded = []` in production |392| `created_at` not updating on `update()` | Only `updated_at` auto-sets | Use `$model->touch()` or `timestamps = true` (default) |393| Queue job fails on model serialization | Model state may change between dispatch and processing | Use `SerializesModels` trait; re-fetch from DB in `handle()` if needed |394| Timezone mismatch in scheduled tasks | Server tz != app tz | Set `APP_TIMEZONE` in `.env`; use `->timezone()` on schedule entries |395| Middleware order matters | Auth middleware must run before policies | Global → route group → route. Auth before throttle check or vice versa changes 401 vs 429 |396| Route model binding skips soft-deleted records | `RouteServiceProvider` ignores `trashed()` | Extend binding: `Route::bind('post', fn($id) => Post::withTrashed()->findOrFail($id))` |397| Service container binding not auto-resolved | Interface not bound to implementation | Register in `AppServiceProvider::register()`: `$this->app->bind(Interface::class, Implementation::class)` |398| Migration foreign key order | Must create referenced table first | Run `migrate:fresh` to verify; use `Schema::disableForeignKeyConstraints()` in tests |399| CSRF protection blocks API routes | `VerifyCsrfToken` runs on all web routes | Register API routes in `routes/api.php` (uses `api` middleware group without CSRF) |400| `env()` returns null after caching | `config:cache` bakes env values | Always access env via `config()` helper in app code; only use `env()` in `config/` files |401| Blade `@stack` renders in wrong order | `@push` must appear after `@stack` in execution | Use `@prepend` for scripts that must appear first |402| Event listener not firing | Listener not registered or discovered | Check `EventServiceProvider::$listen`; or enable `Event::discover()` in Laravel 11 |403404---405406## Reference Files407408| File | Contents |409|------|---------|410| `references/eloquent-queries.md` | Deep-dive: relationships, query builder, scopes, accessors, mutators, events, soft deletes, pagination, performance, collections, factories |411| `references/architecture.md` | Service container, providers, facades, middleware, events, notifications, jobs, scheduling, Blade components, Livewire, Inertia |412| `references/testing-auth.md` | PHPUnit/Pest setup, HTTP tests, database testing, fakes, Sanctum, Fortify, policies, form requests, Dusk |413414---415416## See Also417418- `sql-ops` - Query optimization, indexing strategy, raw SQL patterns419- `postgres-ops` - PostgreSQL-specific features, JSON columns, full-text search420- `testing-ops` - General testing philosophy, TDD, CI integration421- `docker-ops` - Containerizing Laravel apps, Docker Compose, production setup422423### Key External Resources424425- [Laravel 11 Documentation](https://laravel.com/docs/11.x)426- [Pest PHP](https://pestphp.com/)427- [Laravel Horizon](https://laravel.com/docs/11.x/horizon) - Queue monitoring428- [Laravel Telescope](https://laravel.com/docs/11.x/telescope) - Local debugging and request/query monitoring429- [Laravel Octane](https://laravel.com/docs/11.x/octane) - High-performance serving430- [Laravel Forge](https://forge.laravel.com/) - Server management431- [Laravel Vapor](https://vapor.laravel.com/) - Serverless deployment