Skip to content

Navigation Menu

Sign in
Appearance settings
Sign up
Appearance settings
Discussion options

Hi,

I am following the Symfony Migration Guide (Legacy Route Loader pattern) to wrap a large legacy PHP application. We use a custom RouteLoader to map legacy file paths to a single LegacyController.
https://symfony.com/doc/current/migration.html

My Implementation

I categorize legacy scripts based on expected output size. However, both types of scripts may include exit;, flush(), or raw header() calls, which makes lifecycle management difficult.

For Large Outputs (StreamedResponse)

I use StreamedResponse for scripts that call readfile() or produce large output. Before requiring the script, we explicitly clear output buffers using:

while (ob_get_level()) { ob_end_flush(); }
flush();

For Small/Standard Outputs (Buffered Response)

We use ob_start() to capture the output and then return a Symfony Response.

Key Constraints

Legacy Session Management

Legacy scripts always manage their own sessions via session_start(). I need to ensure this does not interfere with Symfony's infrastructure during the migration.

Header Synchronization

For scripts that return control to the Controller, we manually synchronize the Content-Type from headers_list() to the Response object to prevent Symfony from overwriting it with text/html.

Questions

  1. In the Legacy Route Loader pattern, what is the recommended way to handle scripts that inconsistently either call exit; or return control after an echo?

  2. Are there any hidden risks in this approach (especially regarding buffering and streaming)?

  3. Are there any known side effects when session_start() is called within the scope of a Symfony Controller, assuming the legacy code fully manages the session?

Thank you.

Here is my LegacyController implementation:

<?php

namespace App\Controller;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;

class LegacyController
{
    private const STREAMED_SCRIPT_LIST = [
        'download.php',
    ];

    /**
     * @param string $requestPath
     * @param string $legacyScript
     * @return Response
     */
    public function loadLegacyScript(string $requestPath, string $legacyScript): Response
    {
        $scriptName = basename($legacyScript);

        // 1. For scripts handling large files, use StreamedResponse (Memory Protection)
        if (in_array($scriptName, self::STREAMED_SCRIPT_LIST, true)) {
            return new StreamedResponse(function () use ($requestPath, $legacyScript) {
                $this->prepareLegacyEnvironment($requestPath, $legacyScript);
                require $legacyScript; // Some of these scripts call exit;
            });
        }

        // 2. For standard APIs (including small file downloads)
        $this->prepareLegacyEnvironment($requestPath, $legacyScript);

        // Start output buffering to capture content and status codes
        ob_start();

        require $legacyScript;

        // If exit; was called inside the script, the following lines are NOT executed.
        $content = ob_get_clean();

        // Capture the status code set by the legacy script (default 200)
        $statusCode = http_response_code() ?: 200;

        $response = new Response($content, $statusCode);

        // Synchronize Content-Type from raw header() calls back to Symfony Response
        // to prevent Symfony from overwriting it with the default text/html.
        foreach (headers_list() as $h) {
            if (stripos($h, 'Content-Type:') === 0) {
                [, $value] = explode(':', $h, 2);
                $response->headers->set('Content-Type', trim($value));
            }
        }

        return $response;
    }

    /**
     * Setup environment variables and working directory for legacy compatibility.
     */
    private function prepareLegacyEnvironment(string $requestPath, string $legacyScript): void
    {
        $_SERVER['PHP_SELF'] = $requestPath;
        $_SERVER['SCRIPT_NAME'] = $requestPath;
        $_SERVER['SCRIPT_FILENAME'] = $legacyScript;

        // Resolve relative paths inside legacy scripts
        chdir(dirname($legacyScript));
    }
}
You must be logged in to vote

Replies: 0 comments

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
🙏
Q&A
Labels
None yet
1 participant
Morty Proxy This is a proxified and sanitized view of the page, visit original site.