From dd5d0b35e1f8c0076e5808f9d7c4e653de4a0e3c Mon Sep 17 00:00:00 2001 From: Massimiliano Arione Date: Thu, 5 Sep 2024 19:10:44 +0200 Subject: [PATCH] add Twig Runtime --- src/Knp/Menu/Twig/MenuExtension.php | 119 +++++++----------- src/Knp/Menu/Twig/MenuRuntimeExtension.php | 112 +++++++++++++++++ .../Knp/Menu/Tests/Twig/MenuExtensionTest.php | 27 ++-- 3 files changed, 174 insertions(+), 84 deletions(-) create mode 100644 src/Knp/Menu/Twig/MenuRuntimeExtension.php diff --git a/src/Knp/Menu/Twig/MenuExtension.php b/src/Knp/Menu/Twig/MenuExtension.php index 25d2c590..9ed6568b 100644 --- a/src/Knp/Menu/Twig/MenuExtension.php +++ b/src/Knp/Menu/Twig/MenuExtension.php @@ -12,71 +12,74 @@ class MenuExtension extends AbstractExtension { - public function __construct(private Helper $helper, private ?MatcherInterface $matcher = null, private ?MenuManipulator $menuManipulator = null) - { + private ?MenuRuntimeExtension $runtimeExtension = null; + + public function __construct( + ?Helper $helper = null, + ?MatcherInterface $matcher = null, + ?MenuManipulator $menuManipulator = null, + ) { + if (null !== $helper) { + @trigger_error('Injecting dependencies is deprecated since v3.6 and will be removed in v4.', E_USER_DEPRECATED); + $this->runtimeExtension = new MenuRuntimeExtension($helper, $matcher, $menuManipulator); + } } - /** - * @return array - */ public function getFunctions(): array { + $legacy = null !== $this->runtimeExtension; + return [ - new TwigFunction('knp_menu_get', [$this, 'get']), - new TwigFunction('knp_menu_render', [$this, 'render'], ['is_safe' => ['html']]), - new TwigFunction('knp_menu_get_breadcrumbs_array', [$this, 'getBreadcrumbsArray']), - new TwigFunction('knp_menu_get_current_item', [$this, 'getCurrentItem']), + new TwigFunction('knp_menu_get', $legacy ? [$this, 'get'] : [MenuRuntimeExtension::class, 'get']), + new TwigFunction('knp_menu_render', $legacy ? [$this, 'render'] : [MenuRuntimeExtension::class, 'render'], ['is_safe' => ['html']]), + new TwigFunction('knp_menu_get_breadcrumbs_array', $legacy ? [$this, 'getBreadcrumbsArray'] : [MenuRuntimeExtension::class, 'getBreadcrumbsArray']), + new TwigFunction('knp_menu_get_current_item', $legacy ? [$this, 'getCurrentItem'] : [MenuRuntimeExtension::class, 'getCurrentItem']), ]; } - /** - * @return array - */ public function getFilters(): array { + $legacy = null !== $this->runtimeExtension; + return [ - new TwigFilter('knp_menu_as_string', [$this, 'pathAsString']), + new TwigFilter('knp_menu_as_string', $legacy ? [$this, 'pathAsString'] : [MenuRuntimeExtension::class, 'pathAsString']), ]; } - /** - * @return array - */ public function getTests(): array { + $legacy = null !== $this->runtimeExtension; + return [ - new TwigTest('knp_menu_current', [$this, 'isCurrent']), - new TwigTest('knp_menu_ancestor', [$this, 'isAncestor']), + new TwigTest('knp_menu_current', $legacy ? [$this, 'isCurrent'] : [MenuRuntimeExtension::class, 'isCurrent']), + new TwigTest('knp_menu_ancestor', $legacy ? [$this, 'isAncestor'] : [MenuRuntimeExtension::class, 'isAncestor']), ]; } /** - * Retrieves an item following a path in the tree. - * - * @param ItemInterface|string $menu * @param array $path * @param array $options */ - public function get($menu, array $path = [], array $options = []): ItemInterface + public function get(ItemInterface|string $menu, array $path = [], array $options = []): ItemInterface { - return $this->helper->get($menu, $path, $options); + assert(null !== $this->runtimeExtension); + + return $this->runtimeExtension->get($menu, $path, $options); } /** - * Renders a menu with the specified renderer. - * - * @param ItemInterface|string|array $menu + * @param string|ItemInterface|array $menu * @param array $options */ - public function render($menu, array $options = [], ?string $renderer = null): string + public function render(array|ItemInterface|string $menu, array $options = [], ?string $renderer = null): string { - return $this->helper->render($menu, $options, $renderer); + assert(null !== $this->runtimeExtension); + + return $this->runtimeExtension->render($menu, $options, $renderer); } /** - * Returns an array ready to be used for breadcrumbs. - * - * @param ItemInterface|string|array $menu + * @param string|ItemInterface|array $menu * @param string|array|null $subItem * * @phpstan-param string|ItemInterface|array|\Traversable $subItem @@ -84,66 +87,38 @@ public function render($menu, array $options = [], ?string $renderer = null): st * @return array> * @phpstan-return list */ - public function getBreadcrumbsArray($menu, $subItem = null): array + public function getBreadcrumbsArray(array|ItemInterface|string $menu, array|string|null $subItem = null): array { - return $this->helper->getBreadcrumbsArray($menu, $subItem); + assert(null !== $this->runtimeExtension); + + return $this->runtimeExtension->getBreadcrumbsArray($menu, $subItem); } - /** - * Returns the current item of a menu. - * - * @param ItemInterface|string $menu - */ - public function getCurrentItem($menu): ItemInterface + public function getCurrentItem(ItemInterface|string $menu): ItemInterface { - $rootItem = $this->get($menu); + assert(null !== $this->runtimeExtension); - $currentItem = $this->helper->getCurrentItem($rootItem); - - if (null === $currentItem) { - $currentItem = $rootItem; - } - - return $currentItem; + return $this->runtimeExtension->getCurrentItem($menu); } - /** - * A string representation of this menu item - * - * e.g. Top Level > Second Level > This menu - */ public function pathAsString(ItemInterface $menu, string $separator = ' > '): string { - if (null === $this->menuManipulator) { - throw new \BadMethodCallException('The menu manipulator must be set to get the breadcrumbs array'); - } + assert(null !== $this->runtimeExtension); - return $this->menuManipulator->getPathAsString($menu, $separator); + return $this->runtimeExtension->pathAsString($menu, $separator); } - /** - * Checks whether an item is current. - */ public function isCurrent(ItemInterface $item): bool { - if (null === $this->matcher) { - throw new \BadMethodCallException('The matcher must be set to get the breadcrumbs array'); - } + assert(null !== $this->runtimeExtension); - return $this->matcher->isCurrent($item); + return $this->runtimeExtension->isCurrent($item); } - /** - * Checks whether an item is the ancestor of a current item. - * - * @param int|null $depth The max depth to look for the item - */ public function isAncestor(ItemInterface $item, ?int $depth = null): bool { - if (null === $this->matcher) { - throw new \BadMethodCallException('The matcher must be set to get the breadcrumbs array'); - } + assert(null !== $this->runtimeExtension); - return $this->matcher->isAncestor($item, $depth); + return $this->runtimeExtension->isAncestor($item, $depth); } } diff --git a/src/Knp/Menu/Twig/MenuRuntimeExtension.php b/src/Knp/Menu/Twig/MenuRuntimeExtension.php new file mode 100644 index 00000000..e084a143 --- /dev/null +++ b/src/Knp/Menu/Twig/MenuRuntimeExtension.php @@ -0,0 +1,112 @@ + $path + * @param array $options + */ + public function get(ItemInterface|string $menu, array $path = [], array $options = []): ItemInterface + { + return $this->helper->get($menu, $path, $options); + } + + /** + * Renders a menu with the specified renderer. + * + * @param string|ItemInterface|array $menu + * @param array $options + */ + public function render(array|ItemInterface|string $menu, array $options = [], ?string $renderer = null): string + { + return $this->helper->render($menu, $options, $renderer); + } + + /** + * Returns an array ready to be used for breadcrumbs. + * + * @param string|ItemInterface|array $menu + * @param string|array|null $subItem + * + * @phpstan-param string|ItemInterface|array|\Traversable $subItem + * + * @return array> + * @phpstan-return list + */ + public function getBreadcrumbsArray(array|ItemInterface|string $menu, array|string|null $subItem = null): array + { + return $this->helper->getBreadcrumbsArray($menu, $subItem); + } + + /** + * Returns the current item of a menu. + */ + public function getCurrentItem(ItemInterface|string $menu): ItemInterface + { + $rootItem = $this->get($menu); + + $currentItem = $this->helper->getCurrentItem($rootItem); + + if (null === $currentItem) { + $currentItem = $rootItem; + } + + return $currentItem; + } + + /** + * A string representation of this menu item + * + * e.g. Top Level > Second Level > This menu + */ + public function pathAsString(ItemInterface $menu, string $separator = ' > '): string + { + if (null === $this->menuManipulator) { + throw new \BadMethodCallException('The menu manipulator must be set to get the breadcrumbs array'); + } + + return $this->menuManipulator->getPathAsString($menu, $separator); + } + + /** + * Checks whether an item is current. + */ + public function isCurrent(ItemInterface $item): bool + { + if (null === $this->matcher) { + throw new \BadMethodCallException('The matcher must be set to get the breadcrumbs array'); + } + + return $this->matcher->isCurrent($item); + } + + /** + * Checks whether an item is the ancestor of a current item. + * + * @param int|null $depth The max depth to look for the item + */ + public function isAncestor(ItemInterface $item, ?int $depth = null): bool + { + if (null === $this->matcher) { + throw new \BadMethodCallException('The matcher must be set to get the breadcrumbs array'); + } + + return $this->matcher->isAncestor($item, $depth); + } +} diff --git a/tests/Knp/Menu/Tests/Twig/MenuExtensionTest.php b/tests/Knp/Menu/Tests/Twig/MenuExtensionTest.php index d0433851..23afe847 100644 --- a/tests/Knp/Menu/Tests/Twig/MenuExtensionTest.php +++ b/tests/Knp/Menu/Tests/Twig/MenuExtensionTest.php @@ -6,10 +6,13 @@ use Knp\Menu\Matcher\MatcherInterface; use Knp\Menu\Twig\Helper; use Knp\Menu\Twig\MenuExtension; +use Knp\Menu\Twig\MenuRuntimeExtension; use Knp\Menu\Util\MenuManipulator; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Twig\Environment; use Twig\Loader\ArrayLoader; +use Twig\RuntimeLoader\FactoryRuntimeLoader; use Twig\TemplateWrapper; final class MenuExtensionTest extends TestCase @@ -162,10 +165,8 @@ public function testGetCurrentItem(): void /** * @param array $methods - * - * @return Helper|\PHPUnit\Framework\MockObject\MockObject */ - private function getHelperMock(array $methods) + private function getHelperMock(array $methods): MockObject|Helper { return $this->getMockBuilder(Helper::class) ->disableOriginalConstructor() @@ -176,10 +177,8 @@ private function getHelperMock(array $methods) /** * @param array $methods - * - * @return MenuManipulator|\PHPUnit\Framework\MockObject\MockObject */ - private function getManipulatorMock(array $methods) + private function getManipulatorMock(array $methods): MenuManipulator|MockObject { return $this->getMockBuilder(MenuManipulator::class) ->disableOriginalConstructor() @@ -188,10 +187,7 @@ private function getManipulatorMock(array $methods) ; } - /** - * @return MatcherInterface|\PHPUnit\Framework\MockObject\MockObject - */ - private function getMatcherMock() + private function getMatcherMock(): MockObject|MatcherInterface { return $this->getMockBuilder(MatcherInterface::class)->getMock(); } @@ -200,11 +196,18 @@ private function getTemplate( string $template, Helper $helper, ?MatcherInterface $matcher = null, - ?MenuManipulator $menuManipulator = null + ?MenuManipulator $menuManipulator = null, ): TemplateWrapper { $loader = new ArrayLoader(['index' => $template]); $twig = new Environment($loader, ['debug' => true, 'cache' => false]); - $twig->addExtension(new MenuExtension($helper, $matcher, $menuManipulator)); + $twig->addExtension(new MenuExtension()); + $twig->addRuntimeLoader(new FactoryRuntimeLoader([ + MenuRuntimeExtension::class => fn () => new MenuRuntimeExtension( + $helper, + $matcher, + $menuManipulator, + ), + ])); return $twig->load('index'); }