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