Loading...
Loading...
Use when building or maintaining Laravel applications — Eloquent ORM, Blade, Livewire, queues, Pest testing, middleware, service providers, migrations. Trigger conditions: Laravel project setup, Eloquent model design, Blade or Livewire component creation, queue/job implementation, Pest test writing, middleware configuration, migration authoring, route definition, Form Request validation, policy authorization, Sanctum/Passport authentication, Horizon queue monitoring.
npx skill4agent add pixel-process-ug/superkit-agents laravel-specialistcomposer.jsonlaravel/frameworkconfig/routes/STOP — Do NOT begin architecture review without knowing the Laravel version and installed packages.
mcp__context7__resolve-library-idmcp__context7__query-docshttps://github.com/laravel/docsSTOP — Do NOT begin implementation until architecture gaps are documented.
STOP — Do NOT skip Form Requests and Policies. Inline validation and authorization are anti-patterns.
assertDatabaseHasassertSoftDeletedSTOP — Do NOT proceed to optimization without passing tests at all layers.
EXPLAIN| Relationship | Method | Inverse | Use Case |
|---|---|---|---|
| One-to-One | | | User -> Profile |
| One-to-Many | | | Post -> Comments |
| Many-to-Many | | | User <-> Roles (pivot) |
| Has-Many-Through | | — | Country -> Posts (through Users) |
| Polymorphic | | | Comments on Posts and Videos |
| Many-to-Many Polymorphic | | | Tags on Posts and Videos |
// Local scope — reusable query constraint
public function scopeActive(Builder $query): Builder
{
return $query->where('status', 'active');
}
// Usage: User::active()->where('role', 'admin')->get();
// Global scope — applied to all queries on the model
protected static function booted(): void
{
static::addGlobalScope('published', function (Builder $builder) {
$builder->whereNotNull('published_at');
});
}// Attribute accessor/mutator (Laravel 11+ syntax)
protected function fullName(): Attribute
{
return Attribute::make(
get: fn () => "{$this->first_name} {$this->last_name}",
);
}
// Custom cast
protected function casts(): array
{
return [
'options' => AsCollection::class,
'address' => AddressCast::class,
'status' => OrderStatus::class, // Backed enum
'metadata' => 'array',
'is_active' => 'boolean',
'amount' => MoneyCast::class,
];
}// BAD — N+1 problem: 1 query for posts + N queries for authors
$posts = Post::all();
foreach ($posts as $post) {
echo $post->author->name; // Triggers lazy load each iteration
}
// GOOD — Eager load: 2 queries total
$posts = Post::with('author')->get();
// Nested eager loading
$posts = Post::with(['author', 'comments.user'])->get();
// Constrained eager loading
$posts = Post::with(['comments' => function ($query) {
$query->where('approved', true)->latest()->limit(5);
}])->get();
// Prevent lazy loading in development
Model::preventLazyLoading(! app()->isProduction());resources/views/layouts/app.blade.php@yield@section<x-app-layout>resources/views/components/@include('partials.sidebar'){{ }}{!! !!}@auth@can@env// Full-page Livewire component (Livewire 3+)
#[Layout('layouts.app')]
#[Title('Dashboard')]
class Dashboard extends Component
{
public string $search = '';
#[Computed]
public function users(): LengthAwarePaginator
{
return User::where('name', 'like', "%{$this->search}%")->paginate(15);
}
public function render(): View
{
return view('livewire.dashboard');
}
}| Decision | Choose Livewire | Choose Inertia |
|---|---|---|
| Existing Blade codebase | Yes | No |
| SPA-like experience required | Partial (with wire:navigate) | Yes |
| Team has Vue/React expertise | No | Yes |
| Server-side rendering priority | Yes | Depends on adapter |
| Real-time reactivity | Yes (polling, streams) | Requires Echo setup |
| SEO-critical pages | Either works | Either works (SSR adapter) |
class ProcessInvoice implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public int $tries = 3;
public int $backoff = 60;
public int $timeout = 120;
public string $queue = 'invoices';
public function __construct(public readonly Invoice $invoice) {}
public function handle(PdfGenerator $generator): void
{
$generator->generate($this->invoice);
}
public function failed(Throwable $exception): void
{
// Notify admin, log to error tracker
}
}// Dispatch event
OrderPlaced::dispatch($order);
// Listener (queued)
class SendOrderConfirmation implements ShouldQueue
{
public function handle(OrderPlaced $event): void
{
Mail::to($event->order->user)->send(new OrderConfirmationMail($event->order));
}
}| Task | Queued | Synchronous |
|---|---|---|
| Sending emails / notifications | Yes | Never in request cycle |
| PDF generation | Yes | Only if < 2s and user waits |
| Payment processing | Depends — webhook-driven preferred | If gateway responds < 5s |
| Cache warming | Yes | Never |
| Audit logging | Yes (high-volume) or Sync (low-volume) | If guaranteed delivery needed |
| Search indexing | Yes | Never |
bootstrap/app.php->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [
HandleInertiaRequests::class, // After session, before response
]);
$middleware->api(prepend: [
EnsureFrontendRequestsAreStateful::class, // Sanctum SPA auth
]);
$middleware->alias([
'role' => EnsureUserHasRole::class,
'verified' => EnsureEmailIsVerified::class,
]);
})register()boot()test('order total calculates tax correctly', function () {
$order = Order::factory()->make(['subtotal' => 10000, 'tax_rate' => 0.08]);
expect($order->total)->toBe(10800);
});test('authenticated user can create a post', function () {
$user = User::factory()->create();
$response = $this->actingAs($user)
->postJson('/api/posts', [
'title' => 'My Post',
'body' => 'Content here.',
]);
$response->assertCreated()
->assertJsonPath('data.title', 'My Post');
$this->assertDatabaseHas('posts', [
'user_id' => $user->id,
'title' => 'My Post',
]);
});test('placing an order dispatches confirmation job', function () {
Queue::fake();
$order = Order::factory()->create();
PlaceOrder::dispatch($order);
Queue::assertPushed(SendOrderConfirmation::class, function ($job) use ($order) {
return $job->order->id === $order->id;
});
});test('user can complete checkout flow', function () {
$this->browse(function (Browser $browser) {
$browser->loginAs(User::factory()->create())
->visit('/cart')
->press('Checkout')
->waitForText('Order Confirmed')
->assertSee('Thank you');
});
});// Always include down() for rollback capability
public function up(): void
{
Schema::create('invoices', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('number')->unique();
$table->integer('amount'); // Store money as cents
$table->string('currency', 3);
$table->string('status')->default('draft');
$table->timestamp('due_at')->nullable();
$table->timestamps();
$table->softDeletes();
$table->index(['user_id', 'status']);
});
}class InvoiceFactory extends Factory
{
public function definition(): array
{
return [
'user_id' => User::factory(),
'number' => $this->faker->unique()->numerify('INV-####'),
'amount' => $this->faker->numberBetween(1000, 100000),
'currency' => 'USD',
'status' => 'draft',
'due_at' => now()->addDays(30),
];
}
public function paid(): static
{
return $this->state(fn () => ['status' => 'paid']);
}
public function overdue(): static
{
return $this->state(fn () => [
'status' => 'sent',
'due_at' => now()->subDays(7),
]);
}
}app/
├── Actions/ # Single-purpose action classes
├── Casts/ # Custom Eloquent casts
├── Console/Commands/ # Artisan commands
├── Enums/ # PHP backed enums
├── Events/ # Event classes
├── Exceptions/ # Custom exception classes
├── Http/
│ ├── Controllers/ # Resourceful or single-action controllers
│ ├── Middleware/ # Request/response middleware
│ └── Requests/ # Form Request validation
├── Jobs/ # Queued job classes
├── Listeners/ # Event listener classes
├── Mail/ # Mailable classes
├── Models/ # Eloquent models
├── Notifications/ # Notification classes
├── Observers/ # Model observers
├── Policies/ # Authorization policies
├── Providers/ # Service providers
├── Rules/ # Custom validation rules
├── Services/ # Domain service classes
└── View/Components/ # Blade view components
database/
├── factories/ # Model factories
├── migrations/ # Schema migrations (timestamped)
└── seeders/ # Database seeders
resources/views/
├── components/ # Blade components
├── layouts/ # Layout templates
├── livewire/ # Livewire component views
└── mail/ # Email templates
routes/
├── api.php # API routes
├── channels.php # Broadcast channels
├── console.php # Artisan closures
└── web.php # Web routes
tests/
├── Feature/ # Feature (integration) tests
├── Unit/ # Unit tests
└── Browser/ # Dusk browser tests| Scenario | Recommended Approach |
|---|---|
| SPA + same domain | Sanctum (cookie-based, CSRF) |
| SPA + different domain | Sanctum (token-based) |
| Mobile app | Sanctum (token-based) |
| Third-party API consumers | Passport (OAuth2) |
| Simple API tokens | Sanctum (plaintext hash) |
| Social login | Socialite + Sanctum |
| Data Type | Cache Driver | TTL | Invalidation |
|---|---|---|---|
| Config / routes / views | File (artisan cache) | Until next deploy | |
| Database query results | Redis / Memcached | 5-60 min | Event-driven or TTL |
| Full-page / fragment | Redis | 1-15 min | Cache tags |
| Session data | Redis | Session lifetime | Automatic |
| Rate limiting | Redis | Window duration | Automatic |
| Scenario | Disk | Driver |
|---|---|---|
| User uploads (production) | | Amazon S3 / compatible |
| User uploads (local dev) | | Local filesystem |
| Public assets | | Local with symlink |
| Temporary files | | Local, pruned by schedule |
| Anti-Pattern | Why It Fails | What To Do Instead |
|---|---|---|
| Fat controllers | Untestable, unmaintainable business logic | Move logic to Action or Service classes |
| Raw SQL in controllers | SQL injection risk, not portable | Use Eloquent or Query Builder |
| Missing mass-assignment protection | Data manipulation vulnerabilities | Always define |
| Inline validation in controllers | Couples validation to HTTP layer | Use Form Requests |
| Jobs without retry/backoff config | Silent failures, no recovery | Configure |
| Over-using global scopes | Hidden query behavior surprises developers | Prefer local scopes |
| Storing money as floats | Floating-point precision errors | Use integer cents, convert at presentation |
| Missing database indexes | Slow queries at scale | Add composite indexes for WHERE + ORDER BY |
| Secrets in config files | Credential leaks in version control | Use |
| Testing against production DB | Data corruption, unreliable tests | Use SQLite in-memory or dedicated test DB |
| Lazy loading in API responses | N+1 queries, slow API responses | Enable |
preventLazyLoading()| Skill | How It Connects |
|---|---|
| Modern PHP 8.x patterns underpin all Laravel code |
| AI-assisted development guidelines and MCP tooling |
| API design, caching strategies, event-driven architecture |
| Pest testing workflow with RED-GREEN-REFACTOR |
| Migration planning, indexing strategy, data modeling |
| Sanctum/Passport configuration, CSRF, input validation |
| Query profiling, cache tuning, queue worker scaling |
| Forge/Vapor/Envoyer deployment, |
| Fetches up-to-date Laravel docs when information is uncertain |
| Authoritative source for Laravel API reference |