The Day We Deleted 12,000 Lines of Legacy Bridge Code
Eight coordinated commits, two hours, 129 files removed. The Strangler Fig bridge had done its job — now it was time to delete it. Here is the checklist we followed, the traps we avoided, and how we knew the bridge was ready to come down.

After three months of running both frameworks side by side, it was time: every ZF1 module had been ported to Symfony. The catch-all route was still booting ZF1 on every request, but it was booting it to render nothing. No request reached a ZF1 controller anymore.
The bridge had to come down. Here is what that operation looked like.
How we knew the bridge was ready
We did not guess. We had data:
- Access log analysis. The bridge logged each time it fell through to ZF1. We monitored these logs for two weeks. Zero hits on the catch-all route.
- Functional test pass. The Codeception test suite ran every route in the application, both Symfony and ZF1 paths. All tests passed through Symfony only.
- Manual smoke test. A week of manual testing of every critical workflow: employee CRUD, contract lifecycle, payslip generation, leave management. No page rendered through ZF1.
When the catch-all route stopped catching anything, the bridge was ready.
The eight-commit sequence
The bridge removal was not a single git rm command. It was eight coordinated commits over about two hours:
Commit 1: Remove legacy ZF1 tests
The test suite had ZF1-specific tests that bootstrapped the legacy framework. These were no longer needed.
0eb78817 chore: remove legacy zf1 tests
Commit 2: Migrate active utility classes
Some utility classes from the legacy library/Custom/ directory were still in use: form helpers, view helpers, and plugin logic that had been migrated to the Utility namespace but whose old files remained. These had to be permanently moved to their modern namespace.
bbc8278b refactor(domain): migrate active utility classes from legacy
Custom to Utility namespace
Commit 3: Remove the bridge (the main event)
129 files, 12,361 lines deleted. Four bridge components gone in one commit:
f01c5124 chore(legacy): remove ZF1 bridge code, library and obsolete
Composer packages
Deleted in this commit:
LegacyFallbackController, the Strangler Fig catch-all (142 lines)LegacyBridgeListener, database and tenant bridge (65 lines)LegacyAutoloader, ZF1 class autoloader (46 lines)- Entire
library/directory: Custom, ZendX, Menu, DB/MySQL shardj/zf1-futureandlaminas/laminas-loaderComposer packages- All ZF1 test bootstrap references
Commit 4: Remove orphaned service
A MaritalService class that had been refactored months ago was still present in the codebase. No code referenced it.
4217c427 chore(domain): remove orphaned MaritalService zombie
Commit 5: Fix PHPStan errors
Removing the bridge exposed 49 PHPStan errors: references to deleted classes in type hints, docblocks, and conditional code. These had to be fixed before the build would pass.
1256f3f2 fix(static-analysis): resolve 49 PHPStan errors in
PaginatableRepositoryTrait and MountingType
Commit 6: Rewrite the front controller
public/index.php had carried ZF1 constants and the legacy autoloader registration since day one of the migration. It was now rewritten as a standard Symfony front controller:
5ea79463 fix(infra): rewrite public/index.php as standard Symfony 8
front controller
Before (Strangler Fig mode):
define('APPLICATION_PATH', realpath(__DIR__ . '/../application'));
define('APPLICATION_ENV', getenv('APPLICATION_ENV') ?: 'development');
App\Domain\System\Service\LegacyAutoloader::register();
$kernel = new Kernel($env, $env !== 'prod');
$response = $kernel->handle($request);
After (pure Symfony):
$kernel = new Kernel($env, $env !== 'prod');
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);
Commit 7: Update phpstan.neon
PHPStan had exclusions for legacy files and a bootstrap file that loaded ZF1 classes. These were no longer needed:
05e33241 chore(config): update phpstan.neon — remove deleted legacy
exclusions and stubs bootstrap
Commit 8: Remove APPLICATION_ENV
The APPLICATION_ENV constant was a ZF1 artifact. Modern Symfony uses APP_ENV. This was the last ZF1 reference in the codebase:
7d953ac4 chore: remove obsolete APPLICATION_ENV (ZF1 legacy)
The order matters
The commits had to be sequenced carefully:
- Step 2 (utility migration) had to happen before step 3 (bridge removal). Otherwise, active classes would be deleted.
- Step 5 (PHPStan fixes) had to happen before step 6 (front controller rewrite). Otherwise, the build would fail with 49 errors and the front controller change could not be verified.
- Step 3 (bridge removal) was the point of no return. All ZF1 code was gone. Steps 4 through 8 were cleanup and hardening.
After commit 3, the application would not deploy if we had missed a dependency. After commit 6, ZF1 could not boot even if we tried. After commit 8, not a single line of ZF1 code remained in the codebase.
What the deletion felt like
Deleting 12,000 lines of code in two hours is strangely satisfying. The bridge components had been carefully built. Each one solved a real problem. But they were temporary by design. The cleanest commit in the repository history is the one that removes code that is no longer needed.
— Delaa