Skip to content

Add AdminContext::overrideTemplate() - #7894

Open
phcorp wants to merge 1 commit into
EasyCorp:5.xfrom
phcorp:override_template_at_runtime
Open

phcorp wants to merge 1 commit into
EasyCorp:5.xfrom
phcorp:override_template_at_runtime

Conversation

@phcorp

@phcorp phcorp commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #7897

Problem

Templates are configured with Crud::overrideTemplate() in configureCrud() of the dashboard or of a CRUD controller. AdminContextFactory copies them into the TemplateRegistry of the I18nContext when it builds the admin context, and after that there's no supported way to change a template.

Some code needs to pick a template from the current request and for any CRUD controller, so it can't do it in configureCrud(). One example is an event listener that renders pages without the main menu when they're loaded inside a modal dialog. Such code has to read AdminContext::$i18nContext and I18nContext::$templateRegistry with reflection to call setTemplate().

Fix

Add AdminContext::overrideTemplate(string $templateName, string $templatePath), which delegates to a new I18nContext::overrideTemplate(). It changes a template for the rest of the current request. The template name is validated by TemplateRegistry::setTemplate(), the same way as with Crud::overrideTemplate().

public function useDialogLayout(BeforeCrudActionEvent $event): void
{
    $context = $event->getAdminContext();
    if (!$context?->getRequest()->headers->has('X-Dialog')) {
        return;
    }

    $context->overrideTemplate('layout', 'admin/dialog_layout.html.twig');
}

The method is declared with @method in AdminContextInterface, the same way as getDashboardComponentDefaults(), so existing implementations of the interface don't break. doc/design.rst documents it after the overrideTemplate() / overrideTemplates() examples.

Alternatives considered:

  • Expose the TemplateRegistry: this leaks an internal class through the public API.
  • Dispatch an event in CrudResponseListener that lets listeners override templates: it only covers the responses of CRUD actions. Templates are also resolved with ea.templatePath() in dashboards and custom pages, and it would add an event class plus a dispatch for a single use.
  • Add a separate map of runtime overrides consulted by I18nContext::getTemplatePath(): this duplicates what the per-request registry already does.

Tests

  • Unit (I18nContextTest, AdminContextTest): the override changes the resolved path, leaves the other templates unchanged, and rejects an unknown template name.
  • Functional (OverrideTemplateAtRuntimeTest, CustomizationApp): a test event subscriber on BeforeCrudActionEvent overrides layout when the request has an X-Override-Layout header.
    • The index page then renders the overriding layout (body[data-override-layout]) with its rows.
    • A later request without the header renders the default layout.

Results:

  • make tests ARGS="tests/Unit/Context tests/Functional/Customization/Crud/Templates": OK (20 tests)
  • make tests-parallel: OK (3691 tests)
  • phpstan, php-cs-fixer and twig-cs-fixer (new template): no errors

@phcorp
phcorp force-pushed the override_template_at_runtime branch 2 times, most recently from 4dd7aea to 01d2332 Compare November 7, 2026 02:06
Templates are configured with Crud::overrideTemplate() in configureCrud()
of the dashboard or of the CRUD controller, and AdminContextFactory copies
them into the TemplateRegistry of the I18nContext when it builds the admin
context. After that, there's no way to change a template, so code that
decides the template from the current request (e.g. an event listener
that renders a page without the main menu when it's loaded inside a modal
dialog, for any CRUD controller) has to change the private registry with
reflection.

AdminContext::overrideTemplate() (and I18nContext::overrideTemplate())
change a template for the rest of the current request. The template name
is validated by TemplateRegistry::setTemplate(), as with Crud. The method
is declared with @method in AdminContextInterface, as done for
getDashboardComponentDefaults(), to not break its implementations.

The alternatives were discarded because they are larger and less useful:
exposing the TemplateRegistry leaks an internal class; dispatching an
event in CrudResponseListener only covers the responses of CRUD actions,
while templates are also resolved by ea.templatePath() in dashboards and
custom pages; and a read-only map of runtime overrides would duplicate
what the registry already does for the request.
@phcorp
phcorp force-pushed the override_template_at_runtime branch from 01d2332 to 9840cfe Compare November 8, 2026 19:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Override a template at runtime, for the current request

1 participant