How do you enforce PHP code quality and safety at scale?
answer
Enterprise PHP code quality begins with declare(strict_types=1) everywhere, domain types, and narrow interfaces. Enforce static analysis (PHPStan or Psalm) at max practical levels, block merges on new issues, and codify refactors with Rector rules. Lock supply chain with Composer constraints, audited transitive deps, and reproducible builds. Keep secrets out of code using vaults and environment injection, encrypt at rest, and gate configuration by environment. Add CI gates, metrics, and autofixes to keep drift near zero.
Long Answer
Scaling PHP code quality and safety is a systems problem: you must align language features, tooling, process, and culture so that correctness is the default and defects are expensive to introduce. The foundation is strict typing and design-by-contract, enforced by static analysis and automated refactors, with a hardened dependency supply chain and disciplined secrets and configuration management.
1) Strict typing as a contract
Start all files with declare(strict_types=1). Model domain primitives explicitly (Email, Money, Percentage) rather than passing raw strings or floats. Prefer immutable value objects and final classes for safety. Narrow method signatures, return types, and generics (Psalm templates or PHPStan generics). For collections, encode element types in docblocks understood by analyzers. Adopt null-safety conventions: avoid nullable where you can model absence explicitly (Optional, Result).
2) Static analysis at scale
Run PHPStan or Psalm in CI and locally. Target the highest practical level (PHPStan level 8 or Psalm max) and ratchet upwards. Enable strict rulesets: missing types, mixed, risky array access, magic properties, dynamic method calls, and undefined variables. Add baseline files only to start; then ratchet by failing the build on any increase in baseline. Split analysis into fast lane (pull request diff) and full lane (nightly). Wire analyzers to read framework stubs (Symfony, Laravel) so dynamic features still get type-checked.
3) Automated refactors with Rector
Codify conventions as Rector rules: enforce final on services, convert arrays to DTOs, replace legacy helpers with modern APIs, migrate to readonly properties, and standardize exception hierarchies. Run Rector in CI to fail when drift appears and provide autofix. Keep a “safety first” set (purely mechanical) and an “advisory” set (requires human review). Combine Rector with coding standards (PHP-CS-Fixer or PHP_CodeSniffer) to keep formatting and naming consistent.
4) Testing pyramid and mutation testing
Unit tests should lock behavior of value objects and domain rules. Integration tests should exercise containers, database, and queues. Add mutation testing (Infection) for critical modules to ensure tests notice real defects. Run property-based testing for parsers and calculators. Make tests deterministic by controlling time, locale, and randomness.
5) Dependency hygiene with Composer
Pin PHP and extension versions. Use semver-aware Composer constraints that are conservative for runtime libraries (^x.y only when safe) and open for dev tools. Prefer --no-dev in production builds and --prefer-dist --no-interaction --no-progress --audit. Vendor whitelisting: restrict repositories to packagist and your private registry; disable inline path repos for production. Enable Composer’s security advisories, run composer audit, and monitor transitive risks. Build reproducibly with Composer.lock committed, cache artifacts, and verify signatures or checksums where possible.
6) Architecture boundaries and dependency rules
Define layers (Domain, Application, Infra, Web). Enforce direction with PHPStan deprecations or tools like Deptrac: for example, Web may depend on Application, but Domain depends on nothing external. Forbid reaching across modules via static facades; prefer constructor injection with interfaces registered in the container. This reduces hidden coupling and simplifies testability.
7) Configuration and secrets management
Never commit secrets. Load configuration from the environment at runtime, but retrieve secrets from a vault (for example, AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager). Encrypt at rest, rotate regularly, version keys, and scope access with least privilege. Separate configuration per environment, validate on boot (fail fast), and use typed config objects to avoid stringly typed arrays. Logically segregate secrets for applications, build systems, and human operators.
8) Runtime safeguards and policies
Harden PHP-FPM and web server configs: disable dangerous functions where possible, set memory and execution limits, and enforce secure session settings. Use read-only containers and non-root users. Add runtime assertions in critical paths guarded behind environment flags. Emit structured logs and traces (OpenTelemetry) with correlation identifiers, and enforce error budgets and service level objectives so quality is measured.
9) Continuous integration and policy gates
CI must block merges on: failing tests, new static analysis issues, coding standard violations, unsafe Rector diffs (configurable), and Composer audit failures for critical advisories. Add artifact signing, SBOM generation (CycloneDX), and dependency diff checks. For large codebases, shard analysis and tests, but keep single-source-of-truth rules so results are consistent. Publish quality dashboards: test coverage, mutation score, static analysis debt, and rule violations per module.
10) Developer ergonomics and culture
Make the correct path the fast path. Ship a make setup && make check that runs format, Rector, analysis, and tests locally. Provide editor integration for PHPStan/Psalm with quick-fix tips. Pair on difficult type refactors and keep a living “how to type this pattern” cookbook. Document policies: when to create a value object, how to write an exception, what to put in config versus code.
11) Migration and ratcheting strategy
For legacy code, introduce strict types at boundaries first (new modules, changed files). Add analyzers in allow mode with a baseline; then ratchet by failing any new mixed or @var regressions. Convert arrays to DTOs where they cross module boundaries. Use Rector to migrate deprecated APIs incrementally. Celebrate the removal of baseline lines as milestones.
With strict types, disciplined static analysis, codified Rector refactors, hardened Composer supply chain, and robust secrets and configuration management, your PHP codebase scales safely. The system turns correctness into a property of the pipeline, not the heroics of individuals.
Table
Common Mistakes
Leaving strict_types off and trusting runtime coercions. Treating analyzers as advisory and allowing baselines to grow. Using arrays as untyped DTOs, then debugging shape mismatches at runtime. Depending on static facades and globals that hide real coupling. Accepting wild Composer constraints or allowing path repositories into production. Committing .env files with secrets or passing secrets through build logs. Skipping mutation testing and assuming coverage equals quality. Running Rector once during a migration but not as a policy. Failing to validate configuration on boot, leading to late crashes. Relying on manual code reviews for things static tools enforce better and faster.
Sample Answers (Junior / Mid / Senior)
Junior:
“I add declare(strict_types=1) to all files and write explicit parameter and return types. I run PHPStan in CI and fix issues before merging. Composer.lock is committed and I run composer audit. Environment variables hold configuration, never secrets.”
Mid:
“I target PHPStan level 8, block merges on new errors, and use Rector to enforce conventions like final services and readonly properties. I pin Composer versions, restrict repositories, and generate SBOMs. Secrets live in a vault and are injected at runtime into typed config. Deptrac keeps layers clean.”
Senior:
“I design a quality platform: strict types, value objects, and generics, enforced by PHPStan and Psalm. Rector codifies migrations, while CI gates on tests, analysis, audits, and SBOM drift. Composer is locked, audited, and reproducible. Architecture is dependency-inverted and verified by rules. Secrets are rotated in a vault with least privilege. Metrics track baseline lines, mutation score, and rule violations to drive continuous improvement.”
Evaluation Criteria
Strong answers mandate declare(strict_types=1), value objects, and explicit types, and run PHPStan/Psalm at high levels with strict rules and a ratcheting baseline. They automate refactors with Rector, keep Composer constraints conservative, commit and audit composer.lock, and generate SBOMs. They enforce architecture boundaries with tools like Deptrac, and they separate configuration from secrets, storing secrets in a vault with rotation and least privilege. CI blocks merges on analysis, tests, Rector, coding standards, and composer audit. Red flags: no strict types, growing baselines, unpinned dependencies, secrets in code, and relying only on human reviews.
Preparation Tips
Create a demo monorepo with two bounded contexts. Enable declare(strict_types=1) and add PHPStan (level 8) with strict rules. Introduce Rector to auto-finalize services, convert arrays to DTOs, and migrate deprecated APIs. Add Deptrac rules to prevent Web from touching Domain. Configure Composer to use a private registry, commit composer.lock, and enable audits and SBOM generation. Set up CI to run tests, infection for a small module, PHPStan, Rector dry-run, coding standards, and composer audit. Integrate a vault to inject database and API secrets into typed config at runtime. Track baseline deltas and fail on regressions.
Real-world Context
A marketplace with frequent outages enabled strict types and PHPStan at high levels. Defects shifted left and production incidents fell notably. A fintech codified migrations with Rector, completing a framework upgrade two months early and avoiding risky manual edits. Another team locked supply chain risk by pinning Composer versions, auditing transitive dependencies, and producing SBOMs; a downstream vulnerability was contained in hours. Moving secrets to a vault removed leaked .env incidents. Architecture rules (Deptrac) reduced cyclic dependencies, improving deploy cadence and test isolation.
Key Takeaways
- Enforce declare(strict_types=1), value objects, and explicit generics.
- Run PHPStan/Psalm at high levels; ratchet baselines to zero.
- Codify change with Rector; do not rely on manual migration.
- Lock and audit Composer dependencies; generate SBOMs.
- Store secrets in a vault, inject typed config, and rotate regularly.
- Guard merges with CI quality gates and publish observable quality metrics.
Practice Exercise
Scenario:
You are inheriting a legacy PHP codebase that mixes arrays, global helpers, wide Composer ranges, and .env secrets. Management requires measurable improvements in reliability within two sprints, without halting feature delivery.
Tasks:
- Add declare(strict_types=1) and configure PHPStan at level 6 with a baseline. Turn on strict rules for mixed and array offsets. Ratchet by failing any new issues; plan to reach level 8 incrementally.
- Introduce Rector with rules to finalize services, promote properties to readonly, replace legacy helpers, and convert common arrays to DTOs. Run Rector in CI as a failing check and as a developer autofix.
- Lock dependency supply: commit composer.lock, restrict repositories, narrow production constraints, enable composer audit, and generate an SBOM.
- Define layers (Domain, Application, Infra, Web) and enforce with Deptrac. Break one obvious cycle as a showcase.
- Move secrets to a vault; inject typed configuration at boot; validate configuration and fail fast.
- Add Infection to one critical module and measure mutation score.
- Wire CI gates: tests, PHPStan, Rector, coding standards, composer audit, SBOM diff. Publish a dashboard showing baseline lines, analysis violations, mutation score, and audit status.
Deliverable:
A plan and pull requests that demonstrate enforced strict types, high-signal static analysis, Rector-based refactors, Composer hygiene, and secure secrets and configuration management, all guarded by CI with measurable quality improvements.

