From c158f4835c7102f0018ad307c40b0af25c422345 Mon Sep 17 00:00:00 2001 From: Jaapio Date: Fri, 8 Mar 2024 16:42:21 +0100 Subject: [PATCH] [TASK] Improve menu rendering speed By introducing a cache array for menus we do improve the speed when menus are multiple times rendered. (cherry picked from commit 9f72e028f701aa4352156492265bc9ad1614b061) --- packages/guides/resources/config/guides.php | 6 ++ packages/guides/src/RenderContext.php | 2 +- packages/guides/src/Twig/AssetsExtension.php | 24 ++---- .../guides/src/Twig/GlobalMenuExtension.php | 83 +++++++++++++++++++ 4 files changed, 95 insertions(+), 20 deletions(-) create mode 100644 packages/guides/src/Twig/GlobalMenuExtension.php diff --git a/packages/guides/resources/config/guides.php b/packages/guides/resources/config/guides.php index 90ced7927..94e69a63e 100644 --- a/packages/guides/resources/config/guides.php +++ b/packages/guides/resources/config/guides.php @@ -54,6 +54,7 @@ use phpDocumentor\Guides\TemplateRenderer; use phpDocumentor\Guides\Twig\AssetsExtension; use phpDocumentor\Guides\Twig\EnvironmentBuilder; +use phpDocumentor\Guides\Twig\GlobalMenuExtension; use phpDocumentor\Guides\Twig\Theme\ThemeManager; use phpDocumentor\Guides\Twig\TwigTemplateRenderer; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; @@ -207,6 +208,11 @@ ->tag('twig.extension') ->autowire() + ->set(GlobalMenuExtension::class) + ->arg('$nodeRenderer', service('phpdoc.guides.output_node_renderer')) + ->tag('twig.extension') + ->autowire() + ->set(ThemeManager::class) ->arg('$filesystemLoader', service(FilesystemLoader::class)) ->arg( diff --git a/packages/guides/src/RenderContext.php b/packages/guides/src/RenderContext.php index 1db91fe6c..658d0cab2 100644 --- a/packages/guides/src/RenderContext.php +++ b/packages/guides/src/RenderContext.php @@ -142,7 +142,7 @@ public function getDirName(): string return $dirname; } - + public function hasCurrentFileName(): bool { return $this->currentFileName !== null; diff --git a/packages/guides/src/Twig/AssetsExtension.php b/packages/guides/src/Twig/AssetsExtension.php index 359447402..98d3357a5 100644 --- a/packages/guides/src/Twig/AssetsExtension.php +++ b/packages/guides/src/Twig/AssetsExtension.php @@ -21,7 +21,6 @@ use phpDocumentor\Guides\Meta\Target; use phpDocumentor\Guides\NodeRenderers\NodeRenderer; use phpDocumentor\Guides\Nodes\BreadCrumbNode; -use phpDocumentor\Guides\Nodes\CollectionNode; use phpDocumentor\Guides\Nodes\Node; use phpDocumentor\Guides\ReferenceResolvers\DocumentNameResolverInterface; use phpDocumentor\Guides\RenderContext; @@ -29,7 +28,6 @@ use Psr\Log\LoggerInterface; use RuntimeException; use Stringable; -use Throwable; use Twig\Extension\AbstractExtension; use Twig\TwigFunction; use Twig\TwigTest; @@ -39,6 +37,8 @@ final class AssetsExtension extends AbstractExtension { + private GlobalMenuExtension $menuExtension; + /** @param NodeRenderer $nodeRenderer */ public function __construct( private readonly LoggerInterface $logger, @@ -46,6 +46,7 @@ public function __construct( private readonly DocumentNameResolverInterface $documentNameResolver, private readonly UrlGeneratorInterface $urlGenerator, ) { + $this->menuExtension = new GlobalMenuExtension($this->nodeRenderer); } /** @return TwigFunction[] */ @@ -56,7 +57,7 @@ public function getFunctions(): array new TwigFunction('renderNode', $this->renderNode(...), ['is_safe' => ['html'], 'needs_context' => true]), new TwigFunction('renderLink', $this->renderLink(...), ['is_safe' => ['html'], 'needs_context' => true]), new TwigFunction('renderBreadcrumb', $this->renderBreadcrumb(...), ['is_safe' => ['html'], 'needs_context' => true]), - new TwigFunction('renderMenu', $this->renderMenu(...), ['is_safe' => ['html'], 'needs_context' => true]), + new TwigFunction('renderMenu', $this->renderMenu(...), ['is_safe' => ['html'], 'needs_context' => true, 'deprecated' => true]), new TwigFunction('renderTarget', $this->renderTarget(...), ['is_safe' => ['html'], 'needs_context' => true]), ]; } @@ -136,22 +137,7 @@ public function renderBreadcrumb(array $context): string /** @param array{env: RenderContext} $context */ public function renderMenu(array $context, string $menuType, int $maxMenuCount = 0): string { - $renderContext = $this->getRenderContext($context); - $globalMenues = $renderContext->getProjectNode()->getGlobalMenues(); - $menues = []; - foreach ($globalMenues as $menu) { - $menu = $menu->withOptions(['menu' => $menuType]); - try { - $menu = $menu->withCurrentPath($renderContext->getCurrentFileName()); - $menu = $menu->withRootlinePaths($renderContext->getCurrentFileRootline()); - } catch (Throwable) { - // do nothing, we are in a context without active menu like single page or functional test - } - - $menues[] = $menu; - } - - return $this->nodeRenderer->render(new CollectionNode($menues), $renderContext); + return $this->menuExtension->renderMenu($context, $menuType); } /** @param array{env: RenderContext} $context */ diff --git a/packages/guides/src/Twig/GlobalMenuExtension.php b/packages/guides/src/Twig/GlobalMenuExtension.php new file mode 100644 index 000000000..6cf4f5b54 --- /dev/null +++ b/packages/guides/src/Twig/GlobalMenuExtension.php @@ -0,0 +1,83 @@ + + */ + private array $menuCache = []; + + /** @param NodeRenderer $nodeRenderer */ + public function __construct( + private readonly NodeRenderer $nodeRenderer, + ) { + } + + /** @return TwigFunction[] */ + public function getFunctions(): array + { + return [ + new TwigFunction('renderMenu', $this->renderMenu(...), ['is_safe' => ['html'], 'needs_context' => true]), + ]; + } + + /** @param array{env: RenderContext} $context */ + public function renderMenu(array $context, string $menuType): string + { + $renderContext = $this->getRenderContext($context); + $globalMenues = $renderContext->getProjectNode()->getGlobalMenues(); + if (isset($this->menuCache[$renderContext->getCurrentFileName() . '::' . $menuType])) { + return $this->menuCache[$renderContext->getCurrentFileName() . '::' . $menuType]; + } + + $menues = []; + foreach ($globalMenues as $menu) { + $menu = $menu->withOptions(['menu' => $menuType]); + try { + $menu = $menu->withCurrentPath($renderContext->getCurrentFileName()); + $menu = $menu->withRootlinePaths($renderContext->getCurrentFileRootline()); + } catch (Throwable) { + // do nothing, we are in a context without active menu like single page or functional test + } + + $menues[] = $menu; + } + + return $this->menuCache[$renderContext->getCurrentFileName() . '::' . $menuType] = $this->nodeRenderer->render(new CollectionNode($menues), $renderContext); + } + + /** @param array{env: RenderContext} $context */ + private function getRenderContext(array $context): RenderContext + { + $renderContext = $context['env'] ?? null; + if (!$renderContext instanceof RenderContext) { + throw new RuntimeException('Render context must be set in the twig global state to render nodes'); + } + + return $renderContext; + } +}