NYC
skills/smithery/ai/php-guidelines-from-spatie

php-guidelines-from-spatie

SKILL.md

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:
    /** @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:
    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
    } */
    

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
// 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):
    $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:

// Get the failed checks for this site
$checks = $site->checks()->where('status', 'failed')->get();

Do this:

$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):
    public function rules() {
        return [
            'email' => ['required', 'email'],
        ];
    }
    
  • Custom validation rules use snake_case:
    Validator::extend('organisation_type', function ($attribute, $value) {
        return OrganisationType::isValid($value);
    });
    

Blade Templates

  • Indent with 4 spaces
  • No spaces after control structures:
    @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)
Weekly Installs
3
Repository
smithery/ai
First Seen
8 days ago
Installed on
claude-code2
kilo1
amp1
opencode1
kimi-cli1
codex1