Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
32 / 32
100.00% covered (success)
100.00%
10 / 10
CRAP
100.00% covered (success)
100.00%
1 / 1
Navigation
100.00% covered (success)
100.00%
32 / 32
100.00% covered (success)
100.00%
10 / 10
14
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 section
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 collection
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 children
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 items
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 refs
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 ref
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 payload
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 register
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 serialize
100.00% covered (success)
100.00%
18 / 18
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;
9
10final class Navigation
11{
12    private readonly Section $root;
13
14    /** @var array<string, Collection> */
15    private array $collections = [];
16
17    public function __construct()
18    {
19        $this->root = new Section('_root', Closure::fromCallable([$this, 'register']));
20    }
21
22    public function section(string $label): Section
23    {
24        return $this->root->section($label);
25    }
26
27    /** @param class-string<Collection> $class */
28    public function collection(string $class): Collection
29    {
30        return $this->root->collection($class);
31    }
32
33    /** @return list<NavigationItem> */
34    public function children(): array
35    {
36        return $this->root->children();
37    }
38
39    /** @return list<NavigationItem> */
40    public function items(): array
41    {
42        return $this->children();
43    }
44
45    /** @return array<string, Collection> */
46    public function refs(): array
47    {
48        return $this->collections;
49    }
50
51    public function ref(string $handle): Collection
52    {
53        if (!isset($this->collections[$handle])) {
54            throw new RuntimeException('Unknown collection handle: ' . $handle);
55        }
56
57        return $this->collections[$handle];
58    }
59
60    public function payload(): array
61    {
62        return $this->serialize($this->items());
63    }
64
65    private function register(Collection $collection): void
66    {
67        $handle = $collection->slug();
68        assert(is_string($handle), 'Collection slugs must be strings');
69
70        if (isset($this->collections[$handle])) {
71            throw new RuntimeException('Duplicate collection handle: ' . $handle);
72        }
73
74        $this->collections[$handle] = $collection;
75    }
76
77    /**
78     * @param list<NavigationItem> $items
79     * @return list<array<string, mixed>>
80     */
81    private function serialize(array $items): array
82    {
83        $result = [];
84
85        foreach ($items as $item) {
86            if ($item instanceof Section) {
87                $result[] = [
88                    'type' => 'section',
89                    'name' => $item->meta->label,
90                    'meta' => $item->meta->array(),
91                    'children' => $this->serialize($item->children()),
92                ];
93
94                continue;
95            }
96
97            $result[] = [
98                'type' => 'collection',
99                'slug' => $item->slug(),
100                'name' => $item->meta->label,
101                'meta' => $item->meta->array(),
102                'children' => [],
103            ];
104        }
105
106        return $result;
107    }
108}