Passed
Push — master ( 1fd91b...909177 )
by Ivan
01:58
created

NavigationBuilder::getCurrent()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 3
nc 2
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Everlution\Navigation\Nested\Builder;
6
7
use Everlution\Navigation\Nested\AdvancedNavigationInterface;
8
use Everlution\Navigation\Nested\Container\ContainerNotFoundException;
9
use Everlution\Navigation\Nested\Container\NestableContainerInterface;
10
use Everlution\Navigation\Builder\MatcherInterface;
11
use Everlution\Navigation\Builder\NoCurrentItemFoundException;
12
use Everlution\Navigation\ContainerInterface;
13
14
/**
15
 * Class NavigationBuilder.
16
 *
17
 * @author Martin Lutter <[email protected]>
18
 */
19
class NavigationBuilder
20
{
21
    /** @var AdvancedNavigationInterface */
22
    private $navigation;
23
    /** @var ParentNode */
24
    private $root;
25
    /** @var ContainerInterface[] */
26
    private $stack = [];
27
    /** @var ContainerInterface[] */
28
    private $used = [];
29
    /** @var ParentNode */
30
    private $current;
31
    /** @var MatcherInterface */
32
    private $matcher;
33
34
    public function __construct(AdvancedNavigationInterface $container, MatcherInterface $matcher)
35
    {
36
        $this->navigation = $container;
37
        $this->matcher = $matcher;
38
        $this->build($matcher);
39
    }
40
41
    /**
42
     * @return ParentNode
43
     *
44
     * @throws NoCurrentItemFoundException
45
     */
46
    public function getCurrent(): ParentNode
47
    {
48
        if (!$this->current) {
49
            throw new NoCurrentItemFoundException();
50
        }
51
52
        return $this->current;
53
    }
54
55
    /**
56
     * @param MatcherInterface $matcher
57
     */
58
    private function build(MatcherInterface $matcher)
59
    {
60
        $this->root = new ParentNode($this->navigation->getRoot());
61
        $this->setCurrentNode($matcher, $this->root);
62
        $this->addToUsed($this->root);
63
64
        foreach ($this->navigation->getNavigationContainers() as $container) {
65
            $this->walkToRootContainer($container);
66
            $this->addContainersFromStack();
67
        }
68
    }
69
70
    /**
71
     * @param NestableContainerInterface $item
72
     *
73
     * @return NestableContainerInterface
74
     *
75
     * @throws ContainerNotFoundException
76
     */
77
    private function getParent(NestableContainerInterface $item): NestableContainerInterface
78
    {
79
        return $this->navigation->get($item->getParentContainer());
80
    }
81
82
    /**
83
     * @param NestableContainerInterface $container
84
     *
85
     * @return NestableContainerInterface
86
     */
87
    private function walkToRootContainer(NestableContainerInterface $container): NestableContainerInterface
88
    {
89
        $this->stack[] = $container;
90
91
        try {
92
            return $this->walkToRootContainer($this->getParent($container));
93
        } catch (ContainerNotFoundException $e) {
94
            return $container;
95
        }
96
    }
97
98
    private function addContainersFromStack(): void
99
    {
100
        $root = $this->root;
101
        while ($container = array_pop($this->stack)) {
102
            if (false === $this->isUsed($container)) {
103
                $this->addContainer($root, $container);
104
            }
105
106
            $root = $root->get(get_class($container));
107
        }
108
    }
109
110
    /**
111
     * @param ParentNode $root
112
     * @param NestableContainerInterface $container
113
     */
114
    private function addContainer(ParentNode $root, NestableContainerInterface $container): void
115
    {
116
        $parentNode = new ParentNode($container);
117
118
        $root->addChild($parentNode);
119
        $this->addToUsed($parentNode);
120
        $this->setCurrentNode($this->matcher, $parentNode);
121
    }
122
123
    /**
124
     * @param MatcherInterface $matcher
125
     * @param ParentNode $node
126
     */
127
    private function setCurrentNode(MatcherInterface $matcher, ParentNode $node): void
128
    {
129
        if (!$this->current && $matcher->isCurrent($node->getContainer())) {
130
            $this->current = $node;
131
        }
132
    }
133
134
    /**
135
     * @param ContainerInterface $container
136
     *
137
     * @return bool
138
     */
139
    private function isUsed(ContainerInterface $container): bool
140
    {
141
        $name = get_class($container);
142
143
        return isset($this->used[$name]);
144
    }
145
146
    /**
147
     * @param ParentNode $node
148
     */
149
    private function addToUsed(ParentNode $node): void
150
    {
151
        $name = get_class($node->getContainer());
152
        $this->used[$name] = $node;
153
    }
154
}
155