If you're using Claude or another AI assistant for PHP development without a CLAUDE.md, you've seen this pattern: the AI generates working code, but it's PHP 5-era style — no type hints, mysql_* functions, procedural globals, or die() error handling.
PHP has evolved dramatically. PHP 8.x has union types, enums, fibers, named arguments, match expressions, and readonly properties. Modern PHP is a different language from what most AI training data contains. Without explicit rules, AI defaults to the lowest common denominator.
A CLAUDE.md file at your repo root tells the AI which decade you're working in. Here are the 13 rules that have the highest impact.
Rule 1: Enforce strict types on every file
Every PHP file must start with `declare(strict_types=1);` immediately after the opening tag.
No exceptions — including scripts, controllers, helpers, and test files.
Without strict_types, PHP silently coerces values. A function expecting int accepts "42" without warning. With strict types enabled, type errors throw TypeError at call time, not at some downstream point where the symptom is unrelated to the cause.
AI tends to omit this declaration unless you make it explicit.
Rule 2: PHP 8.x minimum — use modern syntax
Target PHP 8.2+. Use:
- Named arguments for clarity over positional guessing
- Match expressions instead of switch/case with break
- Nullsafe operator (?->) instead of nested null checks
- Readonly properties for immutable data
- Enums instead of class constants for finite value sets
- First-class callable syntax where it improves readability
AI often generates switch where match is cleaner, nested isset() chains where ?-> works, and class constant arrays where typed enums are correct. Specify the PHP version and the syntax you expect.
Rule 3: Type hints everywhere — no mixed, no omissions
All function parameters, return types, and class properties must have type declarations.
- Use union types (int|string) rather than omitting types
- Use intersection types when a parameter must satisfy multiple interfaces
- Use never return type for functions that always throw or exit
- Avoid `mixed` except at true boundaries (serialization input, external API payloads)
- Use `void` for methods with no meaningful return value
PHP 8 supports rich type systems. A function signature like function process($data) tells the AI nothing about intent. A signature like function process(array $data): ProcessedResult locks in both the contract and the shape.
Rule 4: No legacy database patterns
Database access: PDO with prepared statements only.
- No `mysql_*` functions (removed in PHP 7)
- No `mysqli_*` procedural API
- No raw string interpolation in SQL queries: `"SELECT * FROM users WHERE id = $id"` is forbidden
- Always use `?` placeholders or named `:param` placeholders
- Wrap PDO in a repository class — no direct PDO calls from controllers or views
SQL injection is still the most common PHP vulnerability. AI will generate raw interpolated queries if you don't prohibit them explicitly. The PDO pattern should be the only pattern in your codebase.
Rule 5: Error handling — exceptions, not die()
Error handling:
- No `or die()`, no `exit()` for flow control
- No `@` (error suppression operator) — fix the root cause instead
- Throw domain-specific exceptions that extend `\RuntimeException` or `\LogicException`
- Use `set_exception_handler()` at the application boundary, not try/catch everywhere
- Log exceptions with context (request ID, user ID, stack trace) — not bare `error_log()`
Legacy PHP code is littered with or die("connection failed"). AI perpetuates this because it's common in training data. Exceptions with structured logging are the modern pattern.
Rule 6: Dependency injection — no static state
Object construction:
- No static methods for business logic (static is acceptable for pure utility functions)
- No service locator pattern (`App::make()`, `Container::get()` inside domain classes)
- Constructor injection for required dependencies
- No `new ClassName()` inside methods — receive dependencies via constructor
- No global state: no `$_GLOBALS`, no static class properties that mutate
Static methods and service locators make code untestable and create hidden coupling. AI gravitates toward them because they're "easy." Constructor injection is the pattern that survives growth.
Rule 7: Namespaces and PSR-4 autoloading
Namespace conventions:
- All classes must be in a namespace matching the directory structure
- Follow PSR-4: `App\Http\Controllers\UserController` maps to `src/Http/Controllers/UserController.php`
- No procedural files at the root level except `index.php` (the bootstrap)
- One class per file, always
- Use Composer autoloading — no manual `require_once` chains
AI sometimes generates self-contained procedural scripts. Modern PHP is object-oriented with Composer. Make the file structure convention explicit.
Rule 8: Security defaults — output escaping and CSRF
Security rules (non-negotiable):
- All output in templates must be escaped: `htmlspecialchars($var, ENT_QUOTES, 'UTF-8')` or the framework equivalent
- CSRF tokens required on all state-mutating forms (POST, PUT, DELETE, PATCH)
- No `eval()`, no `shell_exec()`, no `system()` unless explicitly required (and reviewed)
- Passwords: `password_hash($pass, PASSWORD_ARGON2ID)` + `password_verify()` — never MD5/SHA1
- Sessions: `session_regenerate_id(true)` after login
- File uploads: validate MIME type server-side, never trust the client-provided Content-Type
Security defaults must be stated explicitly. AI generates working code first, secure code only if you specify.
Rule 9: Array functions over loops
Prefer functional array operations over manual loops:
- `array_map()`, `array_filter()`, `array_reduce()` for collection transforms
- `array_column()` for plucking a field from a list of associative arrays
- Spread operator `[...$a, ...$b]` for merging instead of `array_merge()` in expressions
- Do not use `for ($i = 0; ...)` when `foreach` reads better
- Keep closures short — extract named functions if the closure exceeds 5 lines
This is a readability rule. Dense for loops with index arithmetic are harder to review than array_filter($users, fn($u) => $u->active). AI can write either; specify which you want.
Rule 10: Immutable value objects for domain data
Domain data:
- Use readonly classes (PHP 8.2) or readonly properties for value objects
- Value objects compare by value, not by reference — implement `equals()` if comparison is needed
- DTOs (Data Transfer Objects) must be readonly — no setters
- Money values: integer cents, never floats
- Dates: `\DateTimeImmutable` only — never mutable `\DateTime`
Mutable state is a common source of bugs. PHP 8.2 readonly classes make immutability first-class. AI defaults to mutable data unless you establish the rule.
Rule 11: Testing — PHPUnit with real assertions
Testing conventions:
- PHPUnit for unit and integration tests
- Test file: `tests/Unit/UserServiceTest.php` maps to `src/Service/UserService.php`
- No `assertTrue(true)` — every test must assert observable behavior
- Avoid mocking internal implementation — mock only external boundaries (database, HTTP, filesystem)
- Data providers for edge cases, not copy-pasted test methods
- Test names must describe behavior: `test_throws_when_email_is_invalid()` not `test1()`
AI writes tests that pass but don't catch regressions. A test that mocks everything and asserts assertTrue(true) is worse than no test — it creates false confidence.
Rule 12: Composer and package hygiene
Dependency management:
- All dependencies via Composer — no manual vendor directory manipulation
- Lock the exact PHP version in `composer.json` under `require`: `"php": "^8.2"`
- Separate `require` (production) from `require-dev` (testing, analysis tools)
- Run `composer audit` before any dependency update — check for known vulnerabilities
- No packages abandoned on Packagist without a forked/maintained alternative
AI sometimes suggests packages that are years out of maintenance. Specifying composer audit as a required step catches this automatically.
Rule 13: Logging with context — not bare strings
Logging:
- Use PSR-3 logger (Monolog or framework logger) — no bare `error_log()`
- Every log call must include context: `$logger->error('Payment failed', ['user_id' => $userId, 'amount' => $amount])`
- Log level discipline: debug (development only), info (normal operations), warning (degraded), error (failure requiring attention), critical (system down)
- No sensitive data in logs: mask card numbers, passwords, tokens before logging
- Structured logs preferred (JSON) for log aggregation
error_log("something went wrong") is useless in production. Context-rich structured logs are the difference between a 5-minute debug session and a 2-hour war room.
What goes in your CLAUDE.md
Drop this into a CLAUDE.md at your PHP project root:
# PHP Project — AI Coding Rules
## Language Version
PHP 8.2+. All files start with `declare(strict_types=1);`.
## Types
Full type declarations on all functions and properties. No `mixed` except at serialization boundaries.
## Database
PDO + prepared statements only. No raw SQL interpolation. Repository pattern.
## Security
- Output: htmlspecialchars on all user data
- Passwords: password_hash(ARGON2ID) + password_verify
- Sessions: regenerate ID after login
- CSRF tokens on all mutating forms
## Error Handling
Throw domain exceptions. No or die(), no @ suppression, no exit() for control flow.
## Architecture
Constructor injection only. No static business logic. No service locator inside domain classes.
## Testing
PHPUnit. Mock only external boundaries. Behavioral test names.
## Logging
PSR-3 with context arrays. Structured JSON preferred. Never log raw sensitive data.
Why this works
CLAUDE.md isn't configuration for the AI's personality — it's the same thing as your team's coding standards document, except the AI reads it before every session instead of once at onboarding.
The rules above fix the specific failure modes that show up most often in AI-generated PHP: legacy syntax, security shortcuts, untestable architecture, and missing type information.
Once they're in place, AI output matches what you'd expect from a senior PHP developer on your team — not a developer who learned PHP in 2009 and never updated their patterns.
The full rules pack (all stacks, team license, configuration templates) is at gumroad — $27.
Top comments (0)