Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Zf1Wrapperbundle Laravel Package

mainlycode/zf1wrapperbundle

A Symfony bundle that wraps Zend Framework 1, helping you reuse ZF1 components and legacy code inside Symfony applications with integration glue for bootstrapping and service access.

View on GitHub
Deep Wiki
Context7

Getting Started

This is not a composer require. The package is a Symfony 2 bundle; if you are on Laravel, there is no integration path. Treat the repository as a readable reference implementation of a delegation seam, not a dependency. The day-to-day value is in the ~200–400 lines that define the handoff — the surrounding ZF1 application, not this bundle, is the expensive part.

Where to look first (in this order):

  1. DependencyInjection/ and Resources/config/services.xml — this is the whole transferable idea: how the ZF1 autoloader and Zend_Application bootstrap are constructed as container services, and what arguments they accept.
  2. The bundle's Configuration.php — the config tree tells you exactly what ZF1 needs bootstrapped (library_path, application_path, environment, bootstrap class). Do not trust key names from memory; read the tree.
  3. The kernel/request integration point — whether delegation is a listener, a route, or a service the controller calls. This decides the shape you will copy.

Ignore the ZF1-facing code. You can write that part yourself in an afternoon.

First use case. Pick exactly one low-risk legacy route (a read-only page, no auth, no POST, no file download) and prove you can serve it through your own handoff. Concretely:

  • Symfony (supported versions): a kernel RequestEvent listener or a catch-all route registered last that boots the ZF1 front controller and converts its Zend_Controller_Response_Http into a Symfony Response.
  • Laravel: a Route::fallback() or terminal middleware that includes the ZF1 front controller under output buffering and maps the result.
// Symfony: the minimal handoff, framework-agnostic in spirit
$front = \Zend_Controller_Front::getInstance();
$front->setControllerDirectory($legacyPath . '/application/controllers');
$front->setParam('bootstrap', $bootstrap); // your Zend_Application_Bootstrap_Bootstrap
$zendResponse = $front->dispatch();
// Laravel: fallback route delegating to the ZF1 front controller
Route::fallback(function (\Illuminate\Http\Request $request) {
    return app(\App\Legacy\Zf1Bridge::class)->handle($request);
});

Time-box the spike. The go/no-go gate is: session shared, login works, one redirect and one 404 render correctly.

Implementation Patterns

Pattern 1 — In-process delegation (what this bundle does). The new application owns the request, boots both runtimes, and dispatches to the ZF1 front controller for unmapped paths. Use it when auth and session must be shared and you cannot split deploys.

// Symfony: terminal catch-all, registered after your real routes
class LegacyController
{
    public function __invoke(Request $r): Response
    {
        $start = microtime(true);

        ob_start();
        $zendResponse = $this->zf1->dispatch($r); // boots ZF1, dispatches
        $body = ob_get_clean();

        $this->logger->info('legacy.delegated', [
            'path' => $r->getPathInfo(),
            'status' => $zendResponse->getStatus(),
            'ms' => (microtime(true) - $start) * 1000,
        ]);

        return new Response($body, $zendResponse->getStatus(), $this->headers($zendResponse));
    }
}

The legacy.delegated log line is the highest-leverage thing in this whole pattern — it is your burndown metric. Emit it from day one.

Pattern 2 — Laravel middleware fallback. If you are on Laravel, this is the day-to-day shape. Rewire $_SERVER and $_GET/$_POST from the Laravel request, include the legacy front controller, capture output.

class Zf1Bridge
{
    public function handle(Request $request): Response
    {
        $_SERVER['REQUEST_URI']    = $request->getRequestUri();
        $_SERVER['REQUEST_METHOD'] = $request->getMethod();
        $_POST   = $request->post();
        $_COOKIE = $request->cookies->all();

        ob_start();
        require base_path('legacy/public/index.php'); // ZF1 front controller
        $body = ob_get_clean();

        $status  = http_response_code() ?: 200;
        $headers = array_filter(headers_list(), fn ($h) => !str_starts_with($h, 'Set-Cookie'));

        return response($body, $status)->withHeaders($headers);
    }
}

Do not attempt to reuse Zend_Session here. Disable session handling on the ZF1 side and pass identity through a request attribute the ZF1 bootstrap reads.

