# Olakunlevpn Laravel Skills

> Use when writing, reviewing, or refactoring any Laravel code — models, controllers, services, actions, traits, tests, config, exceptions, service providers, or package architecture. Always apply these standards to all Laravel work.

- Skill: `olakunlevpn/olakunlevpn-laravel-skills` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add olakunlevpn/olakunlevpn-laravel-skills`
- Raw SKILL.md: https://api.skillmd.com/api/skills/olakunlevpn/olakunlevpn-laravel-skills/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: olakunlevpn (https://skillmd.com/u/olakunlevpn)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/olakunlevpn/olakunlevpn-laravel-skills

---


# Laravel Coding Standards

Comprehensive coding standards for Laravel applications. Follow them exactly. For detailed code examples, see REFERENCE.md.

## When to Apply

Apply these standards to ALL Laravel work:
- Writing new classes, models, controllers, services, actions
- Creating or modifying Eloquent models, traits, relationships
- Writing tests (Pest PHP)
- Creating config files, service providers, migrations
- Writing exception classes, building package architecture
- Code review and refactoring

---

## 1. PHP Style Rules

- `declare(strict_types=1)` at top of every PHP file
- Typed properties, parameter types, and return types everywhere
- Docblocks only when PHP type system cannot express it
- `: void` return type on methods returning nothing
- String interpolation over concatenation: `"Hello {$name}"` not `'Hello ' . $name`
- Happy path last — return early for error/guard cases
- No `else` blocks — use early returns instead
- `match()` over `switch` statements
- PHP 8 constructor property promotion with `readonly`
- PHP 8.1 backed enums over class constants
- PHP 8 attributes for metadata decoration
- Named arguments for clarity with many parameters
- Always `static::class`, `static::query()`, `new static()` — never `self::` for subclassable code
- `$guarded = []` not `$fillable` — guard only primary keys in constructor
- Minimal comments — code is self-documenting. Comments explain WHY, never WHAT
- Blank lines between logical statement groups
- Zero logger calls unless absolutely necessary
- No `final` on classes

---

## 2. Architecture

### Interface + Config + Factory Triangle
Every swappable component has:
1. **Interface** in `Contracts/`
2. **Default implementation** class
3. **Config key** pointing to the class
4. **Factory** that reads config, validates, resolves via container

### Trait Composition (Concerns)
- Traits in `Concerns/` are the primary user API
- Single responsibility per trait
- Compose traits together — one uses another internally
- Users add one trait and get everything
- `boot{TraitName}()` static method for Eloquent lifecycle hooks

### Config-Driven Everything
- Model classes, table names, column names — all configurable
- Feature flags for optional features (disabled by default)
- kebab-case filenames, snake_case keys
- Never `env()` outside config files

### Container Bindings
- `scoped()` — stateful per-request singletons (mappers, resolvers)
- `singleton()` — global registries and caches
- `bind()` — stateless factories and generators

### Domain-Based Directory Structure
```
src/
  Actions/       Concerns/      Contracts/     Enums/
  Events/        Exceptions/    Factories/     Jobs/
  Middleware/    Models/Concerns/  Support/    Transformers/
