Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
92.86% covered (success)
92.86%
52 / 56
50.00% covered (danger)
50.00%
4 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
Section
92.86% covered (success)
92.86%
52 / 56
50.00% covered (danger)
50.00%
4 / 8
18.12
0.00% covered (danger)
0.00%
0 / 1
 __construct
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
2.02
 slug
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 children
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
5.05
 section
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 icon
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
2.01
 link
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 collection
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
3.01
 sort
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3declare(strict_types=1);
4
5namespace Cosray;
6
7use Closure;
8use Cosray\Collection\Ref;
9use Cosray\Collection\Schemas;
10use Cosray\Exception\RuntimeException;
11use Override;
12
13final class Section implements NavigationItem
14{
15    public readonly NavMeta $meta;
16
17    /** @var list<NavigationItem> */
18    private array $children = [];
19
20    private readonly ?Closure $onCollection;
21
22    private readonly Schemas $schemas;
23
24    public function __construct(
25        string $label,
26        ?Closure $onCollection = null,
27        ?Schemas $schemas = null,
28    ) {
29        $label = trim($label);
30
31        if ($label === '') {
32            throw new RuntimeException('Section labels must not be empty');
33        }
34
35        $this->meta = new NavMeta($label);
36        $this->onCollection = $onCollection;
37        $this->schemas = $schemas ?? new Schemas();
38    }
39
40    #[Override]
41    public function slug(): ?string
42    {
43        return null;
44    }
45
46    /** @return list<NavigationItem> */
47    #[Override]
48    public function children(): array
49    {
50        $visible = [];
51
52        foreach ($this->children as $item) {
53            if ($item->meta->hidden) {
54                continue;
55            }
56
57            if ($item instanceof self && $item->children() === []) {
58                continue;
59            }
60
61            $visible[] = $item;
62        }
63
64        return $this->sort($visible);
65    }
66
67    public function section(string $label): self
68    {
69        $section = new self($label, $this->onCollection, $this->schemas);
70        $this->children[] = $section;
71
72        return $section;
73    }
74
75    /** @param array<array-key, mixed> $args */
76    public function icon(string $id, array $args = []): static
77    {
78        $id = trim($id);
79
80        if ($id === '') {
81            throw new RuntimeException('Section icon ids must not be empty');
82        }
83
84        $this->meta->icon = [
85            'id' => $id,
86            'args' => $args,
87        ];
88
89        return $this;
90    }
91
92    public function link(string $label, string $url, ?string $activePrefix = null): NavLink
93    {
94        $link = new NavLink($label, $url, $activePrefix);
95        $this->children[] = $link;
96
97        return $link;
98    }
99
100    /** @param class-string<Collection> $class */
101    public function collection(string $class): Ref
102    {
103        if (!is_a($class, Collection::class, true)) {
104            throw new RuntimeException('Collections must extend ' . Collection::class);
105        }
106
107        $ref = new Ref(
108            $class,
109            $this->schemas->nav($class),
110            (string) $this->schemas->get($class, 'handle'),
111        );
112        $this->children[] = $ref;
113
114        if ($this->onCollection !== null) {
115            ($this->onCollection)($ref);
116        }
117
118        return $ref;
119    }
120
121    /**
122     * @param list<NavigationItem> $items
123     * @return list<NavigationItem>
124     */
125    private function sort(array $items): array
126    {
127        $indexed = [];
128
129        foreach ($items as $index => $item) {
130            $indexed[] = [
131                'index' => $index,
132                'item' => $item,
133            ];
134        }
135
136        usort($indexed, static function (array $left, array $right): int {
137            $cmp = $left['item']->meta->order <=> $right['item']->meta->order;
138
139            if ($cmp !== 0) {
140                return $cmp;
141            }
142
143            return $left['index'] <=> $right['index'];
144        });
145
146        return array_map(
147            static fn(array $item): NavigationItem => $item['item'],
148            $indexed,
149        );
150    }
151}