PHPackages                             nih/legacy-gateway - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. nih/legacy-gateway

ActiveLibrary[API Development](/categories/api)

nih/legacy-gateway
==================

Route-level legacy fallback for route-by-route migration in PSR-15 applications built on nih/http-kernel.

0.1.1(1mo ago)00MITPHPPHP 8.4 - 8.5

Since Apr 11Pushed 1mo agoCompare

[ Source](https://github.com/nih-soft/legacy-gateway)[ Packagist](https://packagist.org/packages/nih/legacy-gateway)[ RSS](/packages/nih-legacy-gateway/feed)WikiDiscussions master Synced 1w ago

READMEChangelogDependencies (9)Versions (3)Used By (0)

`nih/legacy-gateway`
====================

[](#nihlegacy-gateway)

Route-level legacy facade for PSR-15 applications built on top of `nih/http-kernel`.

What It Is
----------

[](#what-it-is)

`nih/legacy-gateway` lets a PSR-15 application act as a facade over an existing legacy application.

Migrating a legacy system that already works in production is almost always painful:

- the old code must keep serving real traffic
- the new code must go live incrementally
- old and new behavior must coexist for a while
- a big-bang rewrite is usually too risky

That is why route-by-route migration exists as a practical strategy.

Instead of replacing the whole legacy application at once, you move one route, one endpoint group, or one use case at a time into the new PSR-15 application. The rest of the traffic continues to be handled by legacy code until you are ready to migrate it too.

This gives teams a controlled migration boundary:

- new routes can be implemented in modern middleware and handlers
- existing legacy pages can continue to work unchanged
- the application can stay online while the boundary moves gradually toward the new codebase

The router defines the boundary between modern and legacy code:

- if a request matches a modern route, it stays in the PSR-15 pipeline
- if no route matches, control falls through to a legacy entrypoint

In other words, the PSR-15 application becomes a route-level facade in front of legacy behavior. Every newly migrated route is claimed by the modern router, and everything else still falls through to the legacy entrypoint.

This makes gradual migration possible without introducing a separate reverse proxy, splitting the application into two public entrypoints, or forcing a rewrite-first migration plan.

How Request Routing Works
-------------------------

[](#how-request-routing-works)

The package keeps the integration surface intentionally small:

- `LegacyGatewayBootstrap` inserts `LegacyGatewayMiddleware` immediately after `RouteMatchMiddleware`
- `LegacyGatewayMiddleware` decides whether the request stays in the PSR-15 application or falls through to legacy code

The key bootstrap line is:

```
$app->pipeline->append(LegacyGatewayMiddleware::class, after: RouteMatchMiddleware::class);
```

This is the whole routing boundary in one place:

- `RouteMatchMiddleware` runs first and decides whether the current request matches a modern route
- `LegacyGatewayMiddleware` runs immediately after that decision
- if a route was matched, the request stays in the modern PSR-15 pipeline
- if no route was matched, the request can fall through to legacy

Without this exact placement, the package would not be route-driven. It would either run too early, before routing knows anything, or too late, after the request had already moved deeper into the modern dispatch flow.

Request flow:

1. The request enters the `nih/http-kernel` application.
2. Routing runs and produces a `RouteMatchResult`.
3. If the route was matched, the request continues through the normal PSR-15 pipeline.
4. If routing returns `RouteMatcher::NOT_FOUND`, the middleware hands off to the legacy entrypoint.
5. The handoff is wrapped in `DeferredCallableResponse`, so the kernel can restore runtime state before executing legacy code.

In practice, this means the PSR-15 app can behave like a route-level proxy or facade over legacy behavior while keeping routing as the decision point.

When To Use It
--------------

[](#when-to-use-it)

Typical use cases:

- migrate a legacy system route by route
- move new endpoints such as `/api/*`, `/health`, or `/admin/*` into PSR-15 code first
- keep a single application entrypoint while modern and legacy code coexist

Example migration shape:

- `/api/users`, `/health`, and `/admin/login` are matched by the modern router and handled by PSR-15 middleware and handlers
- `/catalog`, `/checkout`, and all unmatched URLs fall through to `legacy/index.php`

Quick Start
-----------

[](#quick-start)

Applications opt in explicitly by adding `LegacyGatewayBootstrap` to the bootstrap list and overriding the demo `entrypoint` binding in an app-specific bootstrap.

Recommended bootstrap order:

```
return [
    NIH\HttpKernel\Bootstrap\Psr17Bootstrap::class,
    NIH\HttpKernel\Bootstrap\ErrorHandlingBootstrap::class,
    NIH\HttpKernel\Bootstrap\RoutingBootstrap::class,
    NIH\LegacyGateway\LegacyGatewayBootstrap::class,
    App\Bootstrap\AppBootstrap::class,
];
```

Why this order matters:

- `RouteMatchMiddleware` must run before `LegacyGatewayMiddleware`
- the legacy gateway must see the routing result before route dispatch
- the app-specific bootstrap should override the demo fallback configured by `LegacyGatewayBootstrap`

In other words, `LegacyGatewayBootstrap` does not just "register one more middleware". It places the legacy gateway exactly at the point where the application already knows whether the request belongs to the new router or should fall through to the old system.

`LegacyGatewayBootstrap` ships with a demonstration fallback only:

```
$app->services->auto(LegacyGatewayMiddleware::class)
    ->argument('entrypoint', static function (): void {
        echo 'Legacy Fallback';
    });
```

Real applications are expected to replace that with their own legacy entrypoint.

Configure the Legacy Entrypoint
-------------------------------

[](#configure-the-legacy-entrypoint)

### File-based handoff

[](#file-based-handoff)

```
