Claude Agent Skill · by Affaan M

Laravel Patterns

Install Laravel Patterns skill for Claude Code from affaan-m/everything-claude-code.

Install
Terminal · npx
$npx skills add https://github.com/jeffallan/claude-skills --skill laravel-specialist
Works with Paperclip

How Laravel Patterns fits into a Paperclip company.

Laravel Patterns drops into any Paperclip agent that handles this kind of work. Assign it to a specialist inside a pre-configured PaperclipOrg company and the skill becomes available on every heartbeat — no prompt engineering, no tool wiring.

S
SaaS FactoryPaired

Pre-configured AI company — 18 agents, 18 skills, one-time purchase.

$27$59
Explore pack
Source file
SKILL.md415 lines
Expand
---name: laravel-patternsdescription: Laravel architecture patterns, routing/controllers, Eloquent ORM, service layers, queues, events, caching, and API resources for production apps.origin: ECC--- # Laravel Development Patterns Production-grade Laravel architecture patterns for scalable, maintainable applications. ## When to Use - Building Laravel web applications or APIs- Structuring controllers, services, and domain logic- Working with Eloquent models and relationships- Designing APIs with resources and pagination- Adding queues, events, caching, and background jobs ## How It Works - Structure the app around clear boundaries (controllers -> services/actions -> models).- Use explicit bindings and scoped bindings to keep routing predictable; still enforce authorization for access control.- Favor typed models, casts, and scopes to keep domain logic consistent.- Keep IO-heavy work in queues and cache expensive reads.- Centralize config in `config/*` and keep environments explicit. ## Examples ### Project Structure Use a conventional Laravel layout with clear layer boundaries (HTTP, services/actions, models). ### Recommended Layout ```app/├── Actions/            # Single-purpose use cases├── Console/├── Events/├── Exceptions/├── Http/│   ├── Controllers/│   ├── Middleware/│   ├── Requests/       # Form request validation│   └── Resources/      # API resources├── Jobs/├── Models/├── Policies/├── Providers/├── Services/           # Coordinating domain services└── Support/config/database/├── factories/├── migrations/└── seeders/resources/├── views/└── lang/routes/├── api.php├── web.php└── console.php``` ### Controllers -> Services -> Actions Keep controllers thin. Put orchestration in services and single-purpose logic in actions. ```phpfinal class CreateOrderAction{    public function __construct(private OrderRepository $orders) {}     public function handle(CreateOrderData $data): Order    {        return $this->orders->create($data);    }} final class OrdersController extends Controller{    public function __construct(private CreateOrderAction $createOrder) {}     public function store(StoreOrderRequest $request): JsonResponse    {        $order = $this->createOrder->handle($request->toDto());         return response()->json([            'success' => true,            'data' => OrderResource::make($order),            'error' => null,            'meta' => null,        ], 201);    }}``` ### Routing and Controllers Prefer route-model binding and resource controllers for clarity. ```phpuse Illuminate\Support\Facades\Route; Route::middleware('auth:sanctum')->group(function () {    Route::apiResource('projects', ProjectController::class);});``` ### Route Model Binding (Scoped) Use scoped bindings to prevent cross-tenant access. ```phpRoute::scopeBindings()->group(function () {    Route::get('/accounts/{account}/projects/{project}', [ProjectController::class, 'show']);});``` ### Nested Routes and Binding Names - Keep prefixes and paths consistent to avoid double nesting (e.g., `conversation` vs `conversations`).- Use a single parameter name that matches the bound model (e.g., `{conversation}` for `Conversation`).- Prefer scoped bindings when nesting to enforce parent-child relationships. ```phpuse App\Http\Controllers\Api\ConversationController;use App\Http\Controllers\Api\MessageController;use Illuminate\Support\Facades\Route; Route::middleware('auth:sanctum')->prefix('conversations')->group(function () {    Route::post('/', [ConversationController::class, 'store'])->name('conversations.store');     Route::scopeBindings()->group(function () {        Route::get('/{conversation}', [ConversationController::class, 'show'])            ->name('conversations.show');         Route::post('/{conversation}/messages', [MessageController::class, 'store'])            ->name('conversation-messages.store');         Route::get('/{conversation}/messages/{message}', [MessageController::class, 'show'])            ->name('conversation-messages.show');    });});``` If you want a parameter to resolve to a different model class, define explicit binding. For custom binding logic, use `Route::bind()` or implement `resolveRouteBinding()` on the model. ```phpuse App\Models\AiConversation;use Illuminate\Support\Facades\Route; Route::model('conversation', AiConversation::class);``` ### Service Container Bindings Bind interfaces to implementations in a service provider for clear dependency wiring. ```phpuse App\Repositories\EloquentOrderRepository;use App\Repositories\OrderRepository;use Illuminate\Support\ServiceProvider; final class AppServiceProvider extends ServiceProvider{    public function register(): void    {        $this->app->bind(OrderRepository::class, EloquentOrderRepository::class);    }}``` ### Eloquent Model Patterns ### Model Configuration ```phpfinal class Project extends Model{    use HasFactory;     protected $fillable = ['name', 'owner_id', 'status'];     protected $casts = [        'status' => ProjectStatus::class,        'archived_at' => 'datetime',    ];     public function owner(): BelongsTo    {        return $this->belongsTo(User::class, 'owner_id');    }     public function scopeActive(Builder $query): Builder    {        return $query->whereNull('archived_at');    }}``` ### Custom Casts and Value Objects Use enums or value objects for strict typing. ```phpuse Illuminate\Database\Eloquent\Casts\Attribute; protected $casts = [    'status' => ProjectStatus::class,];``` ```phpprotected function budgetCents(): Attribute{    return Attribute::make(        get: fn (int $value) => Money::fromCents($value),        set: fn (Money $money) => $money->toCents(),    );}``` ### Eager Loading to Avoid N+1 ```php$orders = Order::query()    ->with(['customer', 'items.product'])    ->latest()    ->paginate(25);``` ### Query Objects for Complex Filters ```phpfinal class ProjectQuery{    public function __construct(private Builder $query) {}     public function ownedBy(int $userId): self    {        $query = clone $this->query;         return new self($query->where('owner_id', $userId));    }     public function active(): self    {        $query = clone $this->query;         return new self($query->whereNull('archived_at'));    }     public function builder(): Builder    {        return $this->query;    }}``` ### Global Scopes and Soft Deletes Use global scopes for default filtering and `SoftDeletes` for recoverable records.Use either a global scope or a named scope for the same filter, not both, unless you intend layered behavior. ```phpuse Illuminate\Database\Eloquent\SoftDeletes;use Illuminate\Database\Eloquent\Builder; final class Project extends Model{    use SoftDeletes;     protected static function booted(): void    {        static::addGlobalScope('active', function (Builder $builder): void {            $builder->whereNull('archived_at');        });    }}``` ### Query Scopes for Reusable Filters ```phpuse Illuminate\Database\Eloquent\Builder; final class Project extends Model{    public function scopeOwnedBy(Builder $query, int $userId): Builder    {        return $query->where('owner_id', $userId);    }} // In service, repository etc.$projects = Project::ownedBy($user->id)->get();``` ### Transactions for Multi-Step Updates ```phpuse Illuminate\Support\Facades\DB; DB::transaction(function (): void {    $order->update(['status' => 'paid']);    $order->items()->update(['paid_at' => now()]);});``` ### Migrations ### Naming Convention - File names use timestamps: `YYYY_MM_DD_HHMMSS_create_users_table.php`- Migrations use anonymous classes (no named class); the filename communicates intent- Table names are `snake_case` and plural by default ### Example Migration ```phpuse Illuminate\Database\Migrations\Migration;use Illuminate\Database\Schema\Blueprint;use Illuminate\Support\Facades\Schema; return new class extends Migration{    public function up(): void    {        Schema::create('orders', function (Blueprint $table): void {            $table->id();            $table->foreignId('customer_id')->constrained()->cascadeOnDelete();            $table->string('status', 32)->index();            $table->unsignedInteger('total_cents');            $table->timestamps();        });    }     public function down(): void    {        Schema::dropIfExists('orders');    }};``` ### Form Requests and Validation Keep validation in form requests and transform inputs to DTOs. ```phpuse App\Models\Order; final class StoreOrderRequest extends FormRequest{    public function authorize(): bool    {        return $this->user()?->can('create', Order::class) ?? false;    }     public function rules(): array    {        return [            'customer_id' => ['required', 'integer', 'exists:customers,id'],            'items' => ['required', 'array', 'min:1'],            'items.*.sku' => ['required', 'string'],            'items.*.quantity' => ['required', 'integer', 'min:1'],        ];    }     public function toDto(): CreateOrderData    {        return new CreateOrderData(            customerId: (int) $this->validated('customer_id'),            items: $this->validated('items'),        );    }}``` ### API Resources Keep API responses consistent with resources and pagination. ```php$projects = Project::query()->active()->paginate(25); return response()->json([    'success' => true,    'data' => ProjectResource::collection($projects->items()),    'error' => null,    'meta' => [        'page' => $projects->currentPage(),        'per_page' => $projects->perPage(),        'total' => $projects->total(),    ],]);``` ### Events, Jobs, and Queues - Emit domain events for side effects (emails, analytics)- Use queued jobs for slow work (reports, exports, webhooks)- Prefer idempotent handlers with retries and backoff ### Caching - Cache read-heavy endpoints and expensive queries- Invalidate caches on model events (created/updated/deleted)- Use tags when caching related data for easy invalidation ### Configuration and Environments - Keep secrets in `.env` and config in `config/*.php`- Use per-environment config overrides and `config:cache` in production