Skip to content

Instantly share code, notes, and snippets.

@fritzmg
Last active April 19, 2025 16:10
Show Gist options
  • Save fritzmg/77755f3dfbe6366ae047295cc48a7a9f to your computer and use it in GitHub Desktop.
Save fritzmg/77755f3dfbe6366ae047295cc48a7a9f to your computer and use it in GitHub Desktop.
There is a curious circular dependency issue in Contao 4.13 when you try to decorate (or override) the `router` with your own `ChainRouter`. The first built container example shows the `getRouterService()` method without the decoration, the second with.
<?php
/**
* Here we have
* $this->services['router'] = $instance = new \Symfony\Cmf\Component\Routing\ChainRouter($a);
* almost right at the start, defining the `router` service very early.
*/
protected function getRouterService()
{
$a = ($this->privates['monolog.logger'] ?? $this->getMonolog_LoggerService());
$this->services['router'] = $instance = new \Symfony\Cmf\Component\Routing\ChainRouter($a);
$b = ($this->privates['router.request_context'] ?? $this->getRouter_RequestContextService());
$c = new \Symfony\Bridge\Monolog\Logger('router');
$c->pushProcessor(($this->privates['contao.monolog.processor'] ?? $this->getContao_Monolog_ProcessorService()));
$c->pushHandler(($this->privates['monolog.handler.console'] ?? $this->getMonolog_Handler_ConsoleService()));
$c->pushHandler(($this->privates['monolog.handler.main'] ?? $this->getMonolog_Handler_MainService()));
$c->pushHandler(($this->privates['contao.monolog.handler'] ?? $this->getContao_Monolog_HandlerService()));
$d = new \Symfony\Bundle\FrameworkBundle\Routing\Router((new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($this->getService, [
'routing.loader' => ['services', 'routing.loader', 'getRouting_LoaderService', true],
], [
'routing.loader' => 'Symfony\\Component\\Config\\Loader\\LoaderInterface',
]))->withContext('router.default', $this), 'contao_manager.routing.route_loader::loadFromPlugins', ['cache_dir' => $this->targetDir.'', 'debug' => false, 'generator_class' => 'Symfony\\Component\\Routing\\Generator\\CompiledUrlGenerator', 'generator_dumper_class' => 'Symfony\\Component\\Routing\\Generator\\Dumper\\CompiledUrlGeneratorDumper', 'matcher_class' => 'Symfony\\Bundle\\FrameworkBundle\\Routing\\RedirectableCompiledUrlMatcher', 'matcher_dumper_class' => 'Symfony\\Component\\Routing\\Matcher\\Dumper\\CompiledUrlMatcherDumper', 'strict_requirements' => false, 'resource_type' => 'service'], $b, ($this->privates['parameter_bag'] ?? ($this->privates['parameter_bag'] = new \Symfony\Component\DependencyInjection\ParameterBag\ContainerBag($this))), $c, 'en');
$d->setConfigCacheFactory(($this->privates['config_cache_factory'] ?? ($this->privates['config_cache_factory'] = new \Symfony\Component\Config\ResourceCheckerConfigCacheFactory())));
$d->addExpressionLanguageProvider(($this->privates['router.expression_language_provider'] ?? $this->getRouter_ExpressionLanguageProviderService()));
$e = ($this->privates['contao.routing.route_provider'] ?? $this->getContao_Routing_RouteProviderService());
$f = new \Contao\CoreBundle\Routing\PageUrlGenerator($e, ($this->services['contao.routing.page_registry'] ?? $this->getContao_Routing_PageRegistryService()), $a);
$g = ($this->services['event_dispatcher'] ?? $this->getEventDispatcherService());
$h = new \Symfony\Cmf\Component\Routing\DynamicRouter($b, ($this->services['contao.routing.nested_matcher'] ?? $this->getContao_Routing_NestedMatcherService()), $f, '', $g, $e);
$h->addRouteEnhancer(new \Contao\CoreBundle\Routing\Enhancer\InputEnhancer(($this->services['contao.framework'] ?? $this->getContao_FrameworkService())), 0);
$instance->setContext($b);
$instance->add($d, '100');
$instance->add(new \Symfony\Cmf\Component\Routing\DynamicRouter($b, ($this->services['contao.routing.nested_404_matcher'] ?? $this->getContao_Routing_Nested404MatcherService()), $f, '', $g, ($this->privates['contao.routing.route_404_provider'] ?? $this->getContao_Routing_Route404ProviderService())), -200);
$instance->add($h, 20);
return $instance;
}
<?php
/**
* Here we have
* $this->services['router'] = $instance = new \App\Routing\ChainRouter($h);
* much later, which causes a recursion between `@contao.framework` and `@contao.routing.route_provider`.
* `@contao.framework` tries to get the `@router`, `getRouterService()` tries to get `@contao.routing.route_provider`
* wich then tries to get `@contao.framework`, which then tries to get the `@router` etc.
*/
protected function getRouterService()
{
$a = ($this->privates['contao.routing.route_provider'] ?? $this->getContao_Routing_RouteProviderService());
if (isset($this->services['router'])) {
return $this->services['router'];
}
$b = ($this->services['contao.routing.page_registry'] ?? $this->getContao_Routing_PageRegistryService());
if (isset($this->services['router'])) {
return $this->services['router'];
}
$c = ($this->services['event_dispatcher'] ?? $this->getEventDispatcherService());
if (isset($this->services['router'])) {
return $this->services['router'];
}
$d = ($this->privates['contao.routing.route_404_provider'] ?? $this->getContao_Routing_Route404ProviderService());
if (isset($this->services['router'])) {
return $this->services['router'];
}
$e = ($this->services['contao.routing.nested_matcher'] ?? $this->getContao_Routing_NestedMatcherService());
if (isset($this->services['router'])) {
return $this->services['router'];
}
$f = ($this->services['contao.framework'] ?? $this->getContao_FrameworkService());
if (isset($this->services['router'])) {
return $this->services['router'];
}
$g = ($this->privates['monolog.logger'] ?? $this->getMonolog_LoggerService());
$h = new \Symfony\Cmf\Component\Routing\ChainRouter($g);
$i = ($this->privates['router.request_context'] ?? $this->getRouter_RequestContextService());
$j = new \Symfony\Bridge\Monolog\Logger('router');
$j->pushProcessor(($this->privates['contao.monolog.processor'] ?? $this->getContao_Monolog_ProcessorService()));
$j->pushHandler(($this->privates['monolog.handler.console'] ?? $this->getMonolog_Handler_ConsoleService()));
$j->pushHandler(($this->privates['monolog.handler.main'] ?? $this->getMonolog_Handler_MainService()));
$j->pushHandler(($this->privates['contao.monolog.handler'] ?? $this->getContao_Monolog_HandlerService()));
$k = new \Symfony\Bundle\FrameworkBundle\Routing\Router((new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($this->getService, [
'routing.loader' => ['services', 'routing.loader', 'getRouting_LoaderService', true],
], [
'routing.loader' => 'Symfony\\Component\\Config\\Loader\\LoaderInterface',
]))->withContext('router.default', $this), 'contao_manager.routing.route_loader::loadFromPlugins', ['cache_dir' => $this->targetDir.'', 'debug' => false, 'generator_class' => 'Symfony\\Component\\Routing\\Generator\\CompiledUrlGenerator', 'generator_dumper_class' => 'Symfony\\Component\\Routing\\Generator\\Dumper\\CompiledUrlGeneratorDumper', 'matcher_class' => 'Symfony\\Bundle\\FrameworkBundle\\Routing\\RedirectableCompiledUrlMatcher', 'matcher_dumper_class' => 'Symfony\\Component\\Routing\\Matcher\\Dumper\\CompiledUrlMatcherDumper', 'strict_requirements' => false, 'resource_type' => 'service'], $i, ($this->privates['parameter_bag'] ?? ($this->privates['parameter_bag'] = new \Symfony\Component\DependencyInjection\ParameterBag\ContainerBag($this))), $j, 'en');
$k->setConfigCacheFactory(($this->privates['config_cache_factory'] ?? ($this->privates['config_cache_factory'] = new \Symfony\Component\Config\ResourceCheckerConfigCacheFactory())));
$k->addExpressionLanguageProvider(($this->privates['router.expression_language_provider'] ?? $this->getRouter_ExpressionLanguageProviderService()));
$this->services['router'] = $instance = new \App\Routing\ChainRouter($h);
$l = new \Contao\CoreBundle\Routing\PageUrlGenerator($a, $b, $g);
$m = new \Symfony\Cmf\Component\Routing\DynamicRouter($i, $e, $l, '', $c, $a);
$m->addRouteEnhancer(new \Contao\CoreBundle\Routing\Enhancer\InputEnhancer($f), 0);
$h->setContext($i);
$h->add($k, '100');
$h->add(new \Symfony\Cmf\Component\Routing\DynamicRouter($i, ($this->services['contao.routing.nested_404_matcher'] ?? $this->getContao_Routing_Nested404MatcherService()), $l, '', $c, $d), -200);
$h->add($m, 20);
return $instance;
}
<?php
namespace App\Routing;
use Symfony\Cmf\Component\Routing\ChainRouterInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
use Symfony\Component\Routing\RequestContext;
class ChainRouter implements ChainRouterInterface, WarmableInterface
{
public function __construct(private readonly ChainRouterInterface&WarmableInterface $inner)
{
}
public function add($router, $priority = 0): void
{
$this->inner->add($router, $priority);
}
public function all()
{
return $this->inner->all();
}
public function warmUp(string $cacheDir, string|null $buildDir = null): void
{
$this->inner->warmUp($cacheDir);
}
public function getRouteCollection()
{
return $this->inner->getRouteCollection();
}
public function match(string $pathinfo)
{
return $this->inner->match($pathinfo);
}
public function matchRequest(Request $request)
{
return $this->inner->matchRequest($request);
}
public function generate(string $name, array $parameters = [], int $referenceType = self::ABSOLUTE_PATH): string
{
return $this->inner->generate($name, $parameters, $referenceType);
}
public function setContext(RequestContext $context): void
{
$this->inner->setContext($context);
}
public function getContext(): RequestContext
{
return $this->inner->getContext();
}
}
services:
App\ChainRouter:
decorates: cmf_routing.router
arguments: ['@.inner']
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment