7

I want to decorate the Symfony UrlGenerator class.

Symfony\Component\Routing\Generator\UrlGenerator: ~

my.url_generator:
    class: AppBundle\Service\UrlGenerator
    decorates: Symfony\Component\Routing\Generator\UrlGenerator
    arguments: ['@my.url_generator.inner']
    public:    false

I've added this to the services.yml but my AppBundle\Service\UrlGenerator class is ignored:

I tried the following configuration again.

config/services.yaml

parameters:
    locale: 'en'
    router.options.generator_class: AppBundle\Service\UrlGenerator
    router.options.generator_base_class: AppBundle\Service\UrlGenerator

Still it doesn't work

How to decorate the UrlGenerator in Symfony 4.2?

barbsan
  • 3,418
  • 11
  • 21
  • 28
wuxueling
  • 81
  • 3

3 Answers3

6

The right answer is : you shouldn't decorate UrlGeneratorInterface. You have to decorate 'router' service. Check here : https://github.com/symfony/symfony/issues/28663

** services.yml :

services:
    App\Services\MyRouter:
        decorates: 'router'
        arguments: ['@App\Services\MyRouter.inner']

** MyRouter.php :

<?php

namespace App\Services;

use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\RouterInterface;

class MyRouter implements RouterInterface
{
    /**
     * @var RouterInterface
     */
    private $router;

    /**
     * MyRouter constructor.
     * @param RouterInterface $router
     */
    public function __construct(RouterInterface $router)
    {
        $this->router = $router;
    }

    /**
     * @inheritdoc
     */
    public function generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH)
    {
        // Your code here

        return $this->router->generate($name, $parameters, $referenceType);
    }

    /**
     * @inheritdoc
     */
    public function setContext(RequestContext $context)
    {
        $this->router->setContext($context);
    }

    /**
     * @inheritdoc
     */
    public function getContext()
    {
        return $this->router->getContext();
    }

    /**
     * @inheritdoc
     */
    public function getRouteCollection()
    {
        return $this->router->getRouteCollection();
    }

    /**
     * @inheritdoc
     */
    public function match($pathinfo)
    {
        return $this->router->match($pathinfo);
    }
}
acantepie
  • 329
  • 1
  • 3
  • 9
  • Doesn't work `Argument #1 ($routeGenerator) must be of type Sonata\AdminBundle\Route\RouteGeneratorInterface, Symfony\Cmf\Component\Routing\ChainRouter given` – ManBehindTheCurtain Jul 09 '23 at 14:42
3

I believe the issue is that UrlGenerator service name is Symfony\Component\Routing\Generator\UrlGeneratorInterface, and not Symfony\Component\Routing\Generator\UrlGenerator (cf. this code).

Secondly, when you decorate a service, the decorator will take the service name. So you should not need to modify router.options.generator_class.

Try with this configuration:

my.url_generator:
    class: AppBundle\Service\UrlGenerator
    decorates: Symfony\Component\Routing\Generator\UrlGeneratorInterface
    arguments: ['@my.url_generator.inner']

Setting public to false is likely not needed, as on Symfony4/Flex it should be the default value.

Update for comments: decorated service may look like this:

class MyUrlGenerator implements UrlGeneratorInterface
{
    private $originalUrlGenerator;

    public function __construct(UrlGeneratorInterface $innerUrlGenerator)
    {
        $this->originalUrlGenerator = $innerUrlGenerator;
    }

    public function generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH)
    {
        // Maybe add your custom logic here... 
        // or completely override base method

        return $this->originalUrlGenerator->generate($name, $parameters, $referenceType);
    }
}
romaricdrigon
  • 1,497
  • 12
  • 16
  • 1
    Does this mean the service I defined must be loaded after the "Symfony\Component\Routing\Generator\UrlGeneratorInterface"? Also, does decorating allowing me to override just 1 specific class method? Or should I try to replace the service/class instead? – mr1031011 Aug 23 '19 at 11:56
  • I would also like to know the answer to these exact questions – code_gamer Sep 30 '19 at 15:57
  • 1
    UrlGeneratorInterface is loaded by Symfony, you service will likely always be defined after. About decoration, it is a different mechanism where you will replace the service by your own, but you can pass method to the "inner" service. I will update my answer with a code example, as I can't post it in comments. – romaricdrigon Oct 01 '19 at 09:03
1

I believe you have to decorate the Symfony\Component\Routing\Generator\UrlGeneratorInterface, because the services should depend on the interface and not on a specific implementation (class).

Jakumi
  • 8,043
  • 2
  • 15
  • 32