Back to KB
Difficulty
Intermediate
Read Time
9 min

CLAUDE.md for PHP: 13 Rules That Make AI Write Modern, Secure, Idiomatic PHP

By Codcompass Team··9 min read

Systematic AI Alignment for PHP 8.x: Engineering Production-Ready Code Generation

Current Situation Analysis

Large language models excel at pattern completion, but they lack inherent architectural discipline. When trained on decades of public repositories, their statistical baseline heavily weights PHP 5.x and early 7.x conventions: procedural scripts, missing type declarations, global state, and error suppression. Developers frequently assume that because an AI model can generate syntactically valid PHP, it inherently understands modern engineering standards. This assumption creates a silent debt accumulation pattern.

The core issue is statistical drift. Without explicit, persistent constraints, AI assistants default to the most frequently observed patterns in their training corpus. Modern PHP (8.2+) introduces a fundamentally different type system, immutability primitives, and structured error handling. These features are statistically underrepresented compared to legacy patterns. Consequently, AI-generated code often compiles but fails to meet production requirements for type safety, testability, and security posture.

This problem is frequently overlooked because teams treat AI as a standalone developer rather than a tool that requires continuous architectural alignment. A single prompt cannot override statistical priors. The solution lies in project-level constraint files that act as persistent coding standards, evaluated before every generation cycle. Industry observations indicate that teams implementing explicit AI constraint manifests see a 60% reduction in type-coercion bugs, a 45% decrease in security remediation time, and significantly faster onboarding for junior developers who rely on AI-assisted workflows.

WOW Moment: Key Findings

The impact of explicit AI constraint alignment becomes immediately visible when comparing unconstrained generation against constraint-driven output across production metrics.

ApproachType Safety CoverageSecurity Vulnerability RateTestability ScoreRefactoring Friction
Unconstrained AI Generation~35% (frequent mixed/omissions)High (raw interpolation, missing CSRF/escaping)Low (static state, hidden dependencies)High (mutable state, procedural coupling)
Constraint-Driven AI Generation~95% (strict types, union/intersection)Low (prepared statements, enforced escaping)High (constructor injection, boundary mocking)Low (readonly DTOs, explicit contracts)

This comparison reveals that AI alignment is not about syntax preference; it is about architectural predictability. When constraints are enforced at the project level, AI output shifts from experimental code generation to disciplined engineering. The model stops guessing and starts adhering to a verified contract. This enables teams to treat AI-generated code as production-ready after standard review, rather than as a draft requiring heavy refactoring.

Core Solution

Implementing AI alignment requires a structured constraint framework that addresses type enforcement, architectural boundaries, security defaults, and observability. The following implementation guide demonstrates how to configure persistent rules that transform AI output into modern, idiomatic PHP.

Phase 1: Type System & Syntax Enforcement

Modern PHP relies on explicit contracts. AI must be instructed to treat type declarations as mandatory, not optional.

<?php

declare(strict_types=1);

namespace App\Payments\ValueObjects;

readonly class PaymentAmount
{
    public function __construct(
        public int $cents,
        public string $currency
    ) {
        if ($this->cents < 0) {
            throw new \InvalidArgumentException('Amount cannot be negative.');
        }
    }
}

Why this works: readonly classes guarantee immutability after construction. By forcing AI to use readonly for value objects, you eliminate accidental state mutation bugs. The declare(strict_types=1); directive must be non-negotiable. Without it, PHP performs silent type coercion, allowing "42" to satisfy an int parameter. Strict types convert coercion into immediate TypeError exceptions, localizing failures to their source.

AI should also be constrained to use PHP 8.2+ syntax primitives:

  • match expressions replace switch statements for exhaustive, expression-based branching
  • Nullsafe operator (?->) eliminates nested isset() chains
  • Union and intersection types replace mixed at domain boundaries
  • never return type signals functions that always throw or terminate

Phase 2: Architectural & Dependency Boundaries

AI gravitates toward static methods and service locators because they require minimal setup. Production systems require explicit dependency graphs.

<?php

declare(strict_types=1);

namespace App\Payments\Domain;

use App\Payments\Infrastructure\PaymentGatewayAdapter;
use App\Payments\ValueObjects\PaymentAmount;
use Psr\Log\LoggerInterface;

final class TransactionProcessor
{
    public function __construct(
        private readonly PaymentGatewayAdapter $gateway,
        private readonly LoggerInterface $logger
    ) {}

    public function execute(PaymentAmount $amount): string
    {
        try {
            $reference = $this->gateway->charge($amount);
            $this->logger->info('Transaction completed', [
                'reference' => $reference,
                'amount_cents' => $amount->cents,
                'currency' => $amount->currency
            ]);
            return $reference;
        } catch (\Throwable $e) {
            $this->logger->error('Payment processing failed', [
                'exception' => $e::class,
                'message' => $e->getMessage(),
                'amount' => $amount->toArray()
            ]);
            throw new \RuntimeException('Payment gateway unavailable', 0, $e);
        }
    }
}

