vendor/easycorp/easyadmin-bundle/src/Router/AdminUrlGenerator.php line 317

Open in your IDE?
  1. <?php
  2. namespace EasyCorp\Bundle\EasyAdminBundle\Router;
  3. use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
  4. use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
  5. use EasyCorp\Bundle\EasyAdminBundle\Config\Option\EA;
  6. use EasyCorp\Bundle\EasyAdminBundle\Contracts\Controller\DashboardControllerInterface;
  7. use EasyCorp\Bundle\EasyAdminBundle\Contracts\Provider\AdminContextProviderInterface;
  8. use EasyCorp\Bundle\EasyAdminBundle\Contracts\Router\AdminRouteGeneratorInterface;
  9. use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
  10. use EasyCorp\Bundle\EasyAdminBundle\Registry\DashboardControllerRegistryInterface;
  11. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  12. /**
  13.  * @author Javier Eguiluz <javier.eguiluz@gmail.com>
  14.  */
  15. final class AdminUrlGenerator implements AdminUrlGeneratorInterface
  16. {
  17.     private bool $isInitialized false;
  18.     private ?string $dashboardRoute null;
  19.     private ?bool $includeReferrer null;
  20.     private array $routeParameters = [];
  21.     private ?string $currentPageReferrer null;
  22.     private ?string $customPageReferrer null;
  23.     public function __construct(
  24.         private readonly AdminContextProviderInterface $adminContextProvider,
  25.         private readonly UrlGeneratorInterface $urlGenerator,
  26.         private readonly DashboardControllerRegistryInterface $dashboardControllerRegistry,
  27.         private readonly AdminRouteGeneratorInterface $adminRouteGenerator,
  28.     ) {
  29.     }
  30.     public function setDashboard(string $dashboardControllerFqcn): AdminUrlGeneratorInterface
  31.     {
  32.         $this->setRouteParameter(EA::DASHBOARD_CONTROLLER_FQCN$dashboardControllerFqcn);
  33.         return $this;
  34.     }
  35.     public function setController(string $crudControllerFqcn): AdminUrlGeneratorInterface
  36.     {
  37.         $this->setRouteParameter(EA::CRUD_CONTROLLER_FQCN$crudControllerFqcn);
  38.         $this->unset(EA::ROUTE_NAME);
  39.         $this->unset(EA::ROUTE_PARAMS);
  40.         return $this;
  41.     }
  42.     public function setAction(string $action): AdminUrlGeneratorInterface
  43.     {
  44.         $this->setRouteParameter(EA::CRUD_ACTION$action);
  45.         $this->unset(EA::ROUTE_NAME);
  46.         $this->unset(EA::ROUTE_PARAMS);
  47.         return $this;
  48.     }
  49.     public function setRoute(string $routeName, array $routeParameters = []): AdminUrlGeneratorInterface
  50.     {
  51.         $this->unsetAllExcept(EA::DASHBOARD_CONTROLLER_FQCN);
  52.         $this->setRouteParameter(EA::ROUTE_NAME$routeName);
  53.         $this->setRouteParameter(EA::ROUTE_PARAMS$routeParameters);
  54.         return $this;
  55.     }
  56.     public function setEntityId($entityId): AdminUrlGeneratorInterface
  57.     {
  58.         $this->setRouteParameter(EA::ENTITY_ID$entityId);
  59.         return $this;
  60.     }
  61.     public function get(string $paramName): mixed
  62.     {
  63.         if (false === $this->isInitialized) {
  64.             $this->initialize();
  65.         }
  66.         return $this->routeParameters[$paramName] ?? null;
  67.     }
  68.     public function set(string $paramName$paramValue): AdminUrlGeneratorInterface
  69.     {
  70.         if (\in_array($paramName, [EA::MENU_INDEXEA::SUBMENU_INDEX], true)) {
  71.             trigger_deprecation(
  72.                 'easycorp/easyadmin-bundle',
  73.                 '4.5.0',
  74.                 'Using the "%s" query parameter is deprecated. Menu items are now highlighted automatically based on the Request data, so you don\'t have to deal with menu items manually anymore.',
  75.                 $paramName,
  76.             );
  77.         }
  78.         $this->setRouteParameter($paramName$paramValue);
  79.         return $this;
  80.     }
  81.     public function setAll(array $routeParameters): AdminUrlGeneratorInterface
  82.     {
  83.         foreach ($routeParameters as $paramName => $paramValue) {
  84.             $this->setRouteParameter($paramName$paramValue);
  85.         }
  86.         return $this;
  87.     }
  88.     public function unset(string $paramName): AdminUrlGeneratorInterface
  89.     {
  90.         if (false === $this->isInitialized) {
  91.             $this->initialize();
  92.         }
  93.         unset($this->routeParameters[$paramName]);
  94.         return $this;
  95.     }
  96.     public function unsetAll(): AdminUrlGeneratorInterface
  97.     {
  98.         if (false === $this->isInitialized) {
  99.             $this->initialize();
  100.         }
  101.         $this->routeParameters = [];
  102.         return $this;
  103.     }
  104.     public function unsetAllExcept(string ...$namesOfParamsToKeep): AdminUrlGeneratorInterface
  105.     {
  106.         if (false === $this->isInitialized) {
  107.             $this->initialize();
  108.         }
  109.         $this->routeParameters array_intersect_key($this->routeParametersarray_flip($namesOfParamsToKeep));
  110.         return $this;
  111.     }
  112.     public function includeReferrer(): AdminUrlGeneratorInterface
  113.     {
  114.         trigger_deprecation(
  115.             'easycorp/easyadmin-bundle',
  116.             '4.9.0',
  117.             'Adding the referrer argument in the admin URLs via the AdminUrlGenerator::includeReferrer() method is deprecated and it will be removed in 5.0.0. The referrer will now be determined automatically based on the current request.',
  118.         );
  119.         if (false === $this->isInitialized) {
  120.             $this->initialize();
  121.         }
  122.         $this->includeReferrer true;
  123.         return $this;
  124.     }
  125.     public function removeReferrer(): AdminUrlGeneratorInterface
  126.     {
  127.         trigger_deprecation(
  128.             'easycorp/easyadmin-bundle',
  129.             '4.9.0',
  130.             'Removing the referrer argument in the admin URLs via the AdminUrlGenerator::removeReferrer() method is deprecated and it will be removed in 5.0.0. The referrer will now be determined automatically based on the current request.',
  131.         );
  132.         if (false === $this->isInitialized) {
  133.             $this->initialize();
  134.         }
  135.         $this->includeReferrer false;
  136.         return $this;
  137.     }
  138.     public function setReferrer(string $referrer): AdminUrlGeneratorInterface
  139.     {
  140.         trigger_deprecation(
  141.             'easycorp/easyadmin-bundle',
  142.             '4.9.0',
  143.             'Adding the referrer argument in the admin URLs via the AdminUrlGenerator::setReferrer() method is deprecated and it will be removed in 5.0.0. The referrer will now be determined automatically based on the current request.',
  144.         );
  145.         if (false === $this->isInitialized) {
  146.             $this->initialize();
  147.         }
  148.         $this->includeReferrer true;
  149.         $this->customPageReferrer $referrer;
  150.         return $this;
  151.     }
  152.     public function addSignature(bool $addSignature true): AdminUrlGeneratorInterface
  153.     {
  154.         trigger_deprecation(
  155.             'easycorp/easyadmin-bundle',
  156.             '4.1.0',
  157.             'EasyAdmin URLs no longer include signatures because they don\'t provide any additional security. Calling the "%s" method has no effect, so you can stop calling it. This method will be removed in future EasyAdmin versions.',
  158.             __METHOD__,
  159.         );
  160.         return $this;
  161.     }
  162.     public function getSignature(): string
  163.     {
  164.         trigger_deprecation(
  165.             'easycorp/easyadmin-bundle',
  166.             '4.1.0',
  167.             'EasyAdmin URLs no longer include signatures because they don\'t provide any additional security. Calling the "%s" method will always return an empty string, so you can stop calling it. This method will be removed in future EasyAdmin versions.',
  168.             __METHOD__,
  169.         );
  170.         return '';
  171.     }
  172.     // this method allows to omit the 'generateUrl()' call in templates, making code more concise
  173.     public function __toString(): string
  174.     {
  175.         return $this->generateUrl();
  176.     }
  177.     public function generateUrl(): string
  178.     {
  179.         if (false === $this->isInitialized) {
  180.             $this->initialize();
  181.         }
  182.         $usePrettyUrls $this->adminRouteGenerator->usesPrettyUrls();
  183.         if (true === $this->includeReferrer) {
  184.             $this->setRouteParameter(EA::REFERRER$this->customPageReferrer ?? $this->currentPageReferrer);
  185.         }
  186.         // this avoids forcing users to always be explicit about the action to execute
  187.         if (null !== $this->get(EA::CRUD_CONTROLLER_FQCN) && null === $this->get(EA::CRUD_ACTION)) {
  188.             $this->set(EA::CRUD_ACTIONAction::INDEX);
  189.         }
  190.         // if the Dashboard FQCN is defined, find its route and use it to override
  191.         // the current route (this is needed to allow generating links to different dashboards)
  192.         if (null !== $dashboardControllerFqcn $this->get(EA::DASHBOARD_CONTROLLER_FQCN)) {
  193.             if (null === $dashboardRoute $this->dashboardControllerRegistry->getRouteByControllerFqcn($dashboardControllerFqcn)) {
  194.                 throw new \InvalidArgumentException(sprintf('The given "%s" class is not a valid Dashboard controller. Make sure it extends from "%s" or implements "%s".'$dashboardControllerFqcnAbstractDashboardController::class, DashboardControllerInterface::class));
  195.             }
  196.             $this->dashboardRoute $dashboardRoute;
  197.             if (!$usePrettyUrls) {
  198.                 $this->unset(EA::DASHBOARD_CONTROLLER_FQCN);
  199.             }
  200.         }
  201.         // if the current action is 'index' and an entity ID is defined, remove the entity ID to prevent exceptions automatically
  202.         if (Action::INDEX === $this->get(EA::CRUD_ACTION) && null !== $this->get(EA::ENTITY_ID)) {
  203.             $this->unset(EA::ENTITY_ID);
  204.         }
  205.         // this happens when generating URLs from outside EasyAdmin (AdminContext is null) and
  206.         // no Dashboard FQCN has been defined explicitly
  207.         if (null === $this->dashboardRoute) {
  208.             if ($this->dashboardControllerRegistry->getNumberOfDashboards() > 1) {
  209.                 throw new \RuntimeException('When generating admin URLs from outside EasyAdmin or without a related HTTP request (e.g. in tests, console commands, etc.), if your application has more than one Dashboard, you must associate the URL to a specific Dashboard using the "setDashboard()" method.');
  210.             }
  211.             $this->setDashboard($this->dashboardControllerRegistry->getFirstDashboardFqcn());
  212.             $this->dashboardRoute $this->dashboardControllerRegistry->getFirstDashboardRoute();
  213.         }
  214.         // if present, remove the suffix of i18n route names (it's the content after the last dot
  215.         // in the route name; e.g. 'dashboard.en' -> remove '.en', 'admin.index.en_US' -> remove '.en_US')
  216.         $this->dashboardRoute preg_replace('~\.[a-z]{2}(_[A-Z]{2})?$~'''$this->dashboardRoute);
  217.         // this removes any parameter with a NULL value
  218.         $routeParameters array_filter(
  219.             $this->routeParameters,
  220.             static fn ($parameterValue): bool => null !== $parameterValue
  221.         );
  222.         ksort($routeParameters\SORT_STRING);
  223.         $context $this->adminContextProvider->getContext();
  224.         $urlType null !== $context && false === $context->getAbsoluteUrls() ? UrlGeneratorInterface::ABSOLUTE_PATH UrlGeneratorInterface::ABSOLUTE_URL;
  225.         // if no route parameters are passed, the route doesn't point to any CRUD controller
  226.         // action or to any custom action/route; consider it a link to the current dashboard
  227.         if ([] === $routeParameters) {
  228.             return $this->urlGenerator->generate($this->dashboardRoute, [], $urlType);
  229.         }
  230.         if (null !== $this->get(EA::ROUTE_NAME)) {
  231.             return $this->urlGenerator->generate($this->dashboardRoute$routeParameters$urlType);
  232.         }
  233.         if ($usePrettyUrls) {
  234.             $dashboardControllerFqcn $this->get(EA::DASHBOARD_CONTROLLER_FQCN) ?? $context?->getRequest()->attributes->get(EA::DASHBOARD_CONTROLLER_FQCN) ?? $context?->getDashboardControllerFqcn() ?? $this->dashboardControllerRegistry->getFirstDashboardFqcn();
  235.             $crudControllerFqcn $this->get(EA::CRUD_CONTROLLER_FQCN) ?? $context?->getRequest()->attributes->get(EA::CRUD_CONTROLLER_FQCN);
  236.             $actionName $this->get(EA::CRUD_ACTION) ?? $context?->getRequest()->attributes->get(EA::CRUD_ACTION);
  237.             if (null === $crudControllerFqcn || null === $routeName $this->adminRouteGenerator->findRouteName($dashboardControllerFqcn$crudControllerFqcn$actionName)) {
  238.                 $routeName $this->dashboardRoute;
  239.                 if (null === $crudControllerFqcn) {
  240.                     unset($routeParameters[EA::DASHBOARD_CONTROLLER_FQCN]);
  241.                 }
  242.             } else {
  243.                 // remove these parameters so they don't appear in the query string when using pretty URLs
  244.                 unset($routeParameters[EA::DASHBOARD_CONTROLLER_FQCN]);
  245.                 unset($routeParameters[EA::CRUD_CONTROLLER_FQCN]);
  246.                 unset($routeParameters[EA::CRUD_ACTION]);
  247.                 unset($routeParameters[EA::ENTITY_FQCN]);
  248.             }
  249.         } else {
  250.             $routeName $this->dashboardRoute;
  251.         }
  252.         if (!$usePrettyUrls && \in_array($routeParameters[EA::CRUD_ACTION] ?? Action::INDEXCrud::ACTION_NAMEStrue)) {
  253.             trigger_deprecation(
  254.                 'easycorp/easyadmin-bundle',
  255.                 '4.14.0',
  256.                 'Not using pretty admin URLs is deprecated because they will become the only available URLs starting from EasyAdmin 5.0.0. Read the docs to learn how to enable pretty URLs in your application.',
  257.             );
  258.         }
  259.         $url $this->urlGenerator->generate($routeName$routeParameters$urlType);
  260.         $url '' === $url '?' $url;
  261.         // this is important to start the generation of each URL from the same initial state
  262.         // otherwise, some parameters used when generating some URL could leak to other URLs
  263.         $this->isInitialized false;
  264.         return $url;
  265.     }
  266.     private function setRouteParameter(string $paramName$paramValue): void
  267.     {
  268.         if (false === $this->isInitialized) {
  269.             $this->initialize();
  270.         }
  271.         if (\is_resource($paramValue)) {
  272.             throw new \InvalidArgumentException(sprintf('The value of the "%s" parameter is a PHP resource, which is not supported as a route parameter.'$paramName));
  273.         }
  274.         if (\is_object($paramValue)) {
  275.             if (method_exists($paramValue'__toString')) {
  276.                 $paramValue = (string) $paramValue;
  277.             } else {
  278.                 throw new \InvalidArgumentException(sprintf('The object passed as the value of the "%s" parameter must implement the "__toString()" method to allow using its value as a route parameter.'$paramName));
  279.             }
  280.         }
  281.         $this->routeParameters[$paramName] = $paramValue;
  282.     }
  283.     private function initialize(): void
  284.     {
  285.         $this->isInitialized true;
  286.         $adminContext $this->adminContextProvider->getContext();
  287.         if (null === $adminContext) {
  288.             $this->dashboardRoute null;
  289.             $currentRouteParameters $routeParametersForReferrer = [];
  290.             $this->currentPageReferrer null;
  291.         } else {
  292.             $this->dashboardRoute $adminContext->getDashboardRouteName();
  293.             $routeParameters array_filter([
  294.                 EA::DASHBOARD_CONTROLLER_FQCN => $adminContext->getRequest()->attributes->get(EA::DASHBOARD_CONTROLLER_FQCN),
  295.                 EA::CRUD_CONTROLLER_FQCN => $adminContext->getRequest()->attributes->get(EA::CRUD_CONTROLLER_FQCN),
  296.                 EA::CRUD_ACTION => $adminContext->getRequest()->attributes->get(EA::CRUD_ACTION),
  297.                 EA::ENTITY_ID => $adminContext->getRequest()->attributes->get(EA::ENTITY_ID),
  298.             ]);
  299.             $currentRouteParameters $routeParametersForReferrer array_merge($routeParameters$adminContext->getRequest()->query->all());
  300.             unset($routeParametersForReferrer[EA::REFERRER]);
  301.             $this->currentPageReferrer sprintf('%s%s?%s'$adminContext->getRequest()->getBaseUrl(), $adminContext->getRequest()->getPathInfo(), http_build_query($routeParametersForReferrer));
  302.         }
  303.         $this->includeReferrer null;
  304.         $this->customPageReferrer null;
  305.         $this->routeParameters $currentRouteParameters;
  306.     }
  307. }