You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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
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?
Are there any hidden risks in this approach (especially regarding buffering and streaming)?
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));
}
}
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
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 rawheader()calls, which makes lifecycle management difficult.For Large Outputs (StreamedResponse)
I use
StreamedResponsefor scripts that callreadfile()or produce large output. Before requiring the script, we explicitly clear output buffers using:For Small/Standard Outputs (Buffered Response)
We use
ob_start()to capture the output and then return a SymfonyResponse.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-Typefromheaders_list()to theResponseobject to prevent Symfony from overwriting it withtext/html.Questions
In the Legacy Route Loader pattern, what is the recommended way to handle scripts that inconsistently either call
exit;or return control after anecho?Are there any hidden risks in this approach (especially regarding buffering and streaming)?
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:
Beta Was this translation helpful? Give feedback.
All reactions