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

Legacy Wrapper Bundle Laravel Package

theodo-evolution/legacy-wrapper-bundle

Theodo Evolution's Symfony bundle for wrapping legacy PHP applications and migrating them incrementally. It routes legacy requests through Symfony, bridges sessions, services, and authentication, and lets old and new code coexist so teams can modernize without a risky big-bang rewrite.

View on GitHub
Deep Wiki
Context7

Getting Started

The bundle's job is to let a Symfony app invoke a legacy PHP application (or individual legacy scripts) behind a Symfony route, capture its output, and hand it back as a proper Response. That makes it the seam you build a strangler-pattern migration around: new routes live in Symfony, everything not yet migrated falls through to the legacy code.

  1. Install: composer require theodo-evolution/legacy-wrapper-bundle (this is a 2018-era bundle — expect Symfony 2.x/3.x constraints, not Flex/Symfony 4+).
  2. Register it the old way in app/AppKernel.php:
    new TheodoEvolution\LegacyWrapperBundle\TheodoEvolutionLegacyWrapperBundle(),
    
    Then check app/config/config.yml for whatever legacy_wrapper: block the bundle exposes — read the bundle's DependencyInjection/Configuration.php first, it is the authoritative list of options (legacy root path, session sharing, etc.).
  3. Read these first, in order: README.md → Resources/config/services.xml (what service IDs you actually get) → the wrapper/controller class. The service definitions tell you more than the docs about how the legacy include is wired.
  4. First use case: take one old URL you have no intention of rewriting soon, e.g. /admin/reports, add a Symfony route for it, and have the controller delegate to the legacy script through the wrapper. Verify the HTML comes back byte-identical to what Apache served before you touch anything else.

The mental model to hold: the wrapper is an adapter, not a reimplementation. Symfony owns routing, DI, and the kernel; the legacy app still owns its own bootstrap, DB access, session, and templates.

Implementation Patterns

Progressive migration with a catch-all. Register your new, Symfony-native routes first and a low-priority catch-all route last that forwards to the legacy front controller. New work goes into Symfony controllers; old paths keep hitting legacy untouched. Migrate a page by deleting its fall-through, not by rewriting the wrapper.

Wrap the front controller, not every script. The lowest-friction integration is to let the legacy app's own index.php run inside the wrapper (with its bootstrap intact) rather than including dozens of leaf scripts. You avoid re-creating the legacy router; you only need to translate the incoming path and superglobals correctly.

Adapt the Symfony Request into legacy superglobals. Most legacy apps read $_GET, $_POST, $_SERVER['REQUEST_URI'], $_SERVER['SCRIPT_NAME']. Populate these from the Symfony request before the include, in the controller or a small adapter service:

$_GET     = $request->query->all();
$_POST    = $request->request->all();
$_SERVER['REQUEST_URI']    = $request->getRequestUri();
$_SERVER['REQUEST_METHOD'] = $request->getMethod();

Turn buffered output into a Response. Wrap the include in ob_start() / ob_get_clean() and build a Symfony Response from the buffer, inheriting status and headers where the bundle exposes them. Keep a thin custom controller that returns the wrapper's result so you control the response type (HTML vs. redirect vs. download).

Share the session deliberately. If both apps need login state, decide once whether Symfony owns the session and legacy reads it, or vice versa. A common pattern is to let the legacy app keep managing its session and have Symfony's security layer read the same PHP session — but only if both use identical cookie name, domain, and save path.

Reuse infrastructure where it's safe. Pointing legacy DB config at the same connection parameters as Symfony's Doctrine config is fine; trying to share the live PDO connection is usually not worth the coupling. Keep the two bootstraps separate and let each open its own connection.

Use kernel subrequests for anything nested. If a legacy page emits links back into Symfony-owned pages, or if you want to compose a Symfony fragment inside a legacy layout, do it via HttpKernel::handle() with a subrequest rather than including files across the boundary.

Write a characterization test around each wrapped route before you migrate it. A Symfony functional test that hits the legacy route and snapshots key content gives you a safety net that survives the eventual rewrite.

Gotchas and Tips

  • exit/die in legacy code bypasses everything. The wrapper cannot return a Response if the legacy script terminates the process. Grep the legacy tree for exit, die, header(, and setcookie( early. A register_shutdown_function that flushes the output buffer is a reasonable last resort.
  • Headers already sent. Because output is buffered, most header issues are masked — but any legacy code that calls headers_sent() or relies on header() having taken effect will behave differently under the wrapper than under Apache. Test redirects specifically; a legacy header('Location: …') inside a buffer must be translated into a Symfony RedirectResponse, not echoed.
  • Superglobals must be set before the include. Setting $_SERVER after the legacy bootstrap runs is too late; the legacy router has already made decisions. Do the adaptation in one place at the top of the wrapped call.
  • Session conflicts. Two session_start() calls, differing cookie paths, or the Symfony session handler writing over the legacy one produce silent, deeply confusing "user randomly logged out" bugs. Verify with session_id() on both sides that you're talking about the same session.
  • Autoloader and symbol collisions. The legacy app probably has its own autoloader and global classes/functions. Loading it inside the Symfony process means class_exists collisions and duplicate requires. Namespace-aware includes or isolated bootstrapping are safer than letting both autoloaders run over the same tree.
  • Relative paths and chdir. Legacy scripts assume the working directory is the legacy root. If includes fail with "file not found", it is almost always getcwd(). Wrap the include with chdir($legacyRoot) and restore afterwards.
  • Fatal errors become 500s with no trace. Set a legacy error handler that converts errors into exceptions (or at least logs with context) so failures surface in var/log instead of a blank page. Disable the Symfony profiler for legacy routes — it will intercept and mangle output.
  • This package is old and effectively unmaintained (last release 2018, zero dependents). Pin your Symfony version, vendor the bundle or fork it if you need changes, and budget for replacing the wrapper with a hand-written controller once the legacy surface shrinks. The wrapper's public API is thin — it's mostly a service around include + output buffering — so it's realistic to own it.
  • Extension points are limited, which is actually useful: the main ones are the wrapper service (decorate it), the controller that returns the response, and the request/superglobal adapter. Put your project-specific logic in your own controller extending or calling the wrapper, not in the vendor bundle.
  • Security is entirely the legacy app's problem. The wrapper does no CSRF, no escaping, no auth. Don't let its presence in the Symfony app create a false sense of a shared security boundary — audit the legacy routes as their own application.
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.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums