Search code examples
phpsymfonysymfony7

How to Automatically Add ID Parameter to Route Generation in Symfony 7?


In my Symfony 7 project, I aim to automatically add the ID parameter to route generation if it is present in the current request parameters, whether generating the route via Twig or from a controller.

Example: To generate a URL for the route {Id}/admin (name: admin) from the route {Id}/home (name: home), I would only need to provide the route name 'admin' and my code would automatically add the ID parameter.

To achieve this, I decided to create a custom router. However, I found very few resources on this topic but managed to find an example.

Now, my CustomRouter is causing an error that I am struggling to properly configure and understand:

Error:

RuntimeException HTTP 500 Internal Server Error The definition ".service_locator.H.editd" has a reference to an abstract definition "Symfony\Component\Config\Loader\LoaderInterface". Abstract definitions cannot be the target of references.

src/Router/CustomRouter.php:

namespace App\Router;

use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;

class CustomRouter implements WarmableInterface, ServiceSubscriberInterface, RouterInterface, RequestMatcherInterface
{
    /**
     * @var Router
     */
    private $router;

    public function __construct(Router $router)
    {
        $this->router = $router;
    }

    public function getRouteCollection(): RouteCollection
    {
        return $this->router->getRouteCollection();
    }

    public function warmUp(string $cacheDir, ?string $buildDir = null): array
    {
        return $this->router->warmUp($cacheDir, $buildDir);
    }

    public static function getSubscribedServices(): array
    {
        return Router::getSubscribedServices();
    }

    public function setContext(RequestContext $context): void
    {
        $this->router->setContext($context);
    }

    public function getContext(): RequestContext
    {
        return $this->router->getContext();
    }

    public function matchRequest(Request $request): array
    {
        return $this->router->matchRequest($request);
    }

    public function generate(string $name, array $parameters = [], int $referenceType = self::ABSOLUTE_PATH): string
    {
        // My logic for injecting ID parameter

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

    public function match(string $pathinfo): array
    {
        return $this->router->match($pathinfo);
    }
}

In my services.yaml:

services:
    App\Router\CustomRouter:
        decorates: router
        arguments: ['@App\Router\CustomRouter.inner']

Why i want to do that ?

Actually, the reason I'm aiming for automatic injection of an id parameter during route generation is that I want to use EasyAdmin while always having a reference in the URL to the Application we are operating on. However, when generating a dashboard with EasyAdmin, we get a URL like /admin, and it doesn't seem feasible to add a parameter to the URL. When creating links to CRUD controllers (linkToCrud), we can't add route parameters as we wish (we can configure the CRUD entity, the type of page (edit, index, create, etc.), but we are not 'really free' with the route parameters).

I could potentially use links like this in the dashboard:

yield MenuItem::linkToRoute('Home', 'fa fa-home', 'admin', ['appId' => $appId]);

But then I would also need to add appId to every use of path() in the EasyAdmin Twig files (for example, EasyAdmin uses path(ea.dashboardRouteName) for the link to the dashboard). That's why I prefer to see if there's a way to automate this parameter addition to the route.

<?php

namespace App\Controller\Admin;

use App\Entity\App;
use App\Repository\AppRepository;
use App\Repository\UserRepository;
use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\UX\Chartjs\Builder\ChartBuilderInterface;
use Symfony\UX\Chartjs\Model\Chart;
use App\Entity\User;
use EasyCorp\Bundle\EasyAdminBundle\Config\Filters;

class DashboardController extends AbstractDashboardController
{
    public function __construct(
        public ChartBuilderInterface $chartBuilder,
        public AppRepository $appRepository,
        public RequestStack $requestStack,
        public UserRepository $userRepository
    ) {
    }

    public function configureFilters(): Filters
    {
        return parent::configureFilters();
    }

    #[Route('/{appId}/admin', name: 'admin')]
    public function index(): Response
    {
        return $this->render('admin/dashboard.html.twig', []);
    }

    public function configureDashboard(): Dashboard
    {
        return Dashboard::new()
            ->setTitle('Hub Cms');
    }

    public function configureMenuItems(): iterable
    {
        $appCount = $this->appRepository->count([]);
        $usersCount = $this->userRepository->count([]);
        yield MenuItem::linkToDashboard('Dashboard', 'fa fa-home');
        yield MenuItem::linkToCrud('Users', 'fa fa-user', User::class)->setBadge($usersCount);
        yield MenuItem::linkToCrud('Apps', 'fa fa-user', App::class)->setBadge($appCount);
    }
}

How can I resolve this error and properly configure my CustomRouter? Any insights or examples would be greatly appreciated! Thank you!


Solution

  • I've finally found a solution based on an other StackOverflow post (when I was looking for other information about routes,... a customRouter with fewer interfaces x)), and it works!

    Solution:

    Custom router:

    <?php // src/Router/CustomRouter.php
    namespace App\Router;
    
    use App\Repository\AppRepository;
    use Symfony\Component\HttpFoundation\RequestStack;
    use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
    use Symfony\Component\Routing\RouterInterface;
    
    class CustomRouter implements RouterInterface,RequestMatcherInterface
    {
        private $router;
    
        public function __construct(RouterInterface $router,private AppRepository $appRepository,private RequestStack $requestStack)
        {
            $this->router = $router;
        }
    
        public function generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH) : string
        {
            if ($name === 'admin') {
                if ($this->requestStack->getCurrentRequest()->attributes->get('appId')) {
                    $appId = $this->requestStack->getCurrentRequest()->attributes->get('appId');
                    $parameters['appId'] = $appId;
                }
            }
    
            return $this->router->generate($name, $parameters, $referenceType);
        }
    
        public function setContext(\Symfony\Component\Routing\RequestContext $context) : void
        {
            $this->router->setContext($context);
        }
    
        public function getContext() : \Symfony\Component\Routing\RequestContext
        {
            return $this->router->getContext();
        }
    
        public function getRouteCollection() : \Symfony\Component\Routing\RouteCollection
        {
            return $this->router->getRouteCollection();
        }
    
        public function match($pathinfo) : array
        {
            return $this->router->match($pathinfo);
        }
    
        public function matchRequest(\Symfony\Component\HttpFoundation\Request $request) : array
        {
            return $this->router->matchRequest($request);
        }
    }
    

    services.yaml:

    App\Router\CustomRouter:
        decorates: 'router'
        arguments: ['@App\Router\CustomRouter.inner']