From claude-mods
Guides Laravel architecture decisions, Eloquent patterns, authentication, queues, and testing for Laravel 11+. Covers monolith vs. modular vs. microservices, action/repository/service patterns, and relationship conventions.
How this skill is triggered — by the user, by Claude, or both
Slash command
/claude-mods:laravel-opsThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Authoritative reference for Laravel 11+ development: architecture decisions, Eloquent patterns, authentication strategies, queue configuration, and testing approaches.
Authoritative reference for Laravel 11+ development: architecture decisions, Eloquent patterns, authentication strategies, queue configuration, and testing approaches.
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
| 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) |
| 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 |
// 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();
// 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()
// 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
| 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 |
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
// 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 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
// 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);
}
// 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();
| 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 |
use Illuminate\Foundation\Testing\RefreshDatabase; // migrate fresh each test (slower)
use Illuminate\Foundation\Testing\DatabaseTransactions; // rollback each test (faster)
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();
});
// 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]);
| 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 |
| 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 |
sql-ops - Query optimization, indexing strategy, raw SQL patternspostgres-ops - PostgreSQL-specific features, JSON columns, full-text searchtesting-ops - General testing philosophy, TDD, CI integrationdocker-ops - Containerizing Laravel apps, Docker Compose, production setupnpx claudepluginhub 0xdarkmatter/claude-mods --plugin claude-modsProvides Laravel patterns for PHP apps including Eloquent ORM, migrations, routing, controllers, queues, jobs, authentication, middleware, and testing. Use for Laravel projects.
Provides Laravel expertise including Eloquent ORM optimization, service container patterns, queues, events, Sanctum/Passport auth, and Pest testing. Activates on /godmode:laravel or Laravel/eloquent/artisan/blade mentions.
Builds and configures Laravel 10+ apps with Eloquent models, Sanctum auth, Horizon queues, Livewire components, and Pest/PHPUnit tests.