| name | legacy-coexistence |
| description | Add modern PHP code to a legacy 7.x codebase incrementally. Typed properties, readonly, enums — how to introduce them without forcing a big-bang upgrade. |
Legacy coexistence
When to apply
- Working in a codebase with mixed PHP 7.4 and modern PHP 8+ code
- Asked "can I use enums here?" / "is readonly safe in this file?"
- Reviewing a PR that introduces modern syntax in a legacy module
The reality
Many production PHP codebases run a PHP 7.4 baseline but allow modern
features in NEW code, planned for an eventual upgrade. The mix is
intentional, not a smell — as long as new code is forward-compatible.
Forward-compatibility rules
For NEW code (added today, runs on 7.4 today, migrates cleanly to 8.x):
- ✅ Type hints (incl.
?Type, Type|null, union types if 8.0+)
- ✅ Return types
- ✅ Strict types (
declare(strict_types=1))
- ✅ Constructor property promotion (PHP 8.0+) — only if you've moved off 7.4
- ✅ Match expressions (8.0+) — only post-7.4
- ❌ Typed properties — 7.4 supports them; safe to add
- ⚠️ Readonly properties (8.1+) — fine only post-7.4
- ⚠️ Enums (8.1+) — fine only post-7.4
- ❌ Named arguments (8.0+) — caller-side feature, breaks 7.x callers
How to introduce modern features incrementally
- Decide a feature floor per module.
src/Elements/ may be 8.2+;
src/autoload/ (legacy) stays 7.4 until rewritten.
- Use static analysis to enforce the boundary. PHPStan
phpVersion: 70400 for legacy paths; per-path overrides for
modern modules.
- Don't half-modernize. A function with PHP 8 syntax in a 7.4
namespace will crash at runtime. Keep the floor consistent within
a file.
Concrete patterns
Adding a value object to legacy code:
namespace Elements\Value;
final readonly class EmailAddress
{
public function __construct(public string $value) { }
}
Use it from legacy code (7.4):
class SomeOldClass
{
private $email;
public function setEmail($value): void {
$this->email = new \Elements\Value\EmailAddress($value);
}
}
Legacy code can CONSUME modern classes; it just can't internally use
syntax not yet supported by the legacy floor.
Anti-patterns
- Sprinkling modern syntax randomly in legacy files → runtime errors on
legacy servers
- Refusing to add ANY modern code because "the rest is legacy" → no path
to ever modernize
- Modernizing everything at once → big-bang migrations fail. Incremental
is the only sustainable approach.