freekmurze / php-guidelines-from-spatie
Install for your project team
Run this command in your project directory to install the skill for your entire team:
mkdir -p .claude/skills/php-guidelines-from-spatie && curl -L -o skill.zip "https://fastmcp.me/Skills/Download/1610" && unzip -o skill.zip -d .claude/skills/php-guidelines-from-spatie && rm skill.zip
Project Skills
This skill will be saved in .claude/skills/php-guidelines-from-spatie/ and checked into git. All team members will have access to it automatically.
Important: Please verify the skill by reviewing its instructions before using it.
Describes PHP and Laravel guidelines provided by Spatie. These rules result in more maintainable, and readable code.
0 views
0 installs
Skill Content
---
name: php-guidelines-from-spatie
description: Describes PHP and Laravel guidelines provided by Spatie. These rules result in more maintainable, and readable code.
license: MIT
metadata:
author: Spatie
tags: php, laravel, best practices, coding standards
---
## 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
## Class Structure
- Use typed properties, not docblocks:
- 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:
```php
/** @return Collection<int, User> */
public function getUsers(): Collection
```
### Docblock Rules
- Don't use docblocks for fully type-hinted methods (unless description needed)
- **Always import classnames in docblocks** - never use fully qualified names:
```php
use \Spatie\Url\Url;
/** @return Url */
```
- Use one-line docblocks when possible: `/** @var string */`
- Most common type should be first in multi-type docblocks:
```php
/** @var Collection|SomeWeirdVendor\Collection */
```
- If one parameter needs docblock, add docblocks for all parameters
- For iterables, always specify key and value types:
```php
/**
* @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:
```php
/** @return array{
first: SomeClass,
second: SomeClass
} */
```
## Control Flow
- **Happy path last**: Handle error conditions first, success case last
- **Avoid else**: Use early returns instead of nested conditions
- **Separate conditions**: Split compound `if` statements that use `&&` into nested `if` statements for better readability
- **Always use curly brackets** even for single statements
- **Ternary operators**: Each part on own line unless very short
```php
// 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();
// Bad: compound condition with &&
if ($user->isActive() && $user->hasPermission('edit')) {
$user->edit();
}
// Good: nested ifs
if ($user->isActive()) {
if ($user->hasPermission('edit')) {
$user->edit();
}
}
```
## Laravel Conventions
### Routes
- URLs: kebab-case (`/open-source`)
- Route names: camelCase (`->name('openSource')`)
- Parameters: camelCase (`{userId}`)
- Use tuple notation: `[Controller::class, 'method']`
### Controllers
- Plural resource names (`PostsController`)
- Stick to CRUD methods (`index`, `create`, `store`, `show`, `edit`, `update`, `destroy`)
- 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`, don't create new files
- Use `config()` helper, avoid `env()` outside config files
### Artisan Commands
- 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):
```php
$items->each(function(Item $item) {
$this->info("Processing item id `{$item->id}`...");
$this->processItem($item);
});
$this->comment("Processed {$items->count()} items.");
```
## Strings & Formatting
- **String interpolation** over concatenation:
## Enums
- Use PascalCase for enum values:
## Comments
Be very critical about adding comments as they often become outdated and can mislead over time. Code should be self-documenting through descriptive variable and function names.
Adding comments should never be the first tactic to make code readable.
*Instead of this:*
```php
// Get the failed checks for this site
$checks = $site->checks()->where('status', 'failed')->get();
```
*Do this:*
```php
$failedChecks = $site->checks()->where('status', 'failed')->get();
```
**Guidelines:**
- Don't add comments that describe what the code does - make the code describe itself
- Short, readable code doesn't need comments explaining it
- Use descriptive variable names instead of generic names + comments
- Only add comments when explaining *why* something non-obvious is done, not *what* is being done
- Never add comments to tests - test names should be descriptive enough
## Whitespace
- 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
## Validation
- Use array notation for multiple rules (easier for custom rule classes):
```php
public function rules() {
return [
'email' => ['required', 'email'],
];
}
```
- Custom validation rules use snake_case:
```php
Validator::extend('organisation_type', function ($attribute, $value) {
return OrganisationType::isValid($value);
});
```
## Blade Templates
- Indent with 4 spaces
- No spaces after control structures:
```blade
@if($condition)
Something
@endif
```
## Authorization
- Policies use camelCase: `Gate::define('editPost', ...)`
- Use CRUD words, but `view` instead of `show`
## Translations
- Use `__()` function over `@lang`:
## API Routing
- Use plural resource names: `/errors`
- Use kebab-case: `/error-occurrences`
- Limit deep nesting for simplicity:
```
/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
## Quick Reference
### 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` (`PostsController`)
- Views: camelCase (`openSource.blade.php`)
- Jobs: action-based (`CreateUser`, `SendEmailNotification`)
- Events: tense-based (`UserRegistering`, `UserRegistered`)
- Listeners: action + `Listener` suffix (`SendInvitationMailListener`)
- Commands: action + `Command` suffix (`PublishScheduledPostsCommand`)
- Mailables: purpose + `Mail` suffix (`AccountActivatedMail`)
- Resources/Transformers: plural + `Resource`/`Transformer` (`UsersResource`)
- Enums: descriptive name, no prefix (`OrderStatus`, `BookingType`)
### Migrations
- do not write down methods in migrations, only up methods
### Code Quality Reminders
#### PHP
- Use typed properties over docblocks
- Prefer early returns over nested if/else
- Use constructor property promotion when all properties can be promoted
- Avoid `else` statements when possible
- Split compound `if` conditions using `&&` into nested `if` statements
- Use string interpolation over concatenation
- Always use curly braces for control structures
- Always import namespaces with `use` statements — never use inline fully qualified class names (e.g. `\Exception`, `\Illuminate\Support\Facades\Http`)
- Never use single-letter variable names — use descriptive names (e.g. `$exception` not `$e`, `$request` not `$r`)