Passed
Push — html ( 4d98c2...e7bfd2 )
by Peter
03:09
created

Navigation::rewind()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace AbterPhp\Framework\Navigation;
6
7
use AbterPhp\Framework\Constant\Html5;
8
use AbterPhp\Framework\Helper\Debug;
9
use AbterPhp\Framework\Html\Attribute;
10
use AbterPhp\Framework\Html\Contentless;
11
use AbterPhp\Framework\Html\Helper\Collection;
12
use AbterPhp\Framework\Html\INode;
13
use AbterPhp\Framework\Html\IStringer;
14
use AbterPhp\Framework\Html\ITag;
15
use AbterPhp\Framework\Html\Node;
16
use InvalidArgumentException;
17
use LogicException;
18
19
class Navigation extends Contentless
20
{
21
    public const ROLE_NAVIGATION = 'navigation';
22
23
    public const INTENT_NAVBAR    = 'navbar';
24
    public const INTENT_FOOTER    = 'footer';
25
    public const INTENT_PRIMARY   = 'primary';
26
    public const INTENT_SECONDARY = 'secondary';
27
28
    protected const DEFAULT_TAG = Html5::TAG_UL;
29
    protected const CONTENT_TYPE = Item::class;
30
    protected const SEPARATOR = "\n";
31
32
    protected const ERROR_INVALID_TAG_FOR_ITEM_CREATION = 'item creation is not allowed for navigation type: %s';
33
    protected const ERROR_NAVIGATION_OFFSET_NOT_ALLOWED = 'navigation offsets are not allowed';
34
    protected const ERROR_UNEXPECTED_OFFSET_VALUE_COMBINATION = 'Unexpected offset-value combination: %s %s';
35
36
    protected INode $prefix;
37
    protected INode $postfix;
38
39
    protected ?ITag $wrapper = null;
40
41
    /** @var array<int,Item[]> */
42
    protected array $itemsByWeight = [];
43
44
    /** @var int highest key of $itemsByWeight */
45
    protected int $highestWeight = 0;
46
47
    /** @var Item[] */
48
    protected array $content = [];
49
50
    /**
51
     * Navigation constructor.
52
     *
53
     * @param string[]                     $intents
54
     * @param array<string,Attribute>|null $attributes
55
     * @param string|null                  $tag
56
     */
57
    public function __construct(array $intents = [], ?array $attributes = null, ?string $tag = null)
58
    {
59
        parent::__construct($intents, $attributes, $tag);
60
61
        $this->prefix  = new Node();
62
        $this->postfix = new Node();
63
    }
64
65
    /**
66
     * @param int  $weight
67
     * @param Item ...$items
68
     *
69
     * @return $this
70
     */
71
    public function addWithWeight(int $weight, Item ...$items): self
72
    {
73
        foreach ($items as $item) {
74
            $this->itemsByWeight[$weight][] = $item;
75
        }
76
77
        $this->highestWeight = $this->highestWeight > $weight ? $this->highestWeight : $weight;
78
79
        return $this;
80
    }
81
82
    /**
83
     * @param INode ...$items
84
     *
85
     * @return $this
86
     */
87
    public function add(INode ...$items): self
88
    {
89
        foreach ($items as $item) {
90
            assert($item instanceof Item);
91
            $this->itemsByWeight[$this->highestWeight][] = $item;
92
        }
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return AbterPhp\Framework\Navigation\Navigation. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
93
    }
94
95
    protected function resort(): void
96
    {
97
        ksort($this->itemsByWeight);
98
99
        $content = [];
100
        foreach ($this->itemsByWeight as $items) {
101
            $content = array_merge($content, $items);
102
        }
103
104
        $this->content = $content;
105
    }
106
107
    /**
108
     * @return INode
109
     */
110
    public function getPrefix(): INode
111
    {
112
        return $this->prefix;
113
    }
114
115
    /**
116
     * @param INode $prefix
117
     *
118
     * @return $this
119
     */
120
    public function setPrefix(INode $prefix): self
121
    {
122
        $this->prefix = $prefix;
123
124
        return $this;
125
    }
126
127
    /**
128
     * @return INode
129
     */
130
    public function getPostfix(): INode
131
    {
132
        return $this->postfix;
133
    }
134
135
    /**
136
     * @param INode $postfix
137
     *
138
     * @return $this
139
     */
140
    public function setPostfix(INode $postfix): self
141
    {
142
        $this->postfix = $postfix;
143
144
        return $this;
145
    }
146
147
    /**
148
     * @return ITag|null
149
     */
150
    public function getWrapper(): ?ITag
151
    {
152
        return $this->wrapper;
153
    }
154
155
    /**
156
     * @param ITag|null $wrapper
157
     *
158
     * @return $this
159
     */
160
    public function setWrapper(?ITag $wrapper): self
161
    {
162
        $this->wrapper = $wrapper;
163
164
        return $this;
165
    }
166
167
    /**
168
     * @return INode[]
169
     */
170
    public function getNodes(): array
171
    {
172
        $this->resort();
173
174
        return $this->content;
175
    }
176
177
    /**
178
     * @return INode[]
179
     */
180
    public function getExtendedNodes(): array
181
    {
182
        $nodes = array_merge([$this->prefix, $this->postfix], $this->getNodes());
183
184
        if ($this->wrapper) {
185
            $nodes[] = $this->wrapper;
186
        }
187
188
        return $nodes;
189
    }
190
191
    /**
192
     * @return string
193
     */
194
    public function __toString(): string
195
    {
196
        $this->resort();
197
198
        $content = parent::__toString();
199
200
        if ($this->wrapper) {
201
            $content = (string)$this->wrapper->setContent($content);
202
        }
203
204
        $prefix  = $this->prefix ? (string)$this->prefix : '';
205
        $postfix = $this->postfix ? (string)$this->postfix : '';
206
207
        return $prefix . $content . $postfix;
208
    }
209
210
    /**
211
     * @param int $offset
212
     *
213
     * @return INode|null
214
     */
215
    public function offsetGet($offset): ?INode
216
    {
217
        $this->resort();
218
219
        return $this->content[$offset] ?? null;
220
    }
221
222
    /**
223
     * @return int
224
     */
225
    public function count(): int
226
    {
227
        $this->resort();
228
229
        return count($this->content);
230
    }
231
232
    public function rewind(): void
233
    {
234
        $this->resort();
235
236
        $this->position = 0;
237
    }
238
239
    /**
240
     * @param int|null $offset
241
     * @param INode    $value
242
     */
243
    public function offsetSet($offset, $value): void
244
    {
245
        assert($value instanceof Item);
246
247
        if (is_null($offset)) {
248
            $this->addWithWeight($this->highestWeight, $value);
249
            return;
250
        } elseif ($offset < 0 || $offset > $this->count()) {
251
            throw new InvalidArgumentException(static::ERROR_INVALID_OFFSET);
252
        }
253
254
        $count = 0;
255
        foreach ($this->itemsByWeight as $weight => $values) {
256
            $diff = $offset - $count;
257
            if (count($values) + $count >= $offset) {
258
                array_splice($this->itemsByWeight[$weight], $diff, 1, [$value]);
259
260
                break;
261
            } else {
262
                $count += count($values);
263
            }
264
        }
265
    }
266
267
    /**
268
     * @param int $offset
269
     */
270
    public function offsetUnset($offset): void
271
    {
272
        $count = 0;
273
        foreach ($this->itemsByWeight as $weight => $values) {
274
            $diff = $offset - count($values);
275
            if (count($values) + $count >= $offset) {
276
                array_splice($this->itemsByWeight[$weight], $diff, 1);
277
278
                break;
279
            } else {
280
                $count += count($values);
281
            }
282
        }
283
    }
284
285
    /**
286
     * @param array<string|IStringer>|string|IStringer|null $content
287
     *
288
     * @return $this
289
     */
290
    public function setContent($content): self
291
    {
292
        if (null === $content) {
293
            return $this;
294
        }
295
296
        throw new LogicException(self::ERROR_NO_CONTENT);
297
    }
298
}
299