feat: add laravel skills (#420)

* feat: add laravel skills

* docs: fix laravel patterns example

* docs: add laravel api example

* docs: update readme and configure-ecc for laravel skills

* docs: reference laravel skills in php rules

* docs: add php import guidance

* docs: expand laravel skills with more pattern, security, testing, and verification examples

* docs: add laravel routing, security, testing, and sail guidance

* docs: fix laravel example issues from code review

* docs: fix laravel examples and skills per review findings

* docs: resolve remaining laravel review fixes

* docs: refine laravel patterns and tdd guidance

* docs: clarify laravel queue healthcheck guidance

* docs: fix laravel examples and test guidance

* docs: correct laravel tdd and api example details

* docs: align laravel form request auth semantics

* docs: fix laravel coverage, imports, and scope guidance

* docs: align laravel tdd and security examples with guidance

* docs: tighten laravel form request authorization examples

* docs: fix laravel tdd and queue job examples

* docs: harden laravel rate limiting and policy examples

* docs: fix laravel pagination, validation, and verification examples

* docs: align laravel controller response with envelope

* docs: strengthen laravel password validation example

* docs: address feedback regarding examples

* docs: improve guidance and examples for pest usage

* docs: clarify laravel upload storage and authorization notes

* docs: tighten up examples
This commit is contained in:
Sam Rook
2026-03-16 20:35:23 +00:00
committed by GitHub
parent 17a6ef4edb
commit 113119dc6f
11 changed files with 1504 additions and 6 deletions

View File

@@ -292,6 +292,10 @@ everything-claude-code/
| |-- django-security/ # Django security best practices (NEW) | |-- django-security/ # Django security best practices (NEW)
| |-- django-tdd/ # Django TDD workflow (NEW) | |-- django-tdd/ # Django TDD workflow (NEW)
| |-- django-verification/ # Django verification loops (NEW) | |-- django-verification/ # Django verification loops (NEW)
| |-- laravel-patterns/ # Laravel architecture patterns (NEW)
| |-- laravel-security/ # Laravel security best practices (NEW)
| |-- laravel-tdd/ # Laravel TDD workflow (NEW)
| |-- laravel-verification/ # Laravel verification loops (NEW)
| |-- python-patterns/ # Python idioms and best practices (NEW) | |-- python-patterns/ # Python idioms and best practices (NEW)
| |-- python-testing/ # Python testing with pytest (NEW) | |-- python-testing/ # Python testing with pytest (NEW)
| |-- springboot-patterns/ # Java Spring Boot patterns (NEW) | |-- springboot-patterns/ # Java Spring Boot patterns (NEW)
@@ -411,6 +415,7 @@ everything-claude-code/
| |-- saas-nextjs-CLAUDE.md # Real-world SaaS (Next.js + Supabase + Stripe) | |-- saas-nextjs-CLAUDE.md # Real-world SaaS (Next.js + Supabase + Stripe)
| |-- go-microservice-CLAUDE.md # Real-world Go microservice (gRPC + PostgreSQL) | |-- go-microservice-CLAUDE.md # Real-world Go microservice (gRPC + PostgreSQL)
| |-- django-api-CLAUDE.md # Real-world Django REST API (DRF + Celery) | |-- django-api-CLAUDE.md # Real-world Django REST API (DRF + Celery)
| |-- laravel-api-CLAUDE.md # Real-world Laravel API (PostgreSQL + Redis) (NEW)
| |-- rust-api-CLAUDE.md # Real-world Rust API (Axum + SQLx + PostgreSQL) (NEW) | |-- rust-api-CLAUDE.md # Real-world Rust API (Axum + SQLx + PostgreSQL) (NEW)
| |
|-- mcp-configs/ # MCP server configurations |-- mcp-configs/ # MCP server configurations
@@ -615,7 +620,7 @@ cp -r everything-claude-code/.agents/skills/* ~/.claude/skills/
cp -r everything-claude-code/skills/search-first ~/.claude/skills/ cp -r everything-claude-code/skills/search-first ~/.claude/skills/
# Optional: add niche/framework-specific skills only when needed # Optional: add niche/framework-specific skills only when needed
# for s in django-patterns django-tdd springboot-patterns; do # for s in django-patterns django-tdd laravel-patterns springboot-patterns; do
# cp -r everything-claude-code/skills/$s ~/.claude/skills/ # cp -r everything-claude-code/skills/$s ~/.claude/skills/
# done # done
``` ```
@@ -869,7 +874,7 @@ Please contribute! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
### Ideas for Contributions ### Ideas for Contributions
- Language-specific skills (Rust, C#, Kotlin, Java) — Go, Python, Perl, Swift, and TypeScript already included - Language-specific skills (Rust, C#, Kotlin, Java) — Go, Python, Perl, Swift, and TypeScript already included
- Framework-specific configs (Rails, Laravel, FastAPI, NestJS) — Django, Spring Boot already included - Framework-specific configs (Rails, FastAPI, NestJS) — Django, Spring Boot, Laravel already included
- DevOps agents (Kubernetes, Terraform, AWS, Docker) - DevOps agents (Kubernetes, Terraform, AWS, Docker)
- Testing strategies (different frameworks, visual regression) - Testing strategies (different frameworks, visual regression)
- Domain-specific knowledge (ML, data engineering, mobile) - Domain-specific knowledge (ML, data engineering, mobile)

View File

@@ -0,0 +1,311 @@
# Laravel API — Project CLAUDE.md
> Real-world example for a Laravel API with PostgreSQL, Redis, and queues.
> Copy this to your project root and customize for your service.
## Project Overview
**Stack:** PHP 8.2+, Laravel 11.x, PostgreSQL, Redis, Horizon, PHPUnit/Pest, Docker Compose
**Architecture:** Modular Laravel app with controllers -> services -> actions, Eloquent ORM, queues for async work, Form Requests for validation, and API Resources for consistent JSON responses.
## Critical Rules
### PHP Conventions
- `declare(strict_types=1)` in all PHP files
- Use typed properties and return types everywhere
- Prefer `final` classes for services and actions
- No `dd()` or `dump()` in committed code
- Formatting via Laravel Pint (PSR-12)
### API Response Envelope
All API responses use a consistent envelope:
```json
{
"success": true,
"data": {"...": "..."},
"error": null,
"meta": {"page": 1, "per_page": 25, "total": 120}
}
```
### Database
- Migrations committed to git
- Use Eloquent or query builder (no raw SQL unless parameterized)
- Index any column used in `where` or `orderBy`
- Avoid mutating model instances in services; prefer create/update through repositories or query builders
### Authentication
- API auth via Sanctum
- Use policies for model-level authorization
- Enforce auth in controllers and services
### Validation
- Use Form Requests for validation
- Transform input to DTOs for business logic
- Never trust request payloads for derived fields
### Error Handling
- Throw domain exceptions in services
- Map exceptions to HTTP responses in `bootstrap/app.php` via `withExceptions`
- Never expose internal errors to clients
### Code Style
- No emojis in code or comments
- Max line length: 120 characters
- Controllers are thin; services and actions hold business logic
## File Structure
```
app/
Actions/
Console/
Events/
Exceptions/
Http/
Controllers/
Middleware/
Requests/
Resources/
Jobs/
Models/
Policies/
Providers/
Services/
Support/
config/
database/
factories/
migrations/
seeders/
routes/
api.php
web.php
```
## Key Patterns
### Service Layer
```php
<?php
declare(strict_types=1);
final class CreateOrderAction
{
public function __construct(private OrderRepository $orders) {}
public function handle(CreateOrderData $data): Order
{
return $this->orders->create($data);
}
}
final class OrderService
{
public function __construct(private CreateOrderAction $createOrder) {}
public function placeOrder(CreateOrderData $data): Order
{
return $this->createOrder->handle($data);
}
}
```
### Controller Pattern
```php
<?php
declare(strict_types=1);
final class OrdersController extends Controller
{
public function __construct(private OrderService $service) {}
public function store(StoreOrderRequest $request): JsonResponse
{
$order = $this->service->placeOrder($request->toDto());
return response()->json([
'success' => true,
'data' => OrderResource::make($order),
'error' => null,
'meta' => null,
], 201);
}
}
```
### Policy Pattern
```php
<?php
declare(strict_types=1);
use App\Models\Order;
use App\Models\User;
final class OrderPolicy
{
public function view(User $user, Order $order): bool
{
return $order->user_id === $user->id;
}
}
```
### Form Request + DTO
```php
<?php
declare(strict_types=1);
final class StoreOrderRequest extends FormRequest
{
public function authorize(): bool
{
return (bool) $this->user();
}
public function rules(): array
{
return [
'items' => ['required', 'array', 'min:1'],
'items.*.sku' => ['required', 'string'],
'items.*.quantity' => ['required', 'integer', 'min:1'],
];
}
public function toDto(): CreateOrderData
{
return new CreateOrderData(
userId: (int) $this->user()->id,
items: $this->validated('items'),
);
}
}
```
### API Resource
```php
<?php
declare(strict_types=1);
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
final class OrderResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'status' => $this->status,
'total' => $this->total,
'created_at' => $this->created_at?->toIso8601String(),
];
}
}
```
### Queue Job
```php
<?php
declare(strict_types=1);
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Repositories\OrderRepository;
use App\Services\OrderMailer;
final class SendOrderConfirmation implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(private int $orderId) {}
public function handle(OrderRepository $orders, OrderMailer $mailer): void
{
$order = $orders->findOrFail($this->orderId);
$mailer->sendOrderConfirmation($order);
}
}
```
### Test Pattern (Pest)
```php
<?php
declare(strict_types=1);
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use function Pest\Laravel\actingAs;
use function Pest\Laravel\assertDatabaseHas;
use function Pest\Laravel\postJson;
uses(RefreshDatabase::class);
test('user can place order', function () {
$user = User::factory()->create();
actingAs($user);
$response = postJson('/api/orders', [
'items' => [['sku' => 'sku-1', 'quantity' => 2]],
]);
$response->assertCreated();
assertDatabaseHas('orders', ['user_id' => $user->id]);
});
```
### Test Pattern (PHPUnit)
```php
<?php
declare(strict_types=1);
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
final class OrdersControllerTest extends TestCase
{
use RefreshDatabase;
public function test_user_can_place_order(): void
{
$user = User::factory()->create();
$response = $this->actingAs($user)->postJson('/api/orders', [
'items' => [['sku' => 'sku-1', 'quantity' => 2]],
]);
$response->assertCreated();
$this->assertDatabaseHas('orders', ['user_id' => $user->id]);
}
}
```

View File

@@ -25,6 +25,11 @@ paths:
- Use **PHPStan** or **Psalm** for static analysis. - Use **PHPStan** or **Psalm** for static analysis.
- Keep Composer scripts checked in so the same commands run locally and in CI. - Keep Composer scripts checked in so the same commands run locally and in CI.
## Imports
- Add `use` statements for all referenced classes, interfaces, and traits.
- Avoid relying on the global namespace unless the project explicitly prefers fully qualified names.
## Error Handling ## Error Handling
- Throw exceptions for exceptional states; avoid returning `false`/`null` as hidden error channels in new code. - Throw exceptions for exceptional states; avoid returning `false`/`null` as hidden error channels in new code.

View File

@@ -30,3 +30,4 @@ paths:
## Reference ## Reference
See skill: `api-design` for endpoint conventions and response-shape guidance. See skill: `api-design` for endpoint conventions and response-shape guidance.
See skill: `laravel-patterns` for Laravel-specific architecture guidance.

View File

@@ -31,3 +31,7 @@ paths:
- Use `password_hash()` / `password_verify()` for password storage. - Use `password_hash()` / `password_verify()` for password storage.
- Regenerate session identifiers after authentication and privilege changes. - Regenerate session identifiers after authentication and privilege changes.
- Enforce CSRF protection on state-changing web requests. - Enforce CSRF protection on state-changing web requests.
## Reference
See skill: `laravel-security` for Laravel-specific security guidance.

View File

@@ -11,7 +11,7 @@ paths:
## Framework ## Framework
Use **PHPUnit** as the default test framework. **Pest** is also acceptable when the project already uses it. Use **PHPUnit** as the default test framework. If **Pest** is configured in the project, prefer Pest for new tests and avoid mixing frameworks.
## Coverage ## Coverage
@@ -29,6 +29,11 @@ Prefer **pcov** or **Xdebug** in CI, and keep coverage thresholds in CI rather t
- Use factory/builders for fixtures instead of large hand-written arrays. - Use factory/builders for fixtures instead of large hand-written arrays.
- Keep HTTP/controller tests focused on transport and validation; move business rules into service-level tests. - Keep HTTP/controller tests focused on transport and validation; move business rules into service-level tests.
## Inertia
If the project uses Inertia.js, prefer `assertInertia` with `AssertableInertia` to verify component names and props instead of raw JSON assertions.
## Reference ## Reference
See skill: `tdd-workflow` for the repo-wide RED -> GREEN -> REFACTOR loop. See skill: `tdd-workflow` for the repo-wide RED -> GREEN -> REFACTOR loop.
See skill: `laravel-tdd` for Laravel-specific testing patterns (PHPUnit and Pest).

View File

@@ -82,12 +82,12 @@ If the user chooses niche or core + niche, continue to category selection below
### 2b: Choose Skill Categories ### 2b: Choose Skill Categories
There are 7 selectable category groups below. The detailed confirmation lists that follow cover 41 skills across 8 categories, plus 1 standalone template. Use `AskUserQuestion` with `multiSelect: true`: There are 7 selectable category groups below. The detailed confirmation lists that follow cover 45 skills across 8 categories, plus 1 standalone template. Use `AskUserQuestion` with `multiSelect: true`:
``` ```
Question: "Which skill categories do you want to install?" Question: "Which skill categories do you want to install?"
Options: Options:
- "Framework & Language" — "Django, Spring Boot, Go, Python, Java, Frontend, Backend patterns" - "Framework & Language" — "Django, Laravel, Spring Boot, Go, Python, Java, Frontend, Backend patterns"
- "Database" — "PostgreSQL, ClickHouse, JPA/Hibernate patterns" - "Database" — "PostgreSQL, ClickHouse, JPA/Hibernate patterns"
- "Workflow & Quality" — "TDD, verification, learning, security review, compaction" - "Workflow & Quality" — "TDD, verification, learning, security review, compaction"
- "Research & APIs" — "Deep research, Exa search, Claude API patterns" - "Research & APIs" — "Deep research, Exa search, Claude API patterns"
@@ -101,7 +101,7 @@ Options:
For each selected category, print the full list of skills below and ask the user to confirm or deselect specific ones. If the list exceeds 4 items, print the list as text and use `AskUserQuestion` with an "Install all listed" option plus "Other" for the user to paste specific names. For each selected category, print the full list of skills below and ask the user to confirm or deselect specific ones. If the list exceeds 4 items, print the list as text and use `AskUserQuestion` with an "Install all listed" option plus "Other" for the user to paste specific names.
**Category: Framework & Language (17 skills)** **Category: Framework & Language (21 skills)**
| Skill | Description | | Skill | Description |
|-------|-------------| |-------|-------------|
@@ -111,6 +111,10 @@ For each selected category, print the full list of skills below and ask the user
| `django-security` | Django security: auth, CSRF, SQL injection, XSS prevention | | `django-security` | Django security: auth, CSRF, SQL injection, XSS prevention |
| `django-tdd` | Django testing with pytest-django, factory_boy, mocking, coverage | | `django-tdd` | Django testing with pytest-django, factory_boy, mocking, coverage |
| `django-verification` | Django verification loop: migrations, linting, tests, security scans | | `django-verification` | Django verification loop: migrations, linting, tests, security scans |
| `laravel-patterns` | Laravel architecture patterns: routing, controllers, Eloquent, queues, caching |
| `laravel-security` | Laravel security: auth, policies, CSRF, mass assignment, rate limiting |
| `laravel-tdd` | Laravel testing with PHPUnit and Pest, factories, fakes, coverage |
| `laravel-verification` | Laravel verification: linting, static analysis, tests, security scans |
| `frontend-patterns` | React, Next.js, state management, performance, UI patterns | | `frontend-patterns` | React, Next.js, state management, performance, UI patterns |
| `frontend-slides` | Zero-dependency HTML presentations, style previews, and PPTX-to-web conversion | | `frontend-slides` | Zero-dependency HTML presentations, style previews, and PPTX-to-web conversion |
| `golang-patterns` | Idiomatic Go patterns, conventions for robust Go applications | | `golang-patterns` | Idiomatic Go patterns, conventions for robust Go applications |
@@ -258,6 +262,7 @@ grep -rn "skills/" $TARGET/skills/
Some skills reference others. Verify these dependencies: Some skills reference others. Verify these dependencies:
- `django-tdd` may reference `django-patterns` - `django-tdd` may reference `django-patterns`
- `laravel-tdd` may reference `laravel-patterns`
- `springboot-tdd` may reference `springboot-patterns` - `springboot-tdd` may reference `springboot-patterns`
- `continuous-learning-v2` references `~/.claude/homunculus/` directory - `continuous-learning-v2` references `~/.claude/homunculus/` directory
- `python-testing` may reference `python-patterns` - `python-testing` may reference `python-patterns`

View File

@@ -0,0 +1,415 @@
---
name: laravel-patterns
description: 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.
```php
final 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.
```php
use 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.
```php
Route::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.
```php
use 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.
```php
use 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.
```php
use 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
```php
final 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.
```php
use Illuminate\Database\Eloquent\Casts\Attribute;
protected $casts = [
'status' => ProjectStatus::class,
];
```
```php
protected 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
```php
final 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.
```php
use 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
```php
use 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
```php
use 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
```php
use 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.
```php
use 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

View File

@@ -0,0 +1,285 @@
---
name: laravel-security
description: Laravel security best practices for authn/authz, validation, CSRF, mass assignment, file uploads, secrets, rate limiting, and secure deployment.
origin: ECC
---
# Laravel Security Best Practices
Comprehensive security guidance for Laravel applications to protect against common vulnerabilities.
## When to Activate
- Adding authentication or authorization
- Handling user input and file uploads
- Building new API endpoints
- Managing secrets and environment settings
- Hardening production deployments
## How It Works
- Middleware provides baseline protections (CSRF via `VerifyCsrfToken`, security headers via `SecurityHeaders`).
- Guards and policies enforce access control (`auth:sanctum`, `$this->authorize`, policy middleware).
- Form Requests validate and shape input (`UploadInvoiceRequest`) before it reaches services.
- Rate limiting adds abuse protection (`RateLimiter::for('login')`) alongside auth controls.
- Data safety comes from encrypted casts, mass-assignment guards, and signed routes (`URL::temporarySignedRoute` + `signed` middleware).
## Core Security Settings
- `APP_DEBUG=false` in production
- `APP_KEY` must be set and rotated on compromise
- Set `SESSION_SECURE_COOKIE=true` and `SESSION_SAME_SITE=lax` (or `strict` for sensitive apps)
- Configure trusted proxies for correct HTTPS detection
## Session and Cookie Hardening
- Set `SESSION_HTTP_ONLY=true` to prevent JavaScript access
- Use `SESSION_SAME_SITE=strict` for high-risk flows
- Regenerate sessions on login and privilege changes
## Authentication and Tokens
- Use Laravel Sanctum or Passport for API auth
- Prefer short-lived tokens with refresh flows for sensitive data
- Revoke tokens on logout and compromised accounts
Example route protection:
```php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
Route::middleware('auth:sanctum')->get('/me', function (Request $request) {
return $request->user();
});
```
## Password Security
- Hash passwords with `Hash::make()` and never store plaintext
- Use Laravel's password broker for reset flows
```php
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules\Password;
$validated = $request->validate([
'password' => ['required', 'string', Password::min(12)->letters()->mixedCase()->numbers()->symbols()],
]);
$user->update(['password' => Hash::make($validated['password'])]);
```
## Authorization: Policies and Gates
- Use policies for model-level authorization
- Enforce authorization in controllers and services
```php
$this->authorize('update', $project);
```
Use policy middleware for route-level enforcement:
```php
use Illuminate\Support\Facades\Route;
Route::put('/projects/{project}', [ProjectController::class, 'update'])
->middleware(['auth:sanctum', 'can:update,project']);
```
## Validation and Data Sanitization
- Always validate inputs with Form Requests
- Use strict validation rules and type checks
- Never trust request payloads for derived fields
## Mass Assignment Protection
- Use `$fillable` or `$guarded` and avoid `Model::unguard()`
- Prefer DTOs or explicit attribute mapping
## SQL Injection Prevention
- Use Eloquent or query builder parameter binding
- Avoid raw SQL unless strictly necessary
```php
DB::select('select * from users where email = ?', [$email]);
```
## XSS Prevention
- Blade escapes output by default (`{{ }}`)
- Use `{!! !!}` only for trusted, sanitized HTML
- Sanitize rich text with a dedicated library
## CSRF Protection
- Keep `VerifyCsrfToken` middleware enabled
- Include `@csrf` in forms and send XSRF tokens for SPA requests
For SPA authentication with Sanctum, ensure stateful requests are configured:
```php
// config/sanctum.php
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', 'localhost')),
```
## File Upload Safety
- Validate file size, MIME type, and extension
- Store uploads outside the public path when possible
- Scan files for malware if required
```php
final class UploadInvoiceRequest extends FormRequest
{
public function authorize(): bool
{
return (bool) $this->user()?->can('upload-invoice');
}
public function rules(): array
{
return [
'invoice' => ['required', 'file', 'mimes:pdf', 'max:5120'],
];
}
}
```
```php
$path = $request->file('invoice')->store(
'invoices',
config('filesystems.private_disk', 'local') // set this to a non-public disk
);
```
## Rate Limiting
- Apply `throttle` middleware on auth and write endpoints
- Use stricter limits for login, password reset, and OTP
```php
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
RateLimiter::for('login', function (Request $request) {
return [
Limit::perMinute(5)->by($request->ip()),
Limit::perMinute(5)->by(strtolower((string) $request->input('email'))),
];
});
```
## Secrets and Credentials
- Never commit secrets to source control
- Use environment variables and secret managers
- Rotate keys after exposure and invalidate sessions
## Encrypted Attributes
Use encrypted casts for sensitive columns at rest.
```php
protected $casts = [
'api_token' => 'encrypted',
];
```
## Security Headers
- Add CSP, HSTS, and frame protection where appropriate
- Use trusted proxy configuration to enforce HTTPS redirects
Example middleware to set headers:
```php
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
final class SecurityHeaders
{
public function handle(Request $request, \Closure $next): Response
{
$response = $next($request);
$response->headers->add([
'Content-Security-Policy' => "default-src 'self'",
'Strict-Transport-Security' => 'max-age=31536000', // add includeSubDomains/preload only when all subdomains are HTTPS
'X-Frame-Options' => 'DENY',
'X-Content-Type-Options' => 'nosniff',
'Referrer-Policy' => 'no-referrer',
]);
return $response;
}
}
```
## CORS and API Exposure
- Restrict origins in `config/cors.php`
- Avoid wildcard origins for authenticated routes
```php
// config/cors.php
return [
'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
'allowed_origins' => ['https://app.example.com'],
'allowed_headers' => [
'Content-Type',
'Authorization',
'X-Requested-With',
'X-XSRF-TOKEN',
'X-CSRF-TOKEN',
],
'supports_credentials' => true,
];
```
## Logging and PII
- Never log passwords, tokens, or full card data
- Redact sensitive fields in structured logs
```php
use Illuminate\Support\Facades\Log;
Log::info('User updated profile', [
'user_id' => $user->id,
'email' => '[REDACTED]',
'token' => '[REDACTED]',
]);
```
## Dependency Security
- Run `composer audit` regularly
- Pin dependencies with care and update promptly on CVEs
## Signed URLs
Use signed routes for temporary, tamper-proof links.
```php
use Illuminate\Support\Facades\URL;
$url = URL::temporarySignedRoute(
'downloads.invoice',
now()->addMinutes(15),
['invoice' => $invoice->id]
);
```
```php
use Illuminate\Support\Facades\Route;
Route::get('/invoices/{invoice}/download', [InvoiceController::class, 'download'])
->name('downloads.invoice')
->middleware('signed');
```

283
skills/laravel-tdd/SKILL.md Normal file
View File

@@ -0,0 +1,283 @@
---
name: laravel-tdd
description: Test-driven development for Laravel with PHPUnit and Pest, factories, database testing, fakes, and coverage targets.
origin: ECC
---
# Laravel TDD Workflow
Test-driven development for Laravel applications using PHPUnit and Pest with 80%+ coverage (unit + feature).
## When to Use
- New features or endpoints in Laravel
- Bug fixes or refactors
- Testing Eloquent models, policies, jobs, and notifications
- Prefer Pest for new tests unless the project already standardizes on PHPUnit
## How It Works
### Red-Green-Refactor Cycle
1) Write a failing test
2) Implement the minimal change to pass
3) Refactor while keeping tests green
### Test Layers
- **Unit**: pure PHP classes, value objects, services
- **Feature**: HTTP endpoints, auth, validation, policies
- **Integration**: database + queue + external boundaries
Choose layers based on scope:
- Use **Unit** tests for pure business logic and services.
- Use **Feature** tests for HTTP, auth, validation, and response shape.
- Use **Integration** tests when validating DB/queues/external services together.
### Database Strategy
- `RefreshDatabase` for most feature/integration tests (runs migrations once per test run, then wraps each test in a transaction when supported; in-memory databases may re-migrate per test)
- `DatabaseTransactions` when the schema is already migrated and you only need per-test rollback
- `DatabaseMigrations` when you need a full migrate/fresh for every test and can afford the cost
Use `RefreshDatabase` as the default for tests that touch the database: for databases with transaction support, it runs migrations once per test run (via a static flag) and wraps each test in a transaction; for `:memory:` SQLite or connections without transactions, it migrates before each test. Use `DatabaseTransactions` when the schema is already migrated and you only need per-test rollbacks.
### Testing Framework Choice
- Default to **Pest** for new tests when available.
- Use **PHPUnit** only if the project already standardizes on it or requires PHPUnit-specific tooling.
## Examples
### PHPUnit Example
```php
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
final class ProjectControllerTest extends TestCase
{
use RefreshDatabase;
public function test_owner_can_create_project(): void
{
$user = User::factory()->create();
$response = $this->actingAs($user)->postJson('/api/projects', [
'name' => 'New Project',
]);
$response->assertCreated();
$this->assertDatabaseHas('projects', ['name' => 'New Project']);
}
}
```
### Feature Test Example (HTTP Layer)
```php
use App\Models\Project;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
final class ProjectIndexTest extends TestCase
{
use RefreshDatabase;
public function test_projects_index_returns_paginated_results(): void
{
$user = User::factory()->create();
Project::factory()->count(3)->for($user)->create();
$response = $this->actingAs($user)->getJson('/api/projects');
$response->assertOk();
$response->assertJsonStructure(['success', 'data', 'error', 'meta']);
}
}
```
### Pest Example
```php
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use function Pest\Laravel\actingAs;
use function Pest\Laravel\assertDatabaseHas;
uses(RefreshDatabase::class);
test('owner can create project', function () {
$user = User::factory()->create();
$response = actingAs($user)->postJson('/api/projects', [
'name' => 'New Project',
]);
$response->assertCreated();
assertDatabaseHas('projects', ['name' => 'New Project']);
});
```
### Feature Test Pest Example (HTTP Layer)
```php
use App\Models\Project;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use function Pest\Laravel\actingAs;
uses(RefreshDatabase::class);
test('projects index returns paginated results', function () {
$user = User::factory()->create();
Project::factory()->count(3)->for($user)->create();
$response = actingAs($user)->getJson('/api/projects');
$response->assertOk();
$response->assertJsonStructure(['success', 'data', 'error', 'meta']);
});
```
### Factories and States
- Use factories for test data
- Define states for edge cases (archived, admin, trial)
```php
$user = User::factory()->state(['role' => 'admin'])->create();
```
### Database Testing
- Use `RefreshDatabase` for clean state
- Keep tests isolated and deterministic
- Prefer `assertDatabaseHas` over manual queries
### Persistence Test Example
```php
use App\Models\Project;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
final class ProjectRepositoryTest extends TestCase
{
use RefreshDatabase;
public function test_project_can_be_retrieved_by_slug(): void
{
$project = Project::factory()->create(['slug' => 'alpha']);
$found = Project::query()->where('slug', 'alpha')->firstOrFail();
$this->assertSame($project->id, $found->id);
}
}
```
### Fakes for Side Effects
- `Bus::fake()` for jobs
- `Queue::fake()` for queued work
- `Mail::fake()` and `Notification::fake()` for notifications
- `Event::fake()` for domain events
```php
use Illuminate\Support\Facades\Queue;
Queue::fake();
dispatch(new SendOrderConfirmation($order->id));
Queue::assertPushed(SendOrderConfirmation::class);
```
```php
use Illuminate\Support\Facades\Notification;
Notification::fake();
$user->notify(new InvoiceReady($invoice));
Notification::assertSentTo($user, InvoiceReady::class);
```
### Auth Testing (Sanctum)
```php
use Laravel\Sanctum\Sanctum;
Sanctum::actingAs($user);
$response = $this->getJson('/api/projects');
$response->assertOk();
```
### HTTP and External Services
- Use `Http::fake()` to isolate external APIs
- Assert outbound payloads with `Http::assertSent()`
### Coverage Targets
- Enforce 80%+ coverage for unit + feature tests
- Use `pcov` or `XDEBUG_MODE=coverage` in CI
### Test Commands
- `php artisan test`
- `vendor/bin/phpunit`
- `vendor/bin/pest`
### Test Configuration
- Use `phpunit.xml` to set `DB_CONNECTION=sqlite` and `DB_DATABASE=:memory:` for fast tests
- Keep separate env for tests to avoid touching dev/prod data
### Authorization Tests
```php
use Illuminate\Support\Facades\Gate;
$this->assertTrue(Gate::forUser($user)->allows('update', $project));
$this->assertFalse(Gate::forUser($otherUser)->allows('update', $project));
```
### Inertia Feature Tests
When using Inertia.js, assert on the component name and props with the Inertia testing helpers.
```php
use App\Models\User;
use Inertia\Testing\AssertableInertia;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
final class DashboardInertiaTest extends TestCase
{
use RefreshDatabase;
public function test_dashboard_inertia_props(): void
{
$user = User::factory()->create();
$response = $this->actingAs($user)->get('/dashboard');
$response->assertOk();
$response->assertInertia(fn (AssertableInertia $page) => $page
->component('Dashboard')
->where('user.id', $user->id)
->has('projects')
);
}
}
```
Prefer `assertInertia` over raw JSON assertions to keep tests aligned with Inertia responses.

View File

@@ -0,0 +1,179 @@
---
name: laravel-verification
description: Verification loop for Laravel projects: env checks, linting, static analysis, tests with coverage, security scans, and deployment readiness.
origin: ECC
---
# Laravel Verification Loop
Run before PRs, after major changes, and pre-deploy.
## When to Use
- Before opening a pull request for a Laravel project
- After major refactors or dependency upgrades
- Pre-deployment verification for staging or production
- Running full lint -> test -> security -> deploy readiness pipeline
## How It Works
- Run phases sequentially from environment checks through deployment readiness so each layer builds on the last.
- Environment and Composer checks gate everything else; stop immediately if they fail.
- Linting/static analysis should be clean before running full tests and coverage.
- Security and migration reviews happen after tests so you verify behavior before data or release steps.
- Build/deploy readiness and queue/scheduler checks are final gates; any failure blocks release.
## Phase 1: Environment Checks
```bash
php -v
composer --version
php artisan --version
```
- Verify `.env` is present and required keys exist
- Confirm `APP_DEBUG=false` for production environments
- Confirm `APP_ENV` matches the target deployment (`production`, `staging`)
If using Laravel Sail locally:
```bash
./vendor/bin/sail php -v
./vendor/bin/sail artisan --version
```
## Phase 1.5: Composer and Autoload
```bash
composer validate
composer dump-autoload -o
```
## Phase 2: Linting and Static Analysis
```bash
vendor/bin/pint --test
vendor/bin/phpstan analyse
```
If your project uses Psalm instead of PHPStan:
```bash
vendor/bin/psalm
```
## Phase 3: Tests and Coverage
```bash
php artisan test
```
Coverage (CI):
```bash
XDEBUG_MODE=coverage php artisan test --coverage
```
CI example (format -> static analysis -> tests):
```bash
vendor/bin/pint --test
vendor/bin/phpstan analyse
XDEBUG_MODE=coverage php artisan test --coverage
```
## Phase 4: Security and Dependency Checks
```bash
composer audit
```
## Phase 5: Database and Migrations
```bash
php artisan migrate --pretend
php artisan migrate:status
```
- Review destructive migrations carefully
- Ensure migration filenames follow `Y_m_d_His_*` (e.g., `2025_03_14_154210_create_orders_table.php`) and describe the change clearly
- Ensure rollbacks are possible
- Verify `down()` methods and avoid irreversible data loss without explicit backups
## Phase 6: Build and Deployment Readiness
```bash
php artisan optimize:clear
php artisan config:cache
php artisan route:cache
php artisan view:cache
```
- Ensure cache warmups succeed in production configuration
- Verify queue workers and scheduler are configured
- Confirm `storage/` and `bootstrap/cache/` are writable in the target environment
## Phase 7: Queue and Scheduler Checks
```bash
php artisan schedule:list
php artisan queue:failed
```
If Horizon is used:
```bash
php artisan horizon:status
```
If `queue:monitor` is available, use it to check backlog without processing jobs:
```bash
php artisan queue:monitor default --max=100
```
Active verification (staging only): dispatch a no-op job to a dedicated queue and run a single worker to process it (ensure a non-`sync` queue connection is configured).
```bash
php artisan tinker --execute="dispatch((new App\\Jobs\\QueueHealthcheck())->onQueue('healthcheck'))"
php artisan queue:work --once --queue=healthcheck
```
Verify the job produced the expected side effect (log entry, healthcheck table row, or metric).
Only run this on non-production environments where processing a test job is safe.
## Examples
Minimal flow:
```bash
php -v
composer --version
php artisan --version
composer validate
vendor/bin/pint --test
vendor/bin/phpstan analyse
php artisan test
composer audit
php artisan migrate --pretend
php artisan config:cache
php artisan queue:failed
```
CI-style pipeline:
```bash
composer validate
composer dump-autoload -o
vendor/bin/pint --test
vendor/bin/phpstan analyse
XDEBUG_MODE=coverage php artisan test --coverage
composer audit
php artisan migrate --pretend
php artisan optimize:clear
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan schedule:list
```