Claude-skill-registry developing-with-laravel
Laravel framework patterns for PHP applications including Eloquent ORM, migrations, routing, queues, and Blade templates. Use when building Laravel applications or working with Laravel projects.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/developing-with-laravel" ~/.claude/skills/majiayu000-claude-skill-registry-developing-with-laravel && rm -rf "$T"
skills/data/developing-with-laravel/SKILL.mdLaravel Skill - Quick Reference
Laravel framework patterns for modern PHP applications. For PHP language fundamentals, see the PHP skill. For advanced patterns, see REFERENCE.md.
Table of Contents
- Project Structure
- Routing & Controllers
- Eloquent ORM
- Validation
- Middleware
- Authentication
- Artisan CLI
- Queues & Jobs
- Events & Listeners
- Testing
- Service Providers
- Task Scheduling
Project Structure
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
Routing & Controllers
Route Definitions
// 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']); });
Controllers
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')); } }
Eloquent ORM
Model Definition
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), ); } }
Relationships Quick Reference
| Method | Relationship | Example |
|---|---|---|
| 1:1 | User has one Profile |
| 1:1 inverse | Profile belongs to User |
| 1:n | User has many Posts |
| n:n | Post has many Tags |
| 1:n polymorphic | Post has many Comments |
Advanced: For hasOneThrough, hasManyThrough, polymorphic relationships, see REFERENCE.md
Query Builder
// 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());
Transactions
// 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; });
Validation
Form Requests
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.']; } }
Common Validation Rules
| Rule | Description |
|---|---|
| Must be present and not empty |
| Can be null |
, , | Type validation |
| Valid email format |
| Unique in database |
| Must exist in database |
| Must be one of values |
, | Size constraints |
Advanced: For custom validation rules and complex conditional validation, see REFERENCE.md
Middleware
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
Authentication
Sanctum (API Tokens)
// 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
Artisan CLI
Custom Command
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; } }
Common Artisan Commands
| Command | Description |
|---|---|
| Model + migration + factory + controller |
| API resource controller |
| Form request validation |
| Queueable job |
| Process queue jobs |
| Run scheduled tasks |
Advanced: For long-running consumers and graceful shutdown, see REFERENCE.md
Queues & Jobs
Job Definition
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()]); } }
Dispatching Jobs
// 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
Events & Listeners
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));
Testing
Feature Test
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, ]); } }
Mocking
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
Service Providers
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
Task Scheduling
// 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