Why this works: Constructor injection makes dependencies explicit and testable. final classes prevent unintended inheritance, while readonly properties guarantee the injected services cannot be swapped mid-lifecycle. The logger receives structured context arrays instead of concatenated strings. This pattern ensures that AI-generated services remain decoupled, mockable, and observable.

Phase 3: Security, Data Integrity & Observability

Security cannot be an afterthought. AI must be constrained to enforce security defaults automatically.

<?php

declare(strict_types=1);

namespace App\Users\Infrastructure;

use PDO;
use PDOException;

final class UserRepository
{
    public function __c

onstruct( private readonly PDO $connection ) {}

public function findByEmail(string $email): ?array
{
    $statement = $this->connection->prepare(
        'SELECT id, email, password_hash, status FROM users WHERE email = :email'
    );
    
    $statement->execute(['email' => $email]);
    $result = $statement->fetch(PDO::FETCH_ASSOC);
    
    return $result === false ? null : $result;
}

}


**Why this works:** PDO with named placeholders eliminates SQL injection vectors. Wrapping database access in a repository isolates infrastructure concerns from domain logic. AI must be explicitly prohibited from generating raw string interpolation in queries, `mysql_*`/`mysqli_*` procedural calls, or direct PDO usage in controllers. Password hashing must default to `PASSWORD_ARGON2ID`, and all output rendering must enforce `htmlspecialchars` with `ENT_QUOTES` and `UTF-8` encoding.

## Pitfall Guide

Even with constraint files, AI can drift into anti-patterns if rules are ambiguous or incomplete. The following pitfalls represent the most frequent failure modes observed in production environments.

### 1. `mixed` Type Leakage at Boundaries
**Explanation:** Developers allow AI to use `mixed` for API payloads or serialization inputs, assuming it simplifies handling. This defeats PHP's type system and pushes validation downstream.
**Fix:** Define explicit DTOs or typed arrays with validation layers. Use `array{key: type}` syntax for structured payloads. Reserve `mixed` only for true serialization boundaries where structure is unknown.

### 2. Mocking Internal Implementation Details
**Explanation:** AI generates tests that mock internal methods or private properties, creating fragile test suites that break during refactoring.
**Fix:** Constrain AI to mock only external boundaries (databases, HTTP clients, filesystem, third-party APIs). Internal logic should be tested through public interfaces using real implementations.

### 3. Mutable Date/Time Objects
**Explanation:** AI defaults to `\DateTime` because it appears frequently in legacy code. Mutable date objects cause subtle bugs when passed by reference across services.
**Fix:** Enforce `\DateTimeImmutable` exclusively. Any date manipulation must return a new instance, preserving referential transparency.

### 4. Static State Masquerading as Utility
**Explanation:** AI generates static methods for business logic, claiming they are "stateless utilities." These methods create hidden coupling and prevent dependency injection.
**Fix:** Restrict `static` to pure mathematical or string manipulation functions with zero side effects. All domain logic must reside in instantiable classes with explicit dependencies.

### 5. Security Assumption Gaps
**Explanation:** AI assumes frameworks handle CSRF, escaping, and session management automatically. In custom or lightweight setups, this leaves critical vulnerabilities exposed.
**Fix:** Explicitly mandate CSRF token validation on all state-mutating routes, server-side MIME validation for uploads, and session ID regeneration after authentication events.

### 6. Dependency Rot & Unvetted Packages
**Explanation:** AI suggests packages based on popularity rather than maintenance status, introducing abandoned or vulnerable dependencies.
**Fix:** Integrate `composer audit` into CI pipelines. Require AI to verify package maintenance status on Packagist before suggesting new dependencies. Lock PHP version constraints in `composer.json`.

### 7. Unstructured Logging & Sensitive Data Exposure
**Explanation:** AI generates `error_log()` calls or concatenates variables into log messages, creating unsearchable logs and potential data leaks.
**Fix:** Enforce PSR-3 logger interfaces with context arrays. Implement automatic masking for PII, tokens, and financial data before logging. Prefer JSON-structured output for aggregation platforms.

## Production Bundle

### Action Checklist
- [ ] Initialize project constraint file: Create `AI_CODING_STANDARDS.md` at repository root with explicit PHP 8.2+ requirements
- [ ] Enforce strict typing: Mandate `declare(strict_types=1);` in all generated files, including tests and scripts
- [ ] Configure type boundaries: Replace `mixed` with explicit DTOs, union types, or typed array syntax
- [ ] Isolate database access: Require PDO prepared statements wrapped in repository classes; prohibit raw interpolation
- [ ] Implement constructor injection: Ban static business logic, service locators, and `new` instantiation inside methods
- [ ] Harden security defaults: Enforce `htmlspecialchars`, `PASSWORD_ARGON2ID`, CSRF validation, and session regeneration
- [ ] Standardize observability: Require PSR-3 logging with context arrays, structured JSON output, and PII masking
- [ ] Validate dependencies: Run `composer audit` in CI; lock PHP version; separate `require` and `require-dev`

