Help us improve
Share bugs, ideas, or general feedback.
From ensemble-development
Provides Laravel patterns for PHP apps including Eloquent ORM, migrations, routing, controllers, queues, jobs, authentication, middleware, and testing. Use for Laravel projects.
npx claudepluginhub fortiumpartners/ensemble --plugin ensemble-developmentHow this skill is triggered — by the user, by Claude, or both
Slash command
/ensemble-development:developing-with-laravelThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Laravel framework patterns for modern PHP applications. For PHP language fundamentals, see the PHP skill. For advanced patterns, see [REFERENCE.md](./REFERENCE.md).
README.mdREFERENCE.mdVALIDATION.mdexamples/api_resource.example.phpexamples/artisan_patterns.example.phpexamples/eloquent_patterns.example.phpexamples/multi_tenant.example.phpexamples/passport_oauth.example.phpexamples/permission_acl.example.phpexamples/queue_workflow.example.phptemplates/command.template.phptemplates/command_consumer.template.phptemplates/command_scheduled.template.phptemplates/controller.template.phptemplates/custom_rule.template.phptemplates/event.template.phptemplates/factory.template.phptemplates/form_request.template.phptemplates/job.template.phptemplates/listener.template.phpProvides Laravel 11+ architecture decision trees, Eloquent relationship references, action classes, repositories, services, authentication, queues, and testing patterns.
Provides Laravel patterns and best practices for Eloquent ORM, model relationships, authentication, API resources, testing, caching, and queues.
Prompts with Laravel-specific patterns like Eloquent queries, Form Requests, API Resources, Jobs/Queues to generate idiomatic framework code for DB ops, validation, APIs, background tasks.
Share bugs, ideas, or general feedback.
Laravel framework patterns for modern PHP applications. For PHP language fundamentals, see the PHP skill. For advanced patterns, see REFERENCE.md.
app/
├── Console/Commands/ # Artisan commands
├── Http/
│ ├── Controllers/ # Request handlers
│ ├── Middleware/ # Request/response filters
│ └── Requests/ # Form validation
├── Jobs/ # Queueable jobs
├── Models/ # Eloquent models
├── Providers/ # Service providers
└── Services/ # Business logic
config/ # Configuration files
database/
├── factories/ # Model factories
├── migrations/ # Database migrations
└── seeders/ # Database seeders
routes/
├── api.php # API routes
└── web.php # Web routes
tests/
├── Feature/ # Integration tests
└── Unit/ # Unit tests
// Basic routes
Route::get('/users', [UserController::class, 'index']);
Route::post('/users', [UserController::class, 'store']);
// Resource routes (all CRUD)
Route::resource('posts', PostController::class);
Route::apiResource('comments', CommentController::class);
// Route groups with middleware
Route::prefix('api/v1')->middleware(['auth:sanctum'])->group(function () {
Route::get('/profile', [ProfileController::class, 'show']);
});
class UserController extends Controller
{
public function index()
{
return UserResource::collection(
User::with(['profile', 'roles'])->paginate(20)
);
}
public function store(StoreUserRequest $request)
{
return new UserResource(User::create($request->validated()));
}
public function show(User $user) // Route model binding
{
return new UserResource($user->load('profile'));
}
}
class Post extends Model
{
use HasFactory, SoftDeletes;
protected $fillable = ['title', 'slug', 'content', 'status', 'author_id'];
protected $casts = ['status' => PostStatus::class, 'published_at' => 'datetime'];
protected $with = ['author']; // Always eager load
// Relationships
public function author(): BelongsTo
{
return $this->belongsTo(User::class, 'author_id');
}
public function tags(): BelongsToMany
{
return $this->belongsToMany(Tag::class)->withTimestamps();
}
// Scopes
public function scopePublished(Builder $query): void
{
$query->where('status', PostStatus::Published)
->where('published_at', '<=', now());
}
// Accessors (Laravel 9+)
protected function excerpt(): Attribute
{
return Attribute::make(
get: fn () => Str::limit(strip_tags($this->content), 150),
);
}
}
| Method | Relationship | Example |
|---|---|---|
hasOne | 1:1 | User has one Profile |
belongsTo | 1:1 inverse | Profile belongs to User |
hasMany | 1:n | User has many Posts |
belongsToMany | n:n | Post has many Tags |
morphMany | 1:n polymorphic | Post has many Comments |
Advanced: For hasOneThrough, hasManyThrough, polymorphic relationships, see REFERENCE.md
// Filtering
$posts = Post::where('status', 'published')
->whereHas('tags', fn($q) => $q->where('name', 'laravel'))
->with(['author', 'comments'])
->latest('published_at')
->paginate(10);
// Aggregates
$count = Post::where('status', 'published')->count();
$avg = Order::avg('total');
// Chunking for large datasets
Post::chunk(100, fn($posts) => $posts->each->process());
// Closure-based (auto commit/rollback)
$order = DB::transaction(function () use ($data) {
$order = Order::create($data['order']);
foreach ($data['items'] as $item) {
$order->items()->create($item);
}
return $order;
});
class StorePostRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user()->can('create', Post::class);
}
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:255'],
'slug' => ['required', Rule::unique('posts')->ignore($this->post)],
'content' => ['required', 'string', 'min:100'],
'status' => ['required', Rule::enum(PostStatus::class)],
'category_id' => ['required', 'exists:categories,id'],
'tags' => ['array'],
'tags.*' => ['exists:tags,id'],
];
}
public function messages(): array
{
return ['title.required' => 'A post title is required.'];
}
}
| Rule | Description |
|---|---|
required | Must be present and not empty |
nullable | Can be null |
string, integer, boolean | Type validation |
email | Valid email format |
unique:table,column | Unique in database |
exists:table,column | Must exist in database |
in:a,b,c | Must be one of values |
min:n, max:n | Size constraints |
Advanced: For custom validation rules and complex conditional validation, see REFERENCE.md
class EnsureUserIsActive
{
public function handle(Request $request, Closure $next): Response
{
if (!$request->user()?->isActive()) {
return response()->json(['message' => 'Account inactive.'], 403);
}
return $next($request);
}
}
// With parameters
class CheckRole
{
public function handle(Request $request, Closure $next, string ...$roles): Response
{
if (!$request->user()?->hasAnyRole($roles)) {
abort(403);
}
return $next($request);
}
}
// Usage: Route::middleware('role:admin,moderator')
Advanced: For tenant-scoping middleware and session management, see REFERENCE.md
// Login and issue token
public function login(LoginRequest $request)
{
$user = User::where('email', $request->email)->first();
if (!$user || !Hash::check($request->password, $user->password)) {
throw ValidationException::withMessages([
'email' => ['Invalid credentials.'],
]);
}
$user->tokens()->delete(); // Revoke existing
$token = $user->createToken('api-token', ['read', 'write'])->plainTextToken;
return response()->json(['user' => new UserResource($user), 'token' => $token]);
}
// Protected routes
Route::middleware('auth:sanctum')->group(function () {
Route::get('/user', fn(Request $request) => $request->user());
});
Advanced: For Passport OAuth, spatie/permission RBAC, see REFERENCE.md
class ProcessContacts extends Command
{
protected $signature = 'contacts:process
{--status=active : Filter by status}
{--limit=100 : Maximum to process}
{--dry-run : Simulate without changes}';
protected $description = 'Process contacts based on criteria';
public function handle(): int
{
$query = Contact::where('status', $this->option('status'))
->limit((int) $this->option('limit'));
if (!$this->confirm("Process {$query->count()} contacts?")) {
return Command::SUCCESS;
}
$this->withProgressBar($query->cursor(), fn($c) => $this->process($c));
$this->newLine();
$this->info('Done.');
return Command::SUCCESS;
}
}
| Command | Description |
|---|---|
php artisan make:model -mfc | Model + migration + factory + controller |
php artisan make:controller --api | API resource controller |
php artisan make:request | Form request validation |
php artisan make:job | Queueable job |
php artisan queue:work | Process queue jobs |
php artisan schedule:run | Run scheduled tasks |
Advanced: For long-running consumers and graceful shutdown, see REFERENCE.md
class SendWelcomeEmail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public int $tries = 3;
public int $timeout = 60;
public array $backoff = [30, 60, 120];
public function __construct(public User $user) {}
public function handle(Mailer $mailer): void
{
$mailer->to($this->user->email)->send(new WelcomeMail($this->user));
}
public function failed(\Throwable $e): void
{
Log::error('Welcome email failed', ['user' => $this->user->id, 'error' => $e->getMessage()]);
}
}
// Basic dispatch
SendWelcomeEmail::dispatch($user);
// With options
SendWelcomeEmail::dispatch($user)
->onQueue('emails')
->delay(now()->addMinutes(10));
// Conditional
SendWelcomeEmail::dispatchIf($user->wantsEmails(), $user);
Advanced: For job batching, chaining, and rate limiting, see REFERENCE.md
class PostPublished implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public function __construct(public Post $post) {}
public function broadcastOn(): array
{
return [new PrivateChannel("user.{$this->post->author_id}")];
}
}
// Dispatch: event(new PostPublished($post));
class PostControllerTest extends TestCase
{
use RefreshDatabase;
public function test_user_can_create_post(): void
{
$user = User::factory()->create();
$response = $this->actingAs($user)->postJson('/api/posts', [
'title' => 'Test Post',
'content' => 'Test content for the post.',
]);
$response->assertStatus(201)
->assertJson(['data' => ['title' => 'Test Post']]);
$this->assertDatabaseHas('posts', [
'title' => 'Test Post',
'author_id' => $user->id,
]);
}
}
public function test_order_processing(): void
{
Queue::fake();
$gateway = $this->mock(PaymentGateway::class);
$gateway->shouldReceive('charge')->once()->andReturn(['id' => 'ch_123']);
$this->postJson('/api/orders', [...]);
Queue::assertPushed(ProcessOrder::class);
}
Advanced: For testing commands, complex mocking, see REFERENCE.md
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
// Bind interfaces to implementations
$this->app->bind(UserRepositoryInterface::class, UserRepository::class);
// Singleton binding
$this->app->singleton(PaymentGateway::class, fn($app) =>
new StripeGateway(config('services.stripe.key'))
);
}
public function boot(): void
{
Model::preventLazyLoading(!app()->isProduction());
}
}
Advanced: For contextual binding and deferred providers, see REFERENCE.md
// app/Console/Kernel.php
protected function schedule(Schedule $schedule): void
{
$schedule->command('queue:work --stop-when-empty')
->everyMinute()
->withoutOverlapping();
$schedule->command('reports:generate')
->dailyAt('00:00')
->onOneServer()
->emailOutputOnFailure('admin@example.com');
$schedule->job(new ProcessPendingOrders)
->hourly()
->between('08:00', '22:00');
}
For advanced patterns including multi-tenancy, repository pattern, performance optimization, and debugging, see REFERENCE.md