Why PHP Needs Specific AI Coding Rules
PHP has undergone a dramatic transformation since PHP 7 — strict typing, enums, readonly properties and classes, fibers, match expressions, named arguments, and union/intersection types. Modern PHP is a genuinely excellent language. But AI assistants trained on two decades of PHP code default to patterns from the era when PHP had a very different reputation.
The most common AI failures in PHP: generating code without declare(strict_types=1), using arrays where typed DTOs or enums would be appropriate, ignoring Laravel's Eloquent conventions and writing raw SQL, creating God classes instead of using Laravel's service container, and mixing echo/print with proper response handling.
A few rules that declare your PHP version and framework preferences immediately transform AI output from legacy-style to modern, type-safe PHP that your team actually wants to maintain.
Rule 1: Modern PHP 8.3 Features
The rule: 'This project uses PHP 8.3. Every file starts with declare(strict_types=1). Use enums for fixed value sets (Status, Role, Priority). Use readonly classes for immutable DTOs. Use match expressions instead of switch for value mapping. Use named arguments for functions with 3+ parameters. Use union types (string|int) and intersection types (Countable&Iterator) where applicable.'
For typed properties: 'All class properties must have type declarations. Use constructor promotion for dependency injection: public function __construct(private readonly UserRepository $users). Never use untyped arrays as data structures — create typed DTOs or use Collections.'
The strict_types declaration is the single most impactful PHP rule. Without it, PHP silently coerces types, hiding bugs. With it, type mismatches throw TypeErrors immediately — exactly what you want in production code.
declare(strict_types=1) at the top of every file is the single most impactful PHP rule. Without it, PHP silently coerces types. With it, type mismatches throw TypeErrors immediately.
Rule 2: Laravel Conventions and Eloquent
Laravel's convention-over-configuration approach mirrors Rails — and the AI makes the same mistakes: ignoring conventions, bypassing the framework's patterns, and writing verbose code that the framework already handles.
The rule: 'Follow Laravel conventions strictly. Controllers in app/Http/Controllers/, models in app/Models/, services in app/Services/. Use Eloquent for all database operations — never raw DB::statement() unless performance requires it. Use resource controllers for CRUD. Use Form Requests for validation, not inline validation in controllers. Use Laravel's built-in auth scaffolding, never custom auth.'
For Eloquent specifically: 'Use scopes for reusable query conditions. Use eager loading (with()) to prevent N+1 queries. Use accessors/mutators for computed attributes. Use model events and observers for side effects. Use soft deletes where appropriate. Never call save() inside a loop — use bulk operations.'
- Eloquent for all DB operations — raw queries only for performance-critical paths
- Form Requests for validation — not inline $request->validate() in controllers
- Resource controllers for CRUD — single-purpose methods
- Eager loading with() — always, to prevent N+1 queries
- Scopes for reusable queries — scope:active, scope:recent
- Bulk operations over loops — insert/update/upsert collections, not individual saves
AI generates Eloquent loops that trigger N+1 queries. Always use with() for eager loading. Install Laravel Debugbar or Telescope in development to catch N+1 queries early.
Rule 3: Service Layer and Dependency Injection
AI assistants put business logic everywhere: controllers, models, middleware, even config files. Laravel's service container is powerful but the AI often ignores it, creating static methods or new Class() calls instead of using dependency injection.
The rule: 'Use constructor injection for all dependencies — never new Class() inside methods (except value objects). Register services in ServiceProviders. Use interfaces for services that might have multiple implementations. Controllers receive injected services and delegate business logic — never more than 10 lines per controller method.'
For the service pattern: 'Services in app/Services/ handle business logic. Actions in app/Actions/ handle single-purpose operations. Jobs in app/Jobs/ handle background processing. Each class has a single responsibility. Name services as nouns (UserService), actions as verbs (CreateUser), jobs as commands (ProcessPayment).'
Rule 4: Testing with Pest or PHPUnit
PHP testing has evolved significantly — Pest provides a modern, expressive syntax while PHPUnit remains the standard. AI assistants need to know which one your project uses and how to structure tests accordingly.
The rule: 'Use Pest for testing (or PHPUnit if the project predates Pest adoption). Use feature tests for HTTP endpoints (test the full request/response cycle). Use unit tests for isolated service/action logic. Use database transactions (RefreshDatabase trait) — never leave test data between tests. Use factories for test data, never manual Model::create() in tests.'
For Pest specifically: 'Use it() for test cases, describe() for grouping. Use expect() for assertions. Use beforeEach() for setup. Use dataset() for data-driven tests. Mock external services with Http::fake() and Queue::fake().'
Rule 5: PSR Standards and Code Quality
PHP's PSR standards (PHP Standard Recommendations) provide community-agreed conventions for coding style, autoloading, HTTP handling, and logging. AI assistants sometimes violate these standards because their training data includes pre-PSR code.
The rule: 'Follow PSR-12 coding style (extended from PSR-1 and PSR-2). Use PSR-4 autoloading via Composer. Use PSR-7 HTTP message interfaces when building framework-agnostic packages. Use PSR-3 logging interface (Laravel's Log facade complies). Run PHP-CS-Fixer or Pint with the project's configuration — code must pass before commit.'
For static analysis: 'Use PHPStan or Larastan at level 8 minimum. All public methods must have return type declarations. All function parameters must be typed. Use @phpstan-type aliases for complex array shapes that can't be expressed with PHP types alone.'
PHPStan at level 8 catches type errors that PHP's runtime misses. Pair it with strict_types and typed properties for a type safety level that rivals TypeScript.
Complete PHP/Laravel Rules Template
Consolidated template for PHP 8.3 teams using Laravel. Adapt framework rules if you use Symfony or another framework.
- PHP 8.3: strict_types=1 in every file, enums, readonly classes, match expressions
- All properties and parameters typed — constructor promotion for DI
- Laravel conventions: correct directories, resource controllers, Form Requests
- Eloquent: scopes, eager loading, no N+1, bulk operations over loops
- Constructor injection only — never new Class() except value objects
- Services for business logic — controllers under 10 lines per method
- Pest testing: feature tests for HTTP, unit tests for logic, factories for data
- PHPStan level 8 + Pint formatter — code must pass before commit