laravel-php
Idiomatic Laravel/PHP patterns — Eloquent, service layer, PHPUnit/Pest testing, Laravel Pint formatting, and build commands. Covers request validation, jobs/queues, events, and common anti-patterns. Use when working on Laravel PHP projects.
Laravel / PHP Patterns — dog_stack
Idiomatic Laravel and PHP conventions covering Eloquent, service layer, testing, and build toolchain.
Idiomatic Laravel
Service layer
// ✓ Thin controllers, fat service classes
class UserController extends Controller
{
public function __construct(private UserService $userService) {}
public function store(CreateUserRequest $request): JsonResponse
{
$user = $this->userService->create($request->validated());
return response()->json(new UserResource($user), 201);
}
}
class UserService
{
public function create(array $data): User
{
return DB::transaction(fn () => User::create($data));
}
}
Request validation
// ✓ Form Request classes for validation
class CreateUserRequest extends FormRequest
{
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', 'unique:users'],
];
}
}
Eloquent patterns
// ✓ Scopes for reusable query constraints
class User extends Model
{
public function scopeActive(Builder $query): Builder
{
return $query->where('active', true);
}
}
// Usage: User::active()->get()
// ✓ Eager loading to prevent N+1
$users = User::with(['posts', 'roles'])->active()->get();
Jobs and queues
// ✓ Queued jobs for heavy work
class SendWelcomeEmail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public readonly User $user) {}
public function handle(Mailer $mailer): void
{
$mailer->to($this->user)->send(new WelcomeMail($this->user));
}
}
// Dispatch: SendWelcomeEmail::dispatch($user);
Testing with Pest / PHPUnit
// Pest (recommended)
test('user can be created', function () {
$data = ['name' => 'Alice', 'email' => '[email protected]', 'password' => 'password'];
$response = $this->postJson('/api/users', $data);
$response->assertCreated()->assertJsonPath('data.email', '[email protected]');
$this->assertDatabaseHas('users', ['email' => '[email protected]']);
});
test('email must be unique', function () {
User::factory()->create(['email' => '[email protected]']);
$this->postJson('/api/users', ['email' => '[email protected]', 'name' => 'Alice', 'password' => 'pw'])
->assertUnprocessable()
->assertJsonValidationErrors(['email']);
});
Run tests:
php artisan test # all tests
php artisan test --filter=UserTest # specific test
vendor/bin/pest # pest directly
vendor/bin/pest --coverage # with coverage
Build Commands
php artisan serve # local dev server
php artisan migrate # run migrations
php artisan migrate:fresh --seed # reset + seed
php artisan queue:work # process queue
php artisan cache:clear # clear all caches
composer install # install dependencies
composer require <package> # add package
Laravel Pint
vendor/bin/pint # format all files
vendor/bin/pint --test # CI check (non-destructive)
vendor/bin/pint app/ # specific directory
Common Pitfalls
| Pitfall | Fix |
|---|---|
| N+1 queries | Use with() eager loading |
| Logic in controllers | Move to service classes |
| Raw SQL without binding | Use query builder with ? placeholders |
Missing authorized() in Form Requests | Implement authorize() method |
env() in config files only | Never call env() outside config/*.php |
Related
- Rules:
rules/php/coding-style.md - Agent: language reviewers (general-purpose for PHP)