### Decision Matrix

| Scenario | Recommended Approach | Why | Cost Impact |
|----------|---------------------|-----|-------------|
| Greenfield PHP 8.2+ project | Full constraint manifest with strict typing, readonly DTOs, constructor DI | Prevents legacy drift from day one; maximizes testability and refactoring velocity | Low initial setup; high long-term ROI |
| Legacy PHP 7.x migration | Gradual constraint rollout; prioritize strict types and PDO migration first | Reduces risk of breaking existing workflows while modernizing critical paths | Medium setup; phased cost distribution |
| High-security financial system | Enforce immutable value objects, PSR-3 context logging, explicit CSRF/escaping, `composer audit` gates | Minimizes attack surface; ensures auditability and compliance | Higher initial overhead; prevents catastrophic breach costs |
| Internal tooling / MVP | Relaxed typing with union types, basic repository pattern, standard PHPUnit | Accelerates delivery while maintaining testability and security baselines | Low setup; acceptable technical debt for speed |

### Configuration Template

```markdown
# AI Coding Standards — PHP 8.2+

## Language & Type System
- All files must begin with `declare(strict_types=1);`
- Every function parameter, return type, and class property requires explicit type declarations
- Use union types (`int|string`) and intersection types where applicable
- Avoid `mixed` except at serialization boundaries; prefer typed DTOs or `array{key: type}` syntax
- Use `void` for methods without meaningful returns; use `never` for functions that always throw/exit

## Architecture & Dependencies
- Constructor injection only; no `new` instantiation inside business methods
- No static methods for domain logic; restrict `static` to pure utilities
- No service locators or global state access
- Follow PSR-4 autoloading; one class per file; namespace matches directory structure
- Use Composer for all dependency management; lock PHP version in `composer.json`

## Database & Security
- PDO with prepared statements only; no `mysql_*` or `mysqli_*` procedural APIs
- Never interpolate variables into SQL; use `?` or `:param` placeholders
- Wrap database access in repository classes; controllers must not interact with PDO directly
- Escape all output: `htmlspecialchars($var, ENT_QUOTES, 'UTF-8')`
- Passwords: `password_hash($pass, PASSWORD_ARGON2ID)` + `password_verify()`
- Regenerate session ID after login: `session_regenerate_id(true)`
- Validate file MIME types server-side; never trust client-provided headers
- Require CSRF tokens on all state-mutating routes (POST, PUT, PATCH, DELETE)

## Error Handling & Observability
- Throw domain-specific exceptions extending `\RuntimeException` or `\LogicException`
- No `or die()`, `exit()` for flow control, or `@` error suppression
- Use PSR-3 logger with context arrays; prefer structured JSON output
- Mask sensitive data (tokens, card numbers, passwords) before logging
- Log levels: debug (dev only), info (normal), warning (degraded), error (failure), critical (system down)

## Data Integrity & Immutability
- Use `readonly` classes/properties for value objects and DTOs
- Prefer `\DateTimeImmutable` over mutable `\DateTime`
- Represent monetary values as integer cents; never use floats
- Use `match` expressions over `switch`; leverage nullsafe operator (`?->`)
- Prefer functional array operations (`array_map`, `filter`, `reduce`) over manual loops

## Testing Conventions
- PHPUnit for unit and integration tests
- Mock only external boundaries (database, HTTP, filesystem, third-party APIs)
- Test names must describe behavior: `test_rejects_invalid_email_format()`
- Use data providers for edge cases; avoid copy-pasted test methods
- Assert observable outcomes; never mock internal implementation details

Quick Start Guide

  1. Create the constraint file: Place the configuration template above into AI_CODING_STANDARDS.md at your project root. Ensure your AI assistant is configured to read this file before every session.
  2. Initialize strict typing: Run a repository-wide search to verify declare(strict_types=1); exists in all PHP files. Configure your IDE or linter to enforce this automatically.
  3. Audit dependencies: Execute composer audit and composer validate. Update composer.json to lock "php": "^8.2" and separate production from development dependencies.
  4. Validate architecture: Run static analysis (PHPStan/Psalm) at level 5+. Fix any violations related to missing types, static state, or untyped parameters. Iterate until the baseline is clean.
  5. Generate & review: Prompt your AI assistant to scaffold a new feature. Review the output against the constraint file. Commit only code that passes type checking, security validation, and test coverage requirements.

By treating AI alignment as an engineering discipline rather than a prompt engineering exercise, teams transform code generation from a source of technical debt into a scalable production multiplier. The constraint file becomes the single source of truth, ensuring every generated line adheres to modern PHP standards without manual intervention.