NavigationBuilder::setCurrentItem()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 2
c 1
b 1
f 0
dl 0
loc 4
rs 10
cc 3
nc 2
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\GeneratorContainer;
9
use Everlution\Navigation\Item\ItemInterface;
10
use Everlution\Navigation\Item\NestableInterface;
11
use Everlution\Navigation\Item\SortableInterface;
12
use Everlution\Navigation\Helper\ItemHelper;
13
use Everlution\Navigation\OrderedContainer;
14
15
/**
16
 * Class NavigationBuilder.
17
 *
18
 * @author Ivan Barlog <[email protected]>
19
 */
20
class NavigationBuilder
21
{
22
    /** @var MatcherInterface */
23
    private $matcher;
24
    /** @var ContainerInterface */
25
    private $container;
26
    /** @var RootNode */
27
    private $root;
28
    /** @var ItemInterface[] */
29
    private $stack = [];
30
    /** @var ParentNode[] */
31
    private $used = [];
32
    /** @var ParentNode */
33
    private $current;
34
35
    public function __construct(ContainerInterface $container, MatcherInterface $matcher)
36
    {
37
        $this->matcher = $matcher;
38
        $this->container = new OrderedContainer(new GeneratorContainer($container));
39
        $this->build();
40
    }
41
42
    /**
43
     * @return ItemInterface[]
44
     *
45
     * @throws NoCurrentItemFoundException
46
     */
47
    public function getBreadcrumbs(): array
48
    {
49
        if (empty($this->stack)) {
50
            $this->getRootItem($this->getCurrent());
51
            $this->stack = array_reverse($this->stack);
52
            $this->stack = array_map(
53
                function (ItemInterface $item) {
54
                    return $this->used[ItemHelper::getIdentifier($item)];
55
                },
56
                $this->stack
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 (false === empty($children)) {
141
            uasort($children, [$this, 'ascending']);
142
            $item->setChildren($children);
143
        }
144
    }
145
146
    private function ascending(ParentNode $first, ParentNode $second)
147
    {
148
        if (false === ($firstNode = $first->getItem()) instanceof SortableInterface) {
149
            return 0;
150
        }
151
152
        if (false === ($secondNode = $second->getItem()) instanceof SortableInterface) {
153
            return 1;
154
        }
155
156
        return $firstNode->getOrder() <=> $secondNode->getOrder();
157
    }
158
159
    private function getParent(NestableInterface $item): ItemInterface
160
    {
161
        return $this->container->get($item->getParent());
162
    }
163
164
    private function getRootItem(ItemInterface $item): ?ItemInterface
165
    {
166
        $this->stack[] = $item;
167
168
        if (!$item instanceof NestableInterface) {
169
            return null;
170
        }
171
172
        return $this->getRootItem($this->getParent($item));
173
    }
174
175
    private function addItemsFromStack(): void
176
    {
177
        $root = $this->root;
178
        while ($item = array_pop($this->stack)) {
179
            $this->addItem($root, $item);
180
            $root = $root->get(ItemHelper::getIdentifier($item));
181
        }
182
    }
183
184
    private function addItem(RootNode $root, ItemInterface $item): void
185
    {
186
        $name = ItemHelper::getIdentifier($item);
187
        if (array_key_exists($name, $this->used)) {
188
            return;
189
        }
190
191
        $parentNode = new ParentNode($item, $root);
192
        $this->used[$name] = $parentNode;
193
        $root->addChild($parentNode);
194
        $this->setCurrentItem($parentNode);
195
    }
196
197
    private function setCurrentItem(ParentNode $node): void
198
    {
199
        if (!$this->current && $this->matcher->isCurrent($node->getItem())) {
200
            $this->current = $node;
201
        }
202
    }
203
}
204