From 70f1a403e8c0cdd49de4b8635323eef9bd89cc91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sven=20L=C3=BCtje?= Date: Tue, 23 May 2023 09:52:31 +0200 Subject: [PATCH] Added a new sortOrder option to sort menu items when they are added to the menu. --- doc/06-Sorting-Menu-Items.md | 42 +++++++++++++ doc/{06-FAQ.md => 07-FAQ.md} | 0 src/Knp/Menu/Factory/CoreExtension.php | 2 + src/Knp/Menu/ItemInterface.php | 11 ++-- src/Knp/Menu/MenuItem.php | 60 ++++++++++++++++++- .../Knp/Menu/Tests/MenuItemSortOrderTest.php | 39 ++++++++++++ 6 files changed, 146 insertions(+), 8 deletions(-) create mode 100644 doc/06-Sorting-Menu-Items.md rename doc/{06-FAQ.md => 07-FAQ.md} (100%) create mode 100644 tests/Knp/Menu/Tests/MenuItemSortOrderTest.php diff --git a/doc/06-Sorting-Menu-Items.md b/doc/06-Sorting-Menu-Items.md new file mode 100644 index 00000000..df47e922 --- /dev/null +++ b/doc/06-Sorting-Menu-Items.md @@ -0,0 +1,42 @@ +Control the sort order of your menu items +========================================= + +There are 2 ways to control the order of your menu items: + +1. Reorder items by giving a sorted list of item names +2. Sort the items when adding them to the menu + +Reorder items by giving a sorted list of item names +----------------------- + +```php + $factory = new MenuFactory(); + $menu = new MenuItem('root', $factory); + $menu->addChild('c1'); + $menu->addChild('c2'); + $menu->addChild('c3'); + $menu->addChild('c4'); + + $menu->reorderChildren(['c4', 'c3', 'c2', 'c1']); + $menu->getChildren() //'c4', 'c3', 'c2', 'c1' +``` + +Sort the items when adding them to the menu +------------------------------------------- + +The items can be added in a sorted manner by using the `sortOrder` options. + +Caution: The order will be lost when using `ItemInterface->reorderChildren()`! + +```php + $factory = new MenuFactory(); + $menu = new MenuItem('root', $factory); + $menu->addChild('c1', ['sortOrder' => 2]); + $menu->addChild('c2', ['sortOrder' => 4]); + $menu->addChild('c3', ['sortOrder' => 1]); + $menu->addChild('c4', ['sortOrder' => 3]); + + $menu->getChildren() //'c1', 'c2', 'c3', 'c4' +``` + +Items without the `sortOrder` option will be just appended after the items with `sortOrder` in the order they're added. \ No newline at end of file diff --git a/doc/06-FAQ.md b/doc/07-FAQ.md similarity index 100% rename from doc/06-FAQ.md rename to doc/07-FAQ.md diff --git a/src/Knp/Menu/Factory/CoreExtension.php b/src/Knp/Menu/Factory/CoreExtension.php index 8cf17351..71d5f128 100644 --- a/src/Knp/Menu/Factory/CoreExtension.php +++ b/src/Knp/Menu/Factory/CoreExtension.php @@ -23,6 +23,7 @@ public function buildOptions(array $options): array 'current' => null, 'display' => true, 'displayChildren' => true, + 'sortOrder' => null, ], $options ); @@ -40,6 +41,7 @@ public function buildItem(ItemInterface $item, array $options): void ->setCurrent($options['current']) ->setDisplay($options['display']) ->setDisplayChildren($options['displayChildren']) + ->setSortOrder($options['sortOrder']) ; $this->buildExtras($item, $options); diff --git a/src/Knp/Menu/ItemInterface.php b/src/Knp/Menu/ItemInterface.php index a73d6e2a..30b3ce72 100644 --- a/src/Knp/Menu/ItemInterface.php +++ b/src/Knp/Menu/ItemInterface.php @@ -43,6 +43,10 @@ public function getUri(): ?string; */ public function setUri(?string $uri): self; + public function getSortOrder(): ?int; + + public function setSortOrder(int $sortOrder): self; + /** * Returns the label that will be used to render this menu item * @@ -162,14 +166,9 @@ public function setExtras(array $extras): self; /** * @param string $name The name of the extra to return * @param mixed $default The value to return if the extra doesn't exist - * - * @return mixed */ public function getExtra(string $name, $default = null); - /** - * @param mixed $value - */ public function setExtra(string $name, $value): self; public function getDisplayChildren(): bool; @@ -250,7 +249,7 @@ public function getParent(): ?self; * * Provides a fluent interface */ - public function setParent(?self $parent = null): self; + public function setParent(self $parent = null): self; /** * Return the children as an array of ItemInterface objects diff --git a/src/Knp/Menu/MenuItem.php b/src/Knp/Menu/MenuItem.php index bbd8908f..9b25c7b6 100644 --- a/src/Knp/Menu/MenuItem.php +++ b/src/Knp/Menu/MenuItem.php @@ -103,6 +103,8 @@ class MenuItem implements ItemInterface */ protected $factory; + protected ?int $sortOrder = null; + /** * Class constructor * @@ -326,6 +328,18 @@ public function setDisplay(bool $bool): ItemInterface return $this; } + public function getSortOrder(): ?int + { + return $this->sortOrder; + } + + public function setSortOrder(int $sortOrder = null): ItemInterface + { + $this->sortOrder = $sortOrder; + + return $this; + } + public function addChild($child, array $options = []): ItemInterface { if (!$child instanceof ItemInterface) { @@ -336,7 +350,49 @@ public function addChild($child, array $options = []): ItemInterface $child->setParent($this); - $this->children[$child->getName()] = $child; + $sortOrder = $child->getSortOrder(); + + $length = $this->count(); + if (null === $sortOrder || 0 === $length) { + $this->children[$child->getName()] = $child; + + return $child; + } + + return $this->addChildAtPosition($child); + } + + private function addChildAtPosition(ItemInterface $child): ItemInterface + { + $firstChildSortOrder = $this->children[\array_key_first($this->children)]->getSortOrder(); + if (null !== $firstChildSortOrder && $child->getSortOrder() < $firstChildSortOrder) { + $this->children = \array_merge([$child->getName() => $child], $this->children); + + return $child; + } + + $lastChildSortOrder = $this->children[\array_key_last($this->children)]->getSortOrder(); + if (null !== $lastChildSortOrder && $child->getSortOrder() > $lastChildSortOrder) { + $this->children[$child->getName()] = $child; + + return $child; + } + + $i = -1; + foreach ($this->children as $key => $loopedChild) { + ++$i; + if (null !== $loopedChild->getSortOrder() && $child->getSortOrder() >= $loopedChild->getSortOrder()) { + continue; + } + + $firstHalf = \array_slice($this->children, 0, $i); + $secondHalf = \array_slice($this->children, $i); + + $firstHalf[$child->getName()] = $child; + + $this->children = \array_merge($firstHalf, $secondHalf); + break; + } return $child; } @@ -405,7 +461,7 @@ public function getParent(): ?ItemInterface return $this->parent; } - public function setParent(?ItemInterface $parent = null): ItemInterface + public function setParent(ItemInterface $parent = null): ItemInterface { if ($parent === $this) { throw new \InvalidArgumentException('Item cannot be a child of itself'); diff --git a/tests/Knp/Menu/Tests/MenuItemSortOrderTest.php b/tests/Knp/Menu/Tests/MenuItemSortOrderTest.php new file mode 100644 index 00000000..bda94408 --- /dev/null +++ b/tests/Knp/Menu/Tests/MenuItemSortOrderTest.php @@ -0,0 +1,39 @@ +addChild('c1', ['sortOrder' => 1]); + $menu->addChild('c2'); + $menu->addChild('c3', ['sortOrder' => 2]); + $menu->addChild('c4'); + $menu->addChild('c5'); + $menu->addChild('c6', ['sortOrder' => 1]); + $menu->addChild('c7', ['sortOrder' => 1]); + + $arr = \array_keys($menu->getChildren()); + $this->assertEquals(['c1', 'c6', 'c7', 'c3', 'c2', 'c4', 'c5'], $arr); + } + + public function testItemsAreAddedInTheCorrectOrder(): void + { + $factory = new MenuFactory(); + $menu = new MenuItem('root', $factory); + $menu->addChild('c1', ['sortOrder' => 2]); + $menu->addChild('c2', ['sortOrder' => 4]); + $menu->addChild('c3', ['sortOrder' => 1]); + $menu->addChild('c4', ['sortOrder' => 3]); + + $arr = \array_keys($menu->getChildren()); + $this->assertEquals(['c3', 'c1', 'c4', 'c2'], $arr); + } +}