Stack conventions
PHP
Modern PHP 8.2+ applications and libraries with Composer, strict types and static analysis, tested with PHPUnit or Pest.
Modern PHP 8.2+ applications and libraries with Composer, strict types and static analysis, tested with PHPUnit or Pest.
Used for: php.
Principles
- Declare strict types at the top of every PHP file (declare(strict_types=1)) unless the repository deliberately does not.
- Type everything - parameters, return types, typed and readonly properties, and precise PHPDoc generics where native types cannot express it.
- Follow PER Coding Style (the successor of PSR-12) as enforced by the repository's PHP-CS-Fixer or PHP_CodeSniffer configuration.
- Autoload through Composer PSR-4; one class per file, namespace matching the directory; no require/include of class files.
- Prefer small, final, immutable classes with constructor injection; depend on interfaces at boundaries.
- Fail loudly with specific exceptions; never return false or null to signal errors from new code.
- Treat all input as untrusted - validate at the edge, use prepared statements, escape output for its context.
- Use modern language features where the supported PHP version allows (enums, readonly, match, first-class callables, named arguments).
- Raise the PHPStan (or Psalm) level over time and never add new baseline entries for new code.
Conventions
Accessibility
Concurrency
- PHP-FPM requests share nothing; persist shared state in the database or cache with explicit locking (SELECT ... FOR UPDATE, atomic cache operations).
- In long-running workers (queues, RoadRunner, FrankenPHP worker mode, Swoole), reset state between jobs and avoid static caches that leak across requests.
Dependencies
- Commit composer.lock for applications; libraries declare constraints with caret (^) ranges and a php platform requirement.
- Use require-dev for tooling; run composer validate --strict in CI.
- Avoid abandoned packages (composer outdated / audit report them).
Documentation
- Rely on native types; add PHPDoc only where it adds information (generics like list
, array shapes, @throws). - Keep README setup and composer scripts (composer test, composer lint) documented and working.
Errors
- Throw specific exception classes (extending a project base exception or SPL exceptions like InvalidArgumentException, DomainException).
- Catch Throwable only at the top-level handler; catch specific exceptions elsewhere and never silence with @.
- Chain exceptions with the previous parameter to keep the cause.
- Configure display_errors=Off in production and log errors instead.
Logging
- Log through a PSR-3 LoggerInterface (Monolog) injected into classes; no echo, var_dump or print_r left in code.
- Use message placeholders with a context array (logger->info('Order {id} paid', ['id' => $id])).
- Never log passwords, tokens, card data or full request bodies.
Naming
- Classes, interfaces, traits and enums in StudlyCaps; methods and properties in camelCase; constants and enum cases per the repository (UPPER_SNAKE constants, StudlyCaps enum cases are common).
- Interfaces named for the role (Clock, OrderRepository); suffixes like Interface only if the codebase already uses them.
- Namespaces start with the vendor/application root declared in composer.json autoload.
Performance
- Enable OPcache (and preloading where it helps) in production; use composer dump-autoload --optimize --classmap-authoritative for deployments.
- Avoid queries in loops; batch or eager load.
- Use generators (yield) to stream large datasets instead of loading arrays into memory.
Security
- Use PDO prepared statements with bound parameters (PDO::ATTR_EMULATE_PREPARES false where possible) or the ORM's query builder.
- Escape output with htmlspecialchars($s, ENT_QUOTES, 'UTF-8') or the template engine's auto-escaping (Twig, Blade).
- Hash passwords with password_hash/password_verify (PASSWORD_DEFAULT or PASSWORD_ARGON2ID); never md5/sha1.
- Use random_bytes/random_int for tokens; hash_equals for comparing secrets.
- Never unserialize() untrusted data (use JSON); never pass input to eval, include, shell_exec or system without escapeshellarg and an allowlist.
- Run composer audit to check dependencies against security advisories.
Structure
- Source in src/ mapped by composer.json autoload psr-4; tests in tests/ mapped by autoload-dev.
- Public web root contains only the front controller (public/index.php) and assets.
- One class, interface, trait or enum per file; file name equals the class name.
- Configuration from environment variables or config files outside the web root; never commit secrets or .env with real values.
Testing
- PHPUnit (or Pest if the repository uses it); test classes are final, named
Test, methods named for behaviour. - Use data providers (PHPUnit 10+ attributes: #[DataProvider], #[Test]) for input variations.
- Prefer fakes and in-memory implementations over deep mocking; mock only interfaces you own.
- Keep unit tests free of database and network; mark integration tests separately.
- Done means tested: new or changed behaviour ships with tests in the same change, and a fixed bug gets a regression test that fails without the fix.
Review checklist
- Does every changed PHP file declare strict_types=1 (if the repository uses it)?
- Are all parameters, returns and properties natively typed, with PHPDoc only for generics and shapes?
- Does PHPStan/Psalm pass at the configured level without new baseline entries?
- Is the code formatted per the repository's PER/PSR-12 fixer configuration?
- Are dependencies injected through constructors against interfaces?
- Are queries parameterised and output escaped for its context?
- Are exceptions specific, chained with previous, and never silenced?
- Is logging done through PSR-3 with context arrays and without secrets?
- Do new classes follow PSR-4 placement and naming?
- Are there PHPUnit/Pest tests for new behaviour, using data providers for variations?
- Are composer.json changes justified, with composer.lock updated?
Quality checks (run when your repository has the tool configured)
| Check | Kind | Tool | Blocks the pull request |
|---|---|---|---|
phpstan |
static | phpstan | no |
psalm |
static | psalm | no |
php-cs-fixer |
format | php-cs-fixer | no |
phpcs |
lint | PHP_CodeSniffer | no |
composer-audit |
security | composer audit | no |
composer-validate |
lint | composer validate | no |
php-syntax |
static | php | no |
gitleaks |
security | gitleaks | no |