YourBOX Symfony Migration
Purpose
Section titled “Purpose”ADR-0002 commits YourBOX to a Symfony 8 strangler migration. This page documents the
target layout, how the legacy YourBOX/ tree continues to serve URLs through a Symfony
fallback bundle, the order in which routes are ported and the operational signals that
make a route safe to delete from the legacy tree.
Plan 003 landed the strangler scaffolding on modernization/baseline: the
Symfony 8 skeleton lives under app/, the App\Legacy\LegacyBundle is wired
into Kernel.php with priority: -1000, and a HealthController serves
/health for the smoke target. The migration table at the bottom of this
page reflects which downstream routes are still legacy.
Repository Layout
Section titled “Repository Layout”yourbox/├── app/ # Symfony 8 skeleton│ ├── bin/console│ ├── composer.json│ ├── config/│ │ ├── bundles.php│ │ ├── packages/│ │ ├── routes.yaml│ │ └── services.yaml│ ├── public/index.php # Front controller for everything│ ├── src/│ │ ├── Kernel.php│ │ ├── Catalog/ # Bounded context (ADR-0006)│ │ ├── Engagement/│ │ ├── Identity/│ │ ├── Moderation/│ │ └── Legacy/│ │ ├── LegacyBundle.php│ │ ├── LegacyController.php│ │ └── DependencyInjection/│ ├── templates/│ └── var/ # Symfony cache, gitignored├── YourBOX/ # 2020 PHP tree, frozen│ └── src/└── infra/The Symfony front controller at app/public/index.php handles every request. The nginx
configuration documented on the Verification Baseline page roots itself at
app/public/ and applies the standard Symfony fallback (try_files $uri $uri/ /index.php?$query_string;).
Legacy Fallback Bundle
Section titled “Legacy Fallback Bundle”App\Legacy\LegacyBundle registers two pieces:
- A controller
App\Legacy\LegacyControllermapped to the catch all route/{path}withpriority: -1000andrequirements: ['path' => '.+']. Any URL not claimed by another controller falls through to this one. - A configuration parameter
legacy.root_dirpointing at theYourBOX/directory.
The controller resolves the requested path against legacy.root_dir, refuses anything
outside it through a realpath check, streams static assets directly through
BinaryFileResponse, and includes PHP files inside an isolated static closure to keep
their globals from leaking back into the Symfony container.
#[Route('/{path}', name: 'legacy_fallback', requirements: ['path' => '.+'], priority: -1000)]public function __invoke(Request $request, string $path = ''): Response{ $candidate = $path === '' ? 'index.php' : $path; $real = realpath($this->legacyRoot . '/' . $candidate); if ($real === false || !str_starts_with($real, realpath($this->legacyRoot) . DIRECTORY_SEPARATOR)) { throw new NotFoundHttpException(); } if (preg_match('/\.(css|js|png|jpg|jpeg|gif|svg|mp4|webm|ico)$/i', $real)) { return new BinaryFileResponse($real); } if (str_ends_with($real, '.php')) { ob_start(); (static function (string $file) { require $file; })($real); return new Response((string) ob_get_clean()); } throw new NotFoundHttpException();}Migration Order
Section titled “Migration Order”Routes migrate in the order below. Each step lands as an independent merge request that ships and reviews without coupling to the next one.
- Health and observability endpoints.
/healthand/metricsare new Symfony routes that do not exist in the legacy tree. - Secret loading. ADR-0008. The Symfony kernel validates the env contract at boot.
db.phpandfunctions.phpswitch to theEnvhelper. - Authentication. ADR-0004. The OIDC and form firewalls replace
login_registration.php,login.php,registration.php,confirm.php,forget.php,modify_password.phpandreset.php. - Video upload and playback. ADR-0005. The Symfony
Catalogcontext rewritesupload_video.php,form_upload_video.php,video_page.phpandplayer.phpon top of Flysystem. - Comments and votes. ADR-0006. The
Engagementcontext rewritescomments/views/posts/view.php,like.phpand the inline JavaScript that today builds reply markup inside PHP heredoc strings. - Search. ADR-0003. The
Catalogcontext exposes a search endpoint backed by Meilisearch.searching/answer_request.phpand thecache/searchings/HTML cache are removed. - Profile and account management. The
Identitycontext ownsmodify_profile.phpandmanagement_file.php. - Admin panel. The Admin Panel page documents the EasyAdmin layout that replaces
accounts/admin/post/index.php,edit.phpanddelete.php.
The Symfony Migration runbook (strangle-a-legacy-route) describes the per route
procedure with the verification gates.
Route Porting Procedure
Section titled “Route Porting Procedure”-
Inventory the legacy route. Read the legacy PHP file, list its inputs (query string, POST fields, cookies, session keys), outputs (rendered HTML, redirects, flash messages), database queries and external side effects.
-
Pick the bounded context. Each route belongs to one of
Catalog,Engagement,IdentityorModerationper ADR-0006. Place new code underapp/src/<Context>/. -
Write the domain code. Define value objects, the command or query, the handler, and the repository port. Cover invariants with unit tests that boot nothing.
-
Write the controller. Thin controller in
app/src/<Context>/UserInterface/Controller/. Dispatch the command or query through Symfony Messenger. Render a Twig template underapp/templates/<context>/. -
Add a functional test.
WebTestCaseexercising the happy path and the security path (CSRF rejected, auth required, role check). The test lives underapp/tests/Functional/<Context>/. -
Verify in CI.
composer testpasses including the new tests, hadolint and Trivy stay green, the Plumber controls pass. -
Delete the legacy file. Once the Symfony route covers the URL, delete the matching PHP file from
YourBOX/. Search the legacy tree for any cross reference (grep -rn 'YourBOX/src/<old path>') and patch the few remainingrequire_oncechains. -
Update the migration table on this page with the new status row.
Migration Status Table
Section titled “Migration Status Table”The status table reflects which legacy routes still exist in YourBOX/ and which are
fully rewritten in app/. Update the row at the end of each port.
| Bounded context | Route group | Legacy files | Status |
|---|---|---|---|
| Platform | /health, /metrics |
(none) | Done |
| Identity | OIDC login, profile | login_registration.php, login.php, modify_profile.php |
Planned |
| Identity | Registration, reset | registration.php, confirm.php, forget.php, reset.php |
Planned |
| Catalog | Upload, playback | form_upload_video.php, upload_video.php, video_page.php |
Planned |
| Catalog | Search | searching/answer_request.php |
Planned |
| Engagement | Comments, votes | comments/views/posts/view.php, like.php |
Planned |
| Moderation | Admin | accounts/admin/post/index.php, edit.php, delete.php |
Planned |
Boundary Rules
Section titled “Boundary Rules”- No new file lands under
YourBOX/src/. The legacy tree only shrinks. - No
require_oncestatement is added inapp/src/. The autoloader handles imports. - No Symfony import lands under
Domain/orApplication/in any bounded context. PHPStan rules enforce this through a baseline. - The fallback controller cannot serve
prod.env,prod.env.enc, thecomposer.lockor any file outsidelegacy.root_dir. The functional testLegacyControllerTest::testTraversalIsRejectedguards this.
Risks During Transition
Section titled “Risks During Transition”- A legacy file relies on a global initialized by an earlier
require_oncechain. When the URL is served through the fallback controller, that chain still executes because the included file is the same as in 2020. - Two requests in the same worker can collide on
$_SESSION['flash']if Symfony also uses the flash bag. The Symfony session cookie name is changed toyourbox_sessto avoid collisions with the legacyremembercookie until the auth migration completes. - Static asset URLs in legacy CSS files reference paths relative to
YourBOX/. The Symfony route falls through and serves them, so nothing breaks until the page is rewritten.