Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
1.75% covered (danger)
1.75%
1 / 57
33.33% covered (danger)
33.33%
1 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
MenuService
1.75% covered (danger)
1.75%
1 / 57
33.33% covered (danger)
33.33%
1 / 3
104.83
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getMenuTree
0.00% covered (danger)
0.00%
0 / 49
0.00% covered (danger)
0.00%
0 / 1
42
 buildSubTree
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3declare(strict_types=1);
4
5namespace App\Domain\Menu\Service;
6
7use App\Domain\Menu\Entity\MenuRecord;
8use App\Domain\Menu\Repository\MenuRecordRepository;
9use App\Domain\Menu\Repository\MenuSortRepository;
10
11 class MenuService
12{
13    public function __construct(
14        private MenuRecordRepository $recordRepository,
15        private MenuSortRepository $sortRepository
16    ) {}
17
18    /**
19     * Build nested menu tree for sidebar rendering and summary view builder.
20     *
21     * @return array<int, array<string, mixed>>
22     */
23    public function getMenuTree(string $recordsTable = 'srv_menu_records', string $sortTable = 'srv_menu_sort'): array
24    {
25        $records = $this->recordRepository->findAll($recordsTable);
26        $sorts = $this->sortRepository->findAll($sortTable);
27
28        /** @var array<string, MenuRecord> $recordsMap */
29        $recordsMap = [];
30        foreach ($records as $record) {
31            $recordsMap[$record->getId()] = $record;
32        }
33
34        // If no sort records exist yet, fall back to records order
35        if (empty($sorts)) {
36            $fallback = [];
37            foreach ($records as $index => $rec) {
38                $fallback[] = [
39                    'sort_id' => 'sort-' . $rec->getId(),
40                    'id' => $rec->getId(),
41                    'type' => $rec->getType(),
42                    'title' => $rec->getTitle(),
43                    'icon' => $rec->getIcon() ?? 'bi-circle',
44                    'url' => $rec->getUrl() ?? '#',
45                    'badge_text' => $rec->getBadgeText(),
46                    'badge_color' => $rec->getBadgeColor() ?? 'primary',
47                    'target' => $rec->getTarget() ?? '_self',
48                    'status' => $rec->getStatus(),
49                    'parent_id' => null,
50                    'sort_order' => $index + 1,
51                    'children' => [],
52                ];
53            }
54            return $fallback;
55        }
56
57        /** @var array<string, array<string, mixed>> $nodesByRecordId */
58        $nodesByRecordId = [];
59        /** @var array<string, array<int, array<string, mixed>>> $childrenByParentId */
60        $childrenByParentId = [];
61
62        foreach ($sorts as $sort) {
63            $record = $recordsMap[$sort->getMenuRecordId()] ?? null;
64            if ($record === null) {
65                continue;
66            }
67
68            $node = [
69                'sort_id' => $sort->getId(),
70                'id' => $record->getId(),
71                'type' => $record->getType(),
72                'title' => $record->getTitle(),
73                'icon' => $record->getIcon() ?? 'bi-circle',
74                'url' => $record->getUrl() ?? '#',
75                'badge_text' => $record->getBadgeText(),
76                'badge_color' => $record->getBadgeColor() ?? 'primary',
77                'target' => $record->getTarget() ?? '_self',
78                'status' => $record->getStatus(),
79                'parent_id' => $sort->getParentId(),
80                'sort_order' => $sort->getSortOrder(),
81                'children' => [],
82            ];
83
84            $nodesByRecordId[$record->getId()] = $node;
85            $pId = $sort->getParentId() ?? 'root';
86            $childrenByParentId[$pId][] = $node;
87        }
88
89        // Build tree recursively
90        return $this->buildSubTree('root', $childrenByParentId);
91    }
92
93    /**
94     * @param array<string, array<int, array<string, mixed>>> $childrenByParentId
95     * @return array<int, array<string, mixed>>
96     */
97    private function buildSubTree(string $parentId, array $childrenByParentId): array
98    {
99        $items = $childrenByParentId[$parentId] ?? [];
100        usort($items, static fn(array $a, array $b): int => ($a['sort_order'] <=> $b['sort_order']));
101
102        foreach ($items as &$item) {
103            $recordId = is_string($item['id'] ?? null) ? (string) $item['id'] : '';
104            $item['children'] = $this->buildSubTree($recordId, $childrenByParentId);
105        }
106        unset($item);
107
108        return $items;
109    }
110}