Pattern 3 — Edge split (frequently the right answer). Route by path prefix at ingress so both apps are separate deployables:

location ~ ^/(legacy|crm|admin-v1)/ {
    fastcgi_pass legacy_php;
}
location / {
    fastcgi_pass modern_php;
}

You lose shared sessions; you gain independent deploys, independent scaling, and freedom from the dual-runtime failure class. This is a day of infra work and zero dependencies — often better than either PHP-side pattern.

Service bridging. Where you must share, expose narrow seams rather than letting the two containers touch:

  • Wrap the ZF1 bootstrap in a single factory service on your side, alias it to an interface, and inject that interface into your controllers. Never inject Zend_* types into modern code — a LegacyUserProvider returning your User value object, not a Zend_Auth_Storage.
  • Share the database at the connection level (one PDO/Zend_Db_Adapter factory), not by having each framework configure its own connection. Two connection pools against one database is the most common cause of "legacy data not visible" bugs.

Direction of delegation. The bundle assumes new-app-in-front, old-app-behind. If you go the other way (ZF1 dispatches into Symfony for migrated routes), you must invert the listener logic and be much more careful about response headers. Prefer new-in-front: it makes "routes moved" a count you can decrement.

Gotchas and Tips

Composer will not resolve it. The bundle pins symfony/symfony to a 2.x tree and a PHP constraint that predicates PHP 8. Any attempt to install it on Symfony 5/6/7 fails at dependency resolution, not at runtime — you get an error early, which is the one mercy here. Do not try to force the constraint.

Two autoloaders, one process. ZF1's autoloader and Composer's both register on spl_autoload_register. Prepend-ordering determines who wins on a class-name collision, and collisions are real (Zend_ namespaced code that also shipped via Composer). Register ZF1's autoloader with spl_autoload_register($cb, true, true) (prepend) only for the ZF1 namespace, and keep Composer authoritative for everything else. Never let ZF1's loader run unbounded over your modern namespace.

Two error handlers. Zend_Application's bootstrap installs its own error and exception handlers, which will swallow your modern error reporting and produce blank pages. In the ZF1 bootstrap, set Zend_Controller_Front::getInstance()->throwExceptions(true) and remove ZF1's registered error handler before dispatch.

Output buffering and headers. ZF1 writes headers directly via header() and sometimes Zend_Controller_Response does the same at send(). Wrap the dispatch in ob_start() / ob_get_clean() and capture headers from the Zend_Controller_Response object rather than trusting headers_list(). File downloads, streaming, and 302s fail here first — they're the classic symptom of header double-write.

Sessions. Two session abstractions in one process is where most incidents originate. Pick one owner — almost always the modern app — and have ZF1 read session values via a bootstrapped resource you inject. If you must let ZF1 start a session, disable Zend_Session in the modern side first, or you get double-write and silent logouts.

Container leakage. Do not register the Symfony/Laravel container into ZF1's Zend_Registry globally. It works, it's convenient, and it makes every dependency direction bidirectional — you lose the ability to know what the legacy side depends on, which is the one thing you need to measure to migrate. Inject specific services into the ZF1 bootstrap, named, one at a time.

Debugging a delegated request. Log on both sides of the seam with one correlation ID (a request header you set modern-side and ZF1's bootstrap logs). Without this, an exception in ZF1 appears modern-side as an empty 200 or a swallowed 500 and you cannot tell which runtime threw. The single biggest debugging tax in this architecture is attribution, not reproduction.

Extension points you'd want anyway. The bundle's services are ordinary Symfony service definitions — the pattern you should copy is aliasing its interfaces in your own services.yaml so you can swap the implementation for your own bridge without touching the call sites. Do this from the start in your reimplementation; it's the difference between a two-week migration and a two-year one.

The slow-burn failure. A wrapper that works becomes permanent. Write the governance rule — no new code on the ZF1 side, a named owner, a decommission date — before the first delegated route ships. Track routes delegated to legacy as a release metric. If it is not falling quarter over quarter, the bridge is maintenance, not migration.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log
hsluv/hsluv
seampact/codegen
seampact/hooks
seampact/ir
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table