```
Events/exceptions/jobs can live inside feature domain namespace.

---

## 3. Naming Conventions

**Classes:** Models=Singular PascalCase, Controllers=Plural+Controller (CRUD methods only), Exceptions=Descriptive condition, Actions=Verb, Resolvers=Xxx+Resolver, Jobs=Action-based, Commands=Action+Command, Mailables=Event+Mail

**Events:** Before=Present participle (`SavingSettings`), After=Past tense (`SettingsSaved`)

**Methods:**
- Finders: `findByName()` / `findById()` / `findOrCreate()`
- Assignment: verb-first (`assignRole()`, `syncRoles()`)
- Checkers: has-prefix (`hasRole()`, `hasAnyRole()`, `hasAllRoles()`)
- Scopes: positive + negative pairs (`scopeRole()` / `scopeWithoutRole()`)
- Getters: get-prefix (`getRoleNames()`)
- Cache: forget-prefix (`forgetCachedPermissions()`)
- Actions/Resolvers: single `execute()` method

**Properties:** Boolean = `is`/`has` prefix. Config = always `config('key')`.

**Validation:** Always array notation `['required', 'string']`, never pipe-separated.

**Routes:** HTTP verb first, camelCase names, kebab-case URLs.

---

## 4. Design Patterns

### Static Factory Constructors on Exceptions
- Never `new Exception('msg')` — always `ExceptionClass::create($param)`
- Each error type = own class in `Exceptions/`
- Named constructors return `static` not `self`
- Messages use `__()` for i18n
- Config flags hide sensitive data from errors

### Input Polymorphism
Accept `string|int|array|Model|Collection|BackedEnum`, normalize internally with `match(true)`.

### Fluent Builder
- Every setter returns `static`
- Terminal method performs the operation
- Deferred execution: if model doesn't exist, defer to `Model::created` callback
- `Macroable` trait for runtime extensibility

### Pipeline Pattern
Complex creation/transformation = ordered pluggable stages. Each pipe implements `handle()`.

### Observer for Side Effects
File I/O, cache invalidation, cleanup — Eloquent Observers, NOT inside models.

### Action Classes
Single-purpose in `Actions/` with `execute()` method. Config-swappable.

### Contract + Concern Pairs
Every capability = parallel Interface + Trait. Compose only what you need.

---

## 5. Model Patterns

- `$guarded = []` with primary key guarded in constructor
- Table names from config: `$this->table = config('pkg.table_names.x') ?: parent::getTable()`
- Defaults in constructor with `??=`
- Custom Eloquent Collection via `newCollection()`
- Computed attributes via `Attribute::get()`
- Related model classes from config
- Traits in `Models/Concerns/`
- `@property` PHPDoc on model interfaces
- `static::query()->create()` (not `static::create()`) to avoid recursion
- Finder trio: `findByName()`, `findById()`, `findOrCreate()`
- Scopes in positive/negative pairs

---

## 6. Config, Service Provider, Events, Migrations

**Config:** Block comments with full sentences. Sensible defaults. Performance features disabled by default. Models/tables/columns configurable. Feature flags as booleans.

**Service Provider:** Declarative API via `PackageServiceProvider`: `$package->name()->hasConfigFile()->hasMigrations()->hasCommands()`. Bind contracts to config-driven classes.

**Events:** Simple data classes with `SerializesModels` + constructor promotion. Past tense for completed, present participle for pre-action. Opt-in via config flag. Place inside feature domain.

**Migrations:** Anonymous class migrations. Table names from config. `.php.stub` for publishable package migrations.

---

## 7. Testing Standards (Pest PHP)

- Pest PHP exclusively — no PHPUnit class-based tests
- `uses(TestCase::class)->in(__DIR__)` in Pest.php
- `it('can do something')` lowercase natural English. `test()` for edge cases.
- `expect()` fluent API with chaining, `->not->` for negation, `->toThrow()` for exceptions
- In-memory SQLite, no `RefreshDatabase`, manual schema in `setUp()`
- Orchestra Testbench as base with `getPackageProviders()`, `getEnvironmentSetUp()`
- `beforeEach()` for per-test setup
- `DB::enableQueryLog()` for performance assertions
- Domain-based test directories: `Models/`, `Traits/`, `Commands/`

---

## 8. Dependencies & Version Control

- Minimal dependencies — only what is strictly necessary
- No unnecessary external packages
- PHP 8.2+, current Laravel LTS + latest
- kebab-case repo and branch names
- Present tense, descriptive, granular commits
- Main branch always stable, feature branches squash on merge

---

## Summary Checklist

Before submitting any Laravel code:
- [ ] `declare(strict_types=1)` at top
- [ ] All methods have typed parameters and return types
- [ ] No `else` blocks — early returns used
- [ ] `static` not `self` for late static binding
- [ ] `$guarded = []` not `$fillable`
- [ ] Exceptions use static factory constructors
- [ ] Config-driven model/table references
- [ ] Traits compose each other, users add one
- [ ] Scopes in positive/negative pairs
- [ ] Events are opt-in, past-tense naming
- [ ] Tests use Pest `expect()` API
- [ ] Zero unnecessary logging
- [ ] Minimal comments, self-documenting code

