# AGENT.md — PHP Coding Standard This file defines the coding standards and working rules for AI agents and developers contributing to this PHP codebase. It is based on the principles from **PHP: The Right Way** and adapted into practical project instructions. Source reference: https://phptherightway.com/ --- ## 1. Core Philosophy Write PHP that is: - **Readable** before clever. - **Secure by default**. - **Consistent with community standards**. - **Easy to test, debug, and refactor**. - **Separated by responsibility**: routing, controllers, services, models, persistence, templates, and configuration should not be mixed together. PHP does not have only one canonical “right way,” so prefer widely accepted standards, documented project conventions, and clear tradeoffs over personal style. --- ## 2. PHP Version Standard Use the current stable PHP version supported by the project. Default expectation: ```text PHP 8.x+ ``` Do not introduce code that depends on unsupported PHP versions unless the project explicitly targets a legacy runtime. When adding a language feature, verify that it is supported by the project’s configured PHP version in `composer.json`. Example: ```json { "require": { "php": ">=8.2" } } ``` --- ## 3. Coding Style Follow recognized PHP standards unless the repository already defines stricter rules. Preferred standards: - **PSR-1**: Basic Coding Standard - **PSR-12**: Extended Coding Style - **PSR-4**: Autoloading Use automated tooling rather than manual formatting arguments. Recommended tools: ```bash composer require --dev squizlabs/php_codesniffer composer require --dev friendsofphp/php-cs-fixer ``` Example checks: ```bash vendor/bin/phpcs --standard=PSR12 src tests vendor/bin/php-cs-fixer fix --dry-run --diff ``` Example fix: ```bash vendor/bin/php-cs-fixer fix ``` ### Naming Rules Use English names for code symbols and infrastructure. Use: ```php class InvoiceRepository { public function findByCustomerId(int $customerId): array { // ... } } ``` Avoid unclear abbreviations: ```php class InvRepo { public function fbcid($cid) { // ... } } ``` ### Formatting Rules - Use `value; } } ``` Guidelines: - Keep controllers thin. - Put business rules in services or domain objects. - Put persistence logic in repositories or data access classes. - Use interfaces when multiple implementations are expected or when it improves testing. - Avoid huge “utility” classes. - Avoid magic methods unless they provide clear framework integration or a documented benefit. --- ## 8. Dependency Injection Prefer dependency injection over creating dependencies inside classes. Good: ```php final class RegisterUser { public function __construct( private UserRepository $users, private PasswordHasher $passwords ) { } public function handle(string $email, string $plainPassword): void { $hash = $this->passwords->hash($plainPassword); $this->users->create($email, $hash); } } ``` Avoid: ```php final class RegisterUser { public function handle(string $email, string $plainPassword): void { $users = new UserRepository(); $passwords = new PasswordHasher(); // ... } } ``` Rules: - Constructor injection is preferred for required dependencies. - Do not use service locators casually. - Do not hide dependencies in global variables. - Keep dependency containers at application boundaries, not inside domain logic. --- ## 9. Database Access Use PDO or a well-maintained database abstraction layer/ORM. Never concatenate untrusted input into SQL. Bad: ```php $sql = "SELECT * FROM users WHERE id = " . $_GET['id']; ``` Good: ```php $stmt = $pdo->prepare('SELECT * FROM users WHERE id = :id'); $stmt->bindValue(':id', $id, PDO::PARAM_INT); $stmt->execute(); $user = $stmt->fetch(PDO::FETCH_ASSOC); ``` Rules: - Use prepared statements and bound parameters. - Validate input before using it in writes. - Keep SQL out of templates. - Keep database access out of controllers where practical. - Use transactions when multiple writes must succeed or fail together. - Do not rely only on client-side validation. - Do not expose raw database errors to users. Transaction example: ```php $pdo->beginTransaction(); try { $orders->create($order); $auditLog->record('order.created', $order->id()); $pdo->commit(); } catch (Throwable $e) { $pdo->rollBack(); throw $e; } ``` --- ## 10. Input Validation and Output Escaping Treat all external data as untrusted. Untrusted data includes: - `$_GET` - `$_POST` - `$_REQUEST` - `$_COOKIE` - `$_SERVER` - uploaded files - request bodies - session values - database values originally supplied by users - third-party API responses ### Validate on Input Example: ```php $email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL); if ($email === false || $email === null) { throw new InvalidArgumentException('A valid email address is required.'); } ``` ### Escape on Output For HTML output: ```php function e(string $value): string { return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); } ``` Usage: ```php
= e($user->name()) ?>
``` Rules: - Escape based on context: HTML, attribute, JavaScript, CSS, URL, SQL, shell. - Do not use the same escaping function for every context. - Prefer template engines with automatic escaping when appropriate. - Avoid allowing raw HTML from users. If required, sanitize with a proven whitelist sanitizer. - Use `escapeshellarg()` when passing controlled values to shell commands, and avoid shell execution when possible. - Never trust file paths supplied by users. Reject path traversal values such as `../`, `/`, `\`, and null bytes. --- ## 11. Passwords and Authentication Never store plain-text passwords. Use PHP’s password API: ```php $hash = password_hash($plainPassword, PASSWORD_DEFAULT); if (! password_verify($plainPassword, $hash)) { throw new RuntimeException('Invalid credentials.'); } ``` Rules: - Use `password_hash()` for new password hashes. - Use `password_verify()` for login checks. - Use `password_needs_rehash()` when algorithm/cost settings change. - Do not create your own password hashing algorithm. - Do not use general-purpose hashes like `md5`, `sha1`, or raw `sha256` for passwords. - Rate-limit login attempts. - Regenerate session IDs after login. - Use secure, HTTP-only, SameSite cookies for sessions. --- ## 12. Serialization and Data Exchange Do not call `unserialize()` on untrusted data. Prefer JSON for data exchange: ```php $data = json_decode($json, true, flags: JSON_THROW_ON_ERROR); $json = json_encode($data, JSON_THROW_ON_ERROR); ``` Rules: - Use `JSON_THROW_ON_ERROR` for new code. - Validate decoded data before using it. - Avoid PHP serialization for data that crosses trust boundaries. --- ## 13. Configuration and Secrets Rules: - Keep secrets out of source control. - Do not commit passwords, API keys, private keys, tokens, or production DSNs. - Store configuration outside the public web root. - Use environment variables or ignored local config files for secrets. - Provide a safe example file such as `.env.example`. Example `.gitignore` entries: ```text .env .env.local /config/local.php /var/cache/ /var/log/ /vendor/ ``` Example `.env.example`: ```text APP_ENV=local APP_DEBUG=true DATABASE_URL=mysql://user:password@localhost:3306/app ``` --- ## 14. Error Handling and Logging Use exceptions for exceptional failure paths. Development: - Show errors locally. - Log errors. - Use Xdebug when debugging complex issues. Production: - Do not display errors to users. - Log errors to a secure log destination. - Return safe, generic error messages. - Preserve enough context in logs for troubleshooting. Do not leak: - stack traces to users - SQL statements with secrets - environment variables - full filesystem paths - tokens or passwords Example: ```php try { $service->handle($request); } catch (Throwable $e) { $logger->error('Order processing failed.', [ 'exception' => $e, 'requestId' => $requestId, ]); http_response_code(500); echo 'An unexpected error occurred.'; } ``` --- ## 15. Templates and Views Keep presentation separate from business logic. Rules: - Do not query the database from templates. - Do not place business rules in templates. - Escape output by default. - Prefer simple view models or arrays passed into templates. - Use a template engine with automatic escaping when it fits the project. Plain PHP template example: ```php