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.
Follow Laravel conventions first. If Laravel has a documented way to do something, use it. Only deviate when you have a clear justification.
- Follow PSR-1, PSR-2, and PSR-12
- Use camelCase for non-public-facing strings
- Use short nullable notation:
?stringnotstring|null - Always specify
voidreturn types when methods return nothing
- Use typed properties, not docblocks:
- Constructor property promotion when all properties can be promoted:
- One trait per line:
- Use typed properties over docblocks
- Specify return types including
void - Use short nullable syntax:
?TypenotType|null - Document iterables with generics:
/** @return Collection<int, User> */ public function getUsers(): Collection
- Don't use docblocks for fully type-hinted methods (unless description needed)
- Always import classnames 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:
/** @var Collection|SomeWeirdVendor\Collection */ - If one parameter needs docblock, add docblocks for all parameters
- For iterables, always specify key and value types:
/** * @param array<int, MyObject> $myArray * @param int $typedArgument */ function someFunction(array $myArray, int $typedArgument) {}
- Use array shape notation for fixed keys, put each key on it's own line:
/** @return array{ first: SomeClass, second: SomeClass } */
- Happy path last: Handle error conditions first, success case last
- Avoid else: Use early returns instead of nested conditions
- Separate conditions: Prefer multiple if statements over compound conditions
- Always use curly brackets even for single statements
- Ternary operators: Each part on own line unless very short
// Happy path last
if (! $user) {
return null;
}
if (! $user->isActive()) {
return null;
}
// Process active user...
// Short ternary
$name = $isFoo ? 'foo' : 'bar';
// Multi-line ternary
$result = $object instanceof Model ?
$object->name :
'A default value';
// Ternary instead of else
$condition
? $this->doSomething()
: $this->doSomethingElse();- URLs: kebab-case (
/open-source) - Route names: camelCase (
->name('openSource')) - Parameters: camelCase (
{userId}) - Use tuple notation:
[Controller::class, 'method']
- Plural resource names (
PostsController) - Stick to CRUD methods (
index,create,store,show,edit,update,destroy) - Extract new controllers for non-CRUD actions
- Files: kebab-case (
pdf-generator.php) - Keys: snake_case (
chrome_path) - Add service configs to
config/services.php, don't create new files - Use
config()helper, avoidenv()outside config files
- Names: kebab-case (
delete-old-records) - Always provide feedback (
$this->comment('All ok!')) - Show progress for loops, summary at end
- Put output BEFORE processing item (easier debugging):
$items->each(function(Item $item) { $this->info("Processing item id `{$item->id}`..."); $this->processItem($item); }); $this->comment("Processed {$items->count()} items.");
- String interpolation over concatenation:
- Use PascalCase for enum values:
- Avoid comments - write expressive code instead
- When needed, use proper formatting:
// Single line with space after // /* * Multi-line blocks start with single * */
- Refactor comments into descriptive function names
- Add blank lines between statements for readability
- Exception: sequences of equivalent single-line operations
- No extra empty lines between
{}brackets - Let code "breathe" - avoid cramped formatting
- Use array notation for multiple rules (easier for custom rule classes):
public function rules() { return [ 'email' => ['required', 'email'], ]; }
- Custom validation rules use snake_case:
Validator::extend('organisation_type', function ($attribute, $value) { return OrganisationType::isValid($value); });
- Indent with 4 spaces
- No spaces after control structures:
@if($condition) Something @endif
- Policies use camelCase:
Gate::define('editPost', ...) - Use CRUD words, but
viewinstead ofshow
- Use
__()function over@lang:
- Use plural resource names:
/errors - Use kebab-case:
/error-occurrences - Limit deep nesting for simplicity:
/error-occurrences/1 /errors/1/occurrences
- Keep test classes in same file when possible
- Use descriptive test method names
- Follow the arrange-act-assert pattern
- 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)
- Controllers: plural resource name +
Controller(PostsController) - Views: camelCase (
openSource.blade.php) - Jobs: action-based (
CreateUser,SendEmailNotification) - Events: tense-based (
UserRegistering,UserRegistered) - Listeners: action +
Listenersuffix (SendInvitationMailListener) - Commands: action +
Commandsuffix (PublishScheduledPostsCommand) - Mailables: purpose +
Mailsuffix (AccountActivatedMail) - Resources/Transformers: plural +
Resource/Transformer(UsersResource) - Enums: descriptive name, no prefix (
OrderStatus,BookingType)
- do not write down methods in migrations, only up methods
- Use typed properties over docblocks
- Prefer early returns over nested if/else
- Use constructor property promotion when all properties can be promoted
- Avoid
elsestatements when possible - Use string interpolation over concatenation
- Always use curly braces for control structures
These guidelines are maintained by Spatie and optimized for AI code assistants.