Skip to content

Strangle A YourBOX Legacy Route

Use this runbook every time you port a legacy YourBOX route from the 2020 YourBOX/src/ tree to the Symfony application under app/src/. The procedure is the same for every route: inventory, write the domain code, write the controller, write the tests, verify, delete.

  • The verification baseline is in place (PHP 8.5, tests runnable, hadolint and Trivy gates green).
  • The Symfony strangler skeleton lives at app/ with the LegacyBundle fallback active.
  • The bounded context targeted by the route is set up at app/src/<Context>/ per ADR-0006.
  1. Inventory the legacy route.

    Terminal window
    grep -rn "<legacy_php_file_basename>" YourBOX/

    Expected result: every reference is listed. Note inputs (query string, POST fields, cookies, session keys), outputs (HTML, redirects, flash messages), database queries and any external side effect.

  2. Confirm the bounded context.

    The video upload route belongs to Catalog, the comment posting to Engagement, the profile editing to Identity, the admin to Moderation. Place the new code under app/src/<Context>/.

  3. Write the domain code.

    • Add the entity or value object to app/src/<Context>/Domain/.
    • Add the command (UploadVideoCommand) and the handler (UploadVideoHandler) to app/src/<Context>/Application/Command/.
    • Declare the outbound port in app/src/<Context>/Application/Port/.

    Verify:

    Terminal window
    composer test:unit --working-dir=app --filter=<Command>HandlerTest

    Expected result: the unit tests for the handler pass.

  4. Write the adapter.

    Add the Doctrine repository or the Flysystem adapter under app/src/<Context>/Infrastructure/. Wire it in services.yaml.

    Verify:

    Terminal window
    composer test:integration --working-dir=app \
    --filter=<Adapter>Test

    Expected result: the integration tests pass against the local PostgreSQL and MinIO containers from docker compose.

  5. Write the controller.

    Add the controller under app/src/<Context>/UserInterface/Controller/. The controller parses the request, dispatches the command through Symfony Messenger, renders the Twig template under app/templates/<context>/<action>.html.twig.

  6. Write the functional test.

    Add the WebTestCase under app/tests/Functional/<Context>/. Cover the happy path and the security path (CSRF rejected, auth required, role check).

    Verify:

    Terminal window
    composer test:functional --working-dir=app \
    --filter=<Controller>Test

    Expected result: both tests pass.

  7. Verify the strangler fallback no longer serves the route.

    Terminal window
    curl --silent --output /dev/null --write-out "%{http_code}\n" \
    http://127.0.0.1:18080/<legacy_path>

    Expected result: HTTP 200 with the new template, not the legacy HTML.

    Compare the new HTML against the legacy HTML snapshot:

    Terminal window
    diff <(curl --silent http://127.0.0.1:18080/<legacy_path>) \
    tests/fixtures/legacy/<legacy_path>.html

    Expected result: only intentional differences (Tailwind classes, improved markup) appear.

  8. Delete the legacy file.

    Terminal window
    git rm YourBOX/src/<path_to_legacy_file>

    Search for remaining cross references:

    Terminal window
    grep -rn "<legacy_php_file_basename>" YourBOX/

    Expected result: no result. Patch any remaining require_once chain if the search returns results.

  9. Update the migration status table on the Symfony Migration page.

  10. Open the merge request.

    The CI pipeline runs lint, test, scan, smoke, and the corresponding Plumber controls. Once green, request a review.

  • composer test --working-dir=app exits 0 across unit, integration and functional suites.
  • hadolint Dockerfile, trivy image, bash tests/smoke/dockerfile-smoke.sh exit 0.
  • Plumber score stays at 100.
  • The Grafana panel YourBOX > Overview > Error Rate stays at baseline for the route’s class during the canary window.

If the new controller does not claim the URL (the legacy fallback still fires), inspect the Symfony route list:

Terminal window
php app/bin/console debug:router | grep <legacy_path>

Expected result: the new route appears with priority higher than the legacy_fallback route’s -1000.

If a domain test cannot be written without booting Symfony, the domain code is leaking the framework. Inspect the port interface in Application/ and refactor so the test runs with stubs.

Escalate to the maintainer when:

  • The legacy route depends on a global initialized by a chain of require_once calls that does not fit the bounded context.
  • The HTML snapshot diff reveals an unintentional behavior change.
  • The migration would require touching another bounded context’s domain code. Open a design discussion instead of forcing the cross context call.