# The Day We Deleted 12,000 Lines of Legacy Bridge Code

Published 2026-07-01. Updated 2026-09-17.
Canonical URL https://log.delaa.dev/blog/2026-07-01-deleting-12000-lines-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.

---

<Image
  src="/images/deleting-12000-lines-legacy-bridge-code/hero.png"
  alt="A stone bridge built from code blocks, half gray and crumbling into a trash bin, half solid blue"
  width={2000}
  height={1125}
  className="rounded-lg"
/>

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-future` and `laminas/laminas-loader` Composer 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):**
```php
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):**
```php
$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
