Skip to content

Commit

Permalink
bug #6515 Fix performance issue when using pretty URLs (javiereguiluz)
Browse files Browse the repository at this point in the history
This PR was squashed before being merged into the 4.x branch.

Discussion
----------

Fix performance issue when using pretty URLs

Fixes #6506.

Commits
-------

64d82fc Fix performance issue when using pretty URLs
  • Loading branch information
javiereguiluz committed Nov 7, 2024
2 parents 2c73dde + 64d82fc commit a8d66aa
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 25 deletions.
21 changes: 10 additions & 11 deletions src/EventListener/AdminRouterSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Controller\DashboardControllerInterface;
use EasyCorp\Bundle\EasyAdminBundle\Factory\AdminContextFactory;
use EasyCorp\Bundle\EasyAdminBundle\Factory\ControllerFactory;
use EasyCorp\Bundle\EasyAdminBundle\Router\AdminRouteGenerator;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
use Symfony\Component\Routing\RouterInterface;

/**
* This subscriber acts as a "proxy" of all backend requests. First, if the
Expand All @@ -36,16 +37,16 @@ class AdminRouterSubscriber implements EventSubscriberInterface
private ControllerResolverInterface $controllerResolver;
private UrlGeneratorInterface $urlGenerator;
private RequestMatcherInterface $requestMatcher;
private RouterInterface $router;
private CacheItemPoolInterface $cache;

public function __construct(AdminContextFactory $adminContextFactory, ControllerFactory $controllerFactory, ControllerResolverInterface $controllerResolver, UrlGeneratorInterface $urlGenerator, RequestMatcherInterface $requestMatcher, RouterInterface $router)
public function __construct(AdminContextFactory $adminContextFactory, ControllerFactory $controllerFactory, ControllerResolverInterface $controllerResolver, UrlGeneratorInterface $urlGenerator, RequestMatcherInterface $requestMatcher, CacheItemPoolInterface $cache)
{
$this->adminContextFactory = $adminContextFactory;
$this->controllerFactory = $controllerFactory;
$this->controllerResolver = $controllerResolver;
$this->urlGenerator = $urlGenerator;
$this->requestMatcher = $requestMatcher;
$this->router = $router;
$this->cache = $cache;
}

public static function getSubscribedEvents(): array
Expand All @@ -68,25 +69,23 @@ public function onKernelRequestPrettyUrls(RequestEvent $event): void
return;
}

$routes = $this->router->getRouteCollection();
$route = $routes->get($routeName);

if (null === $route || true !== $route->getOption(EA::ROUTE_CREATED_BY_EASYADMIN)) {
$adminRoutes = $this->cache->getItem(AdminRouteGenerator::CACHE_KEY_ROUTE_TO_FQCN)->get();
if (null === $adminRoutes || !\array_key_exists($routeName, $adminRoutes)) {
return;
}

$request->attributes->set(EA::ROUTE_CREATED_BY_EASYADMIN, true);

$dashboardControllerFqcn = $route->getOption(EA::DASHBOARD_CONTROLLER_FQCN);
$dashboardControllerFqcn = $adminRoutes[$routeName][EA::DASHBOARD_CONTROLLER_FQCN];
if (null === $dashboardControllerInstance = $this->getDashboardControllerInstance($dashboardControllerFqcn, $request)) {
return;
}

// creating the context is expensive, so it's created once and stored in the request
// if the current request already has an AdminContext object, do nothing
if (null === $adminContext = $request->attributes->get(EA::CONTEXT_REQUEST_ATTRIBUTE)) {
$crudControllerFqcn = $route->getOption(EA::CRUD_CONTROLLER_FQCN);
$actionName = $route->getOption(EA::CRUD_ACTION);
$crudControllerFqcn = $adminRoutes[$routeName][EA::CRUD_CONTROLLER_FQCN];
$actionName = $adminRoutes[$routeName][EA::CRUD_ACTION];

$request->attributes->set(EA::DASHBOARD_CONTROLLER_FQCN, $dashboardControllerFqcn);
$request->attributes->set(EA::CRUD_CONTROLLER_FQCN, $crudControllerFqcn);
Expand Down
2 changes: 1 addition & 1 deletion src/Resources/config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@
->arg(2, service('controller_resolver'))
->arg(3, service('router'))
->arg(4, service('router'))
->arg(5, service('router'))
->arg(5, service('cache.easyadmin'))
->tag('kernel.event_subscriber')

->set(ControllerFactory::class)
Expand Down
51 changes: 38 additions & 13 deletions src/Router/AdminRouteGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
*/
final class AdminRouteGenerator implements AdminRouteGeneratorInterface
{
public const ADMIN_ROUTES_CACHE_KEY = 'easyadmin.generated_routes';
public const CACHE_KEY_ROUTE_TO_FQCN = 'easyadmin.routes.route_to_fqcn';
public const CACHE_KEY_FQCN_TO_ROUTE = 'easyadmin.routes.fqcn_to_route';

private const DEFAULT_ROUTES_CONFIG = [
'index' => [
'routePath' => '/',
Expand Down Expand Up @@ -78,16 +80,9 @@ public function generateAll(): RouteCollection
$collection->add($routeName, $route);
}

// save the generated routes in the cache; this will allow to detect
// if pretty URLs are being used in the application and also improves
// performance when finding a route name using the {dashboard, CRUD controller, action} tuple
$adminRoutesCache = [];
foreach ($adminRoutes as $routeName => $route) {
$adminRoutesCache[$route->getOption(EA::DASHBOARD_CONTROLLER_FQCN)][$route->getOption(EA::CRUD_CONTROLLER_FQCN)][$route->getOption(EA::CRUD_ACTION)] = $routeName;
}
$cachedAdminRoutes = $this->cache->getItem(self::ADMIN_ROUTES_CACHE_KEY);
$cachedAdminRoutes->set($adminRoutesCache);
$this->cache->save($cachedAdminRoutes);
// this dumps all admin routes in a performance-optimized format to later
// find them quickly without having to use Symfony's router service
$this->saveAdminRoutesInCache($adminRoutes);

return $collection;
}
Expand All @@ -96,14 +91,14 @@ public function generateAll(): RouteCollection
// TODO: remove this method in EasyAdmin 5.x
public function usesPrettyUrls(): bool
{
$cachedAdminRoutes = $this->cache->getItem(self::ADMIN_ROUTES_CACHE_KEY)->get();
$cachedAdminRoutes = $this->cache->getItem(self::CACHE_KEY_FQCN_TO_ROUTE)->get();

return null !== $cachedAdminRoutes && [] !== $cachedAdminRoutes;
}

public function findRouteName(string $dashboardFqcn, string $crudControllerFqcn, string $actionName): ?string
{
$adminRoutes = $this->cache->getItem(self::ADMIN_ROUTES_CACHE_KEY)->get();
$adminRoutes = $this->cache->getItem(self::CACHE_KEY_FQCN_TO_ROUTE)->get();

return $adminRoutes[$dashboardFqcn][$crudControllerFqcn][$actionName] ?? null;
}
Expand Down Expand Up @@ -366,4 +361,34 @@ private function transformCrudControllerNameToSnakeCase(string $crudControllerFq

return $shortName;
}

/**
* @param Route[] $adminRoutes
*/
private function saveAdminRoutesInCache(array $adminRoutes): void
{
// to speedup the look up of routes in different parts of the bundle,
// we cache the admin routes in two different maps:
// 1) $cache[route_name] => [dashboard, CRUD controller, action]
// 2) $cache[dashboard][CRUD controller][action] => route_name
$routeNameToFqcn = [];
$fqcnToRouteName = [];
foreach ($adminRoutes as $routeName => $route) {
$routeNameToFqcn[$routeName] = [
EA::DASHBOARD_CONTROLLER_FQCN => $route->getOption(EA::DASHBOARD_CONTROLLER_FQCN),
EA::CRUD_CONTROLLER_FQCN => $route->getOption(EA::CRUD_CONTROLLER_FQCN),
EA::CRUD_ACTION => $route->getOption(EA::CRUD_ACTION),
];

$fqcnToRouteName[$route->getOption(EA::DASHBOARD_CONTROLLER_FQCN)][$route->getOption(EA::CRUD_CONTROLLER_FQCN)][$route->getOption(EA::CRUD_ACTION)] = $routeName;
}

$routeNameToFqcnItem = $this->cache->getItem(self::CACHE_KEY_ROUTE_TO_FQCN);
$routeNameToFqcnItem->set($routeNameToFqcn);
$this->cache->save($routeNameToFqcnItem);

$fqcnToRouteNameItem = $this->cache->getItem(self::CACHE_KEY_FQCN_TO_ROUTE);
$fqcnToRouteNameItem->set($fqcnToRouteName);
$this->cache->save($fqcnToRouteNameItem);
}
}

0 comments on commit a8d66aa

Please sign in to comment.