L’interface est localisée ; les présentations et documents des Skills sont traduits automatiquement par IA dans votre langue. Le code, les commandes, chemins et termes techniques restent en version originale, et le document de l’auteur fait foi.
Explorer les Skills
Développement · SKILL.md

laravel-ops

laravel-ops

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.

Independent GitHub snapshot · Automated classification · Original documentation governs

Documentation de la Skill

GitHub

name: laravel-ops description: "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." when_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." license: MIT allowed-tools: "Read Write Bash" metadata: author: claude-mods related-skills: sql-ops, postgres-ops, testing-ops, docker-ops

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

PatternUse WhenExample
Action classSingle, reusable business operationCreateOrderAction, SendInvoiceAction
RepositoryAbstract data access, multiple sourcesOrderRepository with EloquentOrderRepository
ServiceOrchestrate multiple actions/reposOrderService combining payment + inventory
Direct EloquentSimple CRUD, < 5 lines in controllerUser::create($data)

Eloquent Quick Reference

Relationships

RelationshipMethodForeign Key Convention
hasOnereturn $this->hasOne(Profile::class)profiles.user_id
hasManyreturn $this->hasMany(Post::class)posts.user_id
belongsToreturn $this->belongsTo(User::class)posts.user_id
belongsToManyreturn $this->belongsToMany(Role::class)role_user pivot
hasManyThroughreturn $this->hasManyThrough(Post::class, User::class)Country → User → Post
morphToreturn $this->morphTo(){col}_type, {col}_id
morphManyreturn $this->morphMany(Comment::class, 'commentable')Polymorphic
morphToManyreturn $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

CommandPurposeCommon Options
make:model Post -mfsModel + migration + factory + seeder-c controller, -r resource
make:controller PostController -rResource controller (7 methods)--api skips create/edit
make:request StorePostRequestForm request for validation
make:job ProcessPaymentQueueable job class--sync for sync job
make:event OrderPlacedEvent class
make:listener SendOrderConfirmation -e OrderPlacedListener for event--queued
make:notification InvoicePaidNotification class
make:policy PostPolicy -m PostPolicy with model
make:middleware EnsureUserIsAdminHTTP middleware
make:command SendDailyReportCustom Artisan command
migrateRun pending migrations--step for individual
migrate:rollbackRoll back last batch--step=5
migrate:fresh --seedDrop all + re-migrate + seed
db:seedRun all seeders--class=UserSeeder
tinkerREPL with app context
route:listShow all routes--name=api filter
route:cacheCache routes for production
config:cacheCache config for production
view:cachePre-compile Blade templates
optimizeRun all cache commandsoptimize:clear to reset
queue:workProcess queue jobs--queue=high,default
queue:listenWork + auto-reload on code change
queue:failedList failed jobs
queue:retry allRetry all failed jobs
schedule:runRun due scheduled tasks
schedule:workRun scheduler every minute (dev)
key:generateGenerate APP_KEY
testRun PHPUnit/Pest tests--filter=UserTest
test --parallelRun tests in parallel--processes=4
vendor:publishPublish 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

TypeClass extendsDatabasePurpose
Feature testTests\TestCaseYes (with trait)HTTP endpoints, full stack
Unit testPHPUnit\Framework\TestCaseNoPure logic, no app boot
Browser testLaravel\Dusk\TestCaseYesReal 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

GotchaWhyFix
N+1 queries on relationshipsEloquent lazy-loads by defaultUse with() eager loading; enable Model::preventLazyLoading() in AppServiceProvider during development
Mass assignment vulnerability$fillable = [] accepts allAlways define $fillable; never use $guarded = [] in production
created_at not updating on update()Only updated_at auto-setsUse $model->touch() or timestamps = true (default)
Queue job fails on model serializationModel state may change between dispatch and processingUse SerializesModels trait; re-fetch from DB in handle() if needed
Timezone mismatch in scheduled tasksServer tz != app tzSet APP_TIMEZONE in .env; use ->timezone() on schedule entries
Middleware order mattersAuth middleware must run before policiesGlobal → route group → route. Auth before throttle check or vice versa changes 401 vs 429
Route model binding skips soft-deleted recordsRouteServiceProvider ignores trashed()Extend binding: Route::bind('post', fn($id) => Post::withTrashed()->findOrFail($id))
Service container binding not auto-resolvedInterface not bound to implementationRegister in AppServiceProvider::register(): $this->app->bind(Interface::class, Implementation::class)
Migration foreign key orderMust create referenced table firstRun migrate:fresh to verify; use Schema::disableForeignKeyConstraints() in tests
CSRF protection blocks API routesVerifyCsrfToken runs on all web routesRegister API routes in routes/api.php (uses api middleware group without CSRF)
env() returns null after cachingconfig:cache bakes env valuesAlways 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 executionUse @prepend for scripts that must appear first
Event listener not firingListener not registered or discoveredCheck EventServiceProvider::$listen; or enable Event::discover() in Laravel 11

Reference Files

FileContents
references/eloquent-queries.mdDeep-dive: relationships, query builder, scopes, accessors, mutators, events, soft deletes, pagination, performance, collections, factories
references/architecture.mdService container, providers, facades, middleware, events, notifications, jobs, scheduling, Blade components, Livewire, Inertia
references/testing-auth.mdPHPUnit/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

Skills enregistrées