Laravel & PHP Guidelines for AI Code Assistants
This file contains Laravel and PHP coding standards optimized for AI code assistants like Claude Code, GitHub Copilot, and Cursor. These guidelines are derived from Spatie’s comprehensive Laravel & PHP standards, combined with personalized practices.
Core Laravel Principle
Follow Laravel conventions first. If Laravel has a documented way to do something, use it. Only deviate when you have a clear justification.
PHP Standards
- Follow PSR-1, PSR-2, and PSR-12
- Use camelCase for non-public-facing strings
- Use short nullable notation: ?string not string|null
- Always specify void return types when methods return nothing
- Add declare(strict_types=1); to the top of all new PHP files
Class Structure
- Use typed properties, not docblocks
- Use constructor property promotion when all properties can be promoted
- One trait per line
Type Declarations & Docblocks
- Use typed properties over docblocks
- Specify return types including void
- Use short nullable syntax: ?Type not Type|null
- Document iterables with generics:
/** @return Collection<int, User> */
public function getUsers(): CollectionDocblock Rules
- Don’t use docblocks for fully type-hinted methods (unless description needed)
- Always import class names in docblocks — never use fully qualified names:
use Spatie\Url\Url;
/** @return Url */- Use one-line docblocks when possible: /** @var string */
- Most common type should be first in multi-type docblocks
- If one parameter needs docblock, add for all
- For iterables, specify key and value types
- Use array shape notation for fixed keys, each key on its own line:
/** @return array{
first: SomeClass,
second: SomeClass
} */Control Flow
- Happy path last: handle error conditions first
- Avoid else: prefer early returns
- Separate conditions: prefer multiple if statements
- Always use curly brackets even for single-line statements
- Ternary operators: put each part on its own line unless very short
if (! $user) {
return null;
}
if (! $user->isActive()) {
return null;
}
$name = $isFoo ? 'foo' : 'bar';
$result = $object instanceof Model
? $object->name
: 'A default value';Laravel Conventions
Routes
- URLs: kebab-case (/open-source)
- Route names: camelCase (->name('openSource'))
- Parameters: camelCase ({userId})
- Use tuple notation: [Controller::class, 'method']
- Use resource words (index, show, create, store, edit, update, destroy)
- Always create a custom FormRequest to validate route inputs
- Use FormRequests for authorization logic beyond basic auth, but NOT for authentication itself
- Never define middleware inline via $this->middleware(); use route files or service providers
- Never use closure-based middleware, always use named classes
Controllers
- Use plural resource names (PostsController)
- Stick to CRUD methods
- Extract new controllers for non-CRUD actions
Configuration
- Files: kebab-case (pdf-generator.php)
- Keys: snake_case (chrome_path)
- Add service configs to config/services.php, not new files
- Use config() helper; avoid env() outside config files
Artisan Commands
- Names: kebab-case (delete-old-records)
- Always provide feedback ($this->comment('Done!'))
- Show progress for loops, summary at end
- Output BEFORE processing for easier debugging:
$items->each(function(Item $item) {
$this->info("Processing item id `{$item->id}`...");
$this->processItem($item);
});
$this->comment("Processed {$items->count()} items.");Strings & Formatting
- Use string interpolation over concatenation
Enums
- Use PascalCase for enum values
- Never use enum fields in migrations — use string/varchar and cast in model
- Always use PHP Enums for reused values
Comments
- Avoid comments — write expressive code
- When needed, use proper formatting:
// Single line with space after //
/*
* Multi-line block
*/- Refactor comments into descriptive function names
Whitespace
- Add blank lines between statements for readability
- No extra empty lines between {}
- Let code “breathe” — avoid cramped formatting
Validation
- Use array notation for multiple rules:
public function rules(): array {
return [
'email' => ['required', 'email'],
];
}- Custom validation rules: snake_case
- Always use strict validation rules in FormRequest
Blade Templates
- Indent with 4 spaces
- No spaces after control structures:
@if($condition)
Something
@endifAuthorization
- Policies use camelCase:
Gate::define('editPost', ...) - Use CRUD words; prefer view instead of show
Translations
- Use __() function over @lang
API Routing
- Use plural resource names: /errors
- Use kebab-case: /error-occurrences
- Limit deep nesting:
/error-occurrences/1
/errors/1/occurrences
Testing • Keep test classes in same file when possible • Use descriptive test method names • Follow the arrange-act-assert pattern
File & Naming Conventions
- Classes: PascalCase (UserController, OrderStatus)
- Methods/Variables: camelCase (getUserName, $firstName)
- Routes: kebab-case (/open-source, /user-profile)
- Config files: kebab-case (pdf-generator.php)
- Config keys: snake_case (chrome_path)
- Artisan commands: kebab-case (php artisan delete-old-records)
File Structure
- Controllers: plural resource name + Controller
- Views: camelCase
- Jobs: action-based (CreateUser)
- Events: tense-based (UserRegistering)
- Listeners: action + Listener
- Commands: action + Command
- Mailables: purpose + Mail
- Resources: plural + Resource
- Enums: descriptive name, no prefix
Migrations
- Only define up() method
- Never define enum columns; use string and cast to enum
- Sync with database/database.dbml
Entity Workflow & Architecture
- Use sail artisan make:model MyModel -m -f to generate new models
- Immediately follow with sail artisan make:repository MyModelRepository --model=MyModel
- Use the repository for all interactions with entities, not models directly
- Use Spatie\LaravelData\Data when passing data between layers for type safety
- Encrypt and hide any sensitive fields in models
- Use casts for JSON fields to Data objects
- Add protected
$guarded = ['id'];to all models
Repository/Service Architecture
- Models:
- Only manipulate their own data
- Cannot use services, repositories, or other models
- Repositories:
- Can use models, but not services or other repositories
- Services:
- Can use repositories only
- Controllers:
- Can use both services and repositories
- Repositories should use
$this->modelQuery()not$this->query()
HTTP Clients
- Use Laravel’s Http Client
- For multiple requests to the same API, register a custom macro in the appropriate service provider
- Use the Http Client’s mocking features in tests