Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

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.