Passed
Pull Request — master (#11)
by
unknown
02:23
created

NavigationBuilder::reorder()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 7
dl 0
loc 13
rs 10
c 0
b 0
f 0
cc 3
nc 4
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Everlution\Navigation\Builder;
6
7
use Everlution\Navigation\ContainerInterface;
8
use Everlution\Navigation\Item\ItemInterface;
9
use Everlution\Navigation\Item\NestableInterface;
10
use Everlution\Navigation\Item\SortableInterface;
11
use Everlution\Navigation\MutableContainer;
12
use Everlution\Navigation\OrderedContainer;
13
14
/**
15
 * Class NavigationBuilder.
16
 *
17
 * @author Ivan Barlog <[email protected]>
18
 */
19
class NavigationBuilder
20
{
21
    /** @var MatcherInterface */
22
    private $matcher;
23
    /** @var ContainerInterface */
24
    private $container;
25
    /** @var RootNode */
26
    private $root;
27
    /** @var ItemInterface[] */
28
    private $stack = [];
29
    /** @var ParentNode[] */
30
    private $used = [];
31
    /** @var ParentNode */
32
    private $current;
33
34
    public function __construct(ContainerInterface $container, MatcherInterface $matcher)
35
    {
36
        $this->matcher = $matcher;
37
        $this->container = new OrderedContainer($container);
38
        $this->build();
39
    }
40
41
    /**
42
     * @return ItemInterface[]
43
     *
44
     * @throws NoCurrentItemFoundException
45
     */
46
    public function getBreadcrumbs(): array
47
    {
48
        if (empty($this->stack)) {
49
            $this->getRootItem($this->getCurrent());
50
            $this->stack = array_reverse($this->stack);
51
            $this->stack = array_map(
52
                function (ItemInterface $item) {
53
                    return $this->used[MutableContainer::getIdentifier($item)];
54
                },
55
                $this->stack
56
            );
57
        }
58
59
60
        return $this->stack;
61
    }
62
63
    /**
64
     * @param ItemInterface $item
65
     *
66
     * @return bool
67
     *
68
     * @throws NoCurrentItemFoundException
69
     */
70
    public function isAncestor(ItemInterface $item): bool
71
    {
72
        $ancestors = $this->getBreadcrumbs();
73
        array_pop($ancestors);
74
75
        return in_array(
76
            $item,
77
            array_map(
78
                function (ParentNode $node) {
79
                    return $node->getItem();
80
                },
81
                $ancestors
82
            )
83
        );
84
    }
85
86
    /**
87
     * @return ItemInterface
88
     *
89
     * @throws NoCurrentItemFoundException
90
     */
91
    public function getCurrent(): ItemInterface
92
    {
93
        if (!$this->current) {
94
            throw new NoCurrentItemFoundException();
95
        }
96
97
        return $this->current->getItem();
98
    }
99
100
    public function getCurrentNode(): ParentNode
101
    {
102
        if (!$this->current) {
103
            throw new NoCurrentItemFoundException();
104
        }
105
106
        return $this->current;
107
    }
108
109
    public function getRoot(): RootNode
110
    {
111
        return $this->root;
112
    }
113
114
    private function build()
115
    {
116
        $this->root = new RootNode();
117
        foreach ($this->container->getItems() as $item) {
118
            if ($item instanceof NestableInterface) {
119
                $this->getRootItem($item);
120
                $this->addItemsFromStack();
121
                continue;
122
            }
123
124
            $this->addItem($this->root, $item);
125
        }
126
127
        foreach ($this->root->getChildren() as $item) {
128
            $this->reorder($item);
129
        }
130
    }
131
132
    private function reorder(ParentNode $item)
133
    {
134
        $children = $item->getChildren();
135
136
        foreach ($children as $child) {
137
            $this->reorder($child);
138
        }
139
140
        if (
141
            false === empty($children)
142
        ) {
143
            uasort($children, [$this, 'ascending']);
144
            $item->setChildren($children);
145
        }
146
147
    }
148
149
    private function ascending(ParentNode $first, ParentNode $second)
150
    {
151
        if (false === ($firstNode = $first->getItem()) instanceof SortableInterface) {
152
            return 0;
153
        }
154
155
        if (false === ($secondNode = $second->getItem()) instanceof SortableInterface) {
156
            return 1;
157
        }
158
159
        return $firstNode->getOrder() <=> $secondNode->getOrder();
160
    }
161
162
    private function getParent(NestableInterface $item): ItemInterface
163
    {
164
        return $this->container->get($item->getParent());
165
    }
166
167
    private function getRootItem(ItemInterface $item): ?ItemInterface
168
    {
169
        $this->stack[] = $item;
170
171
        if (!$item instanceof NestableInterface) {
172
            return null;
173
        }
174
175
        return $this->getRootItem($this->getParent($item));
176
    }
177
178
    private function addItemsFromStack(): void
179
    {
180
        $root = $this->root;
181
        while ($item = array_pop($this->stack)) {
182
            $this->addItem($root, $item);
183
            $root = $root->get(MutableContainer::getIdentifier($item));
184
        }
185
    }
186
187
    private function addItem(RootNode $root, ItemInterface $item): void
188
    {
189
        $name = MutableContainer::getIdentifier($item);
190
        if (array_key_exists($name, $this->used)) {
191
            return;
192
        }
193
194
        $parentNode = new ParentNode($item, $root);
195
        $this->used[$name] = $parentNode;
196
        $root->addChild($parentNode);
197
        $this->setCurrentItem($parentNode);
198
    }
199
200
    private function setCurrentItem(ParentNode $node): void
201
    {
202
        if (!$this->current && $this->matcher->isCurrent($node->getItem())) {
203
            $this->current = $node;
204
        }
205
    }
206
}
207