The Case for Strict PHPStan Baselines in Enterprise Codebases
Modernizing a legacy PHP application presents a paradox: you critically need strict static analysis to prevent future bugs, but turning on a tool like PHPStan immediately throws 5,000 errors, completely breaking the CI pipeline.
During a recent stabilization project on an enterprise platform (rlh-core), we faced exactly this. The codebase had excellent business logic but was rife with missing return types, empty variable checks (empty($message) rather than strict null checks), and loosely defined arrays.
The Impossible Migration
The naive approach is to halt feature development for a month and force the team to fix all 5,000 errors until PHPStan returns green. From a business perspective, this is financial suicide.
The alternative is to leave PHPStan disabled, allowing the technical debt to accumulate.
The "Baseline" Deployment Strategy
To enforce future quality while respecting past debt, we implemented the PHPStan Baseline pattern.
1. Generating the Debt Ledger
We configured PHPStan to its maximum strictness (Level 8) and ran it against the entire codebase. When it identified the 5,000 errors, we piped that output into a specialized baseline file (phpstan-baseline.neon).
This baseline acts as a cryptographic signature of the existing technical debt. Essentially, it tells the CI pipeline: "Ignore these specific 5,000 errors on these specific 5,000 lines of code."
2. Enforcing the Border
Once the baseline was committed, the CI pipeline immediately went "green." However, the strict Level 8 rules were now fully active for all new code.
If a developer wrote a new class that lacked a return type, or attempted to access an array key without verifying its existence, PHPStan failed the build. They could not introduce new debt.
3. Incremental Eradication
We introduced a rule regarding the baseline: if you touch a file to add a feature, you must resolve the baseline errors for that file.
When an engineer fixed an empty() check on a legacy controller, PHPStan automatically noticed the error no longer existed and requested that the specific error be removed from the baseline file (reportUnmatched: true).
A Culture of Gradual Perfection
Over a six-month period, the baseline shrank from 5,000 errors to under 300, without a single dedicated "refactoring sprint." The application became highly deterministic and type-safe simply by enforcing a strict boundary on new code.
When dealing with enterprise technical debt, the strategy isn't to burn the house down; it's to stop adding fuel to the fire.
Looking for an Architect who doesn't just write code, but builds the AI systems that multiply your team's output? View my enterprise CMS case studies at victorjimenezdev.github.io or connect with me on LinkedIn.
