From claude-mods
Guides Laravel 11+ development with architecture decision trees, Eloquent relationships and patterns, action classes vs repositories vs services, auth strategies, queues, and testing approaches.
npx claudepluginhub 0xdarkmatter/claude-modsThis skill is limited to using the following tools:
Authoritative reference for Laravel 11+ development: architecture decisions, Eloquent patterns, authentication strategies, queue configuration, and testing approaches.
Creates isolated Git worktrees for feature branches with prioritized directory selection, gitignore safety checks, auto project setup for Node/Python/Rust/Go, and baseline verification.
Executes implementation plans in current session by dispatching fresh subagents per independent task, with two-stage reviews: spec compliance then code quality.
Dispatches parallel agents to independently tackle 2+ tasks like separate test failures or subsystems without shared state or dependencies.
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 setup