Lines 90.00% 36 / 40
Methods 50.00% 3 / 6
Classes 0.00% 0 / 1
Covered by tests of size
Name Lines Methods CRAP
 getNodes 90.90% 10 / 11 0.00% 0 / 1 4.01
 getChildren 100.00% 1 / 1 100.00% 1 / 1 2
 getChildNodes 100.00% 7 / 7 100.00% 1 / 1 3
 getBrothers 100.00% 2 / 2 100.00% 1 / 1 1
 computeLevel 90.90% 10 / 11 0.00% 0 / 1 5.02
 getValidParentId 75.00% 6 / 8 0.00% 0 / 1 5.39
22final class TreePathResolver
23{
24    /**
25     * Returns a node path from root to the given category id.
26     *
27     * @param array<int, array<string, mixed>> $categoryName Map id => row (needs parent_id)
28     * @return array<int>
29     */
30    public function getNodes(array $categoryName, int $categoryId): array
31    {
32        if ($categoryId <= 0) {
33            return [];
34        }
35
36        $nodes = [$categoryId];
37        $currentCategoryId = $categoryId;
38
39        while ($currentCategoryId > 0) {
40            $parentId = $this->getValidParentId($categoryName, $currentCategoryId);
41            if ($parentId === null) {
42                break;
43            }
44
45            array_unshift($nodes, $parentId);
46            $currentCategoryId = $parentId;
47        }
48
49        return $nodes;
50    }
51
52    /**
53     * Returns direct children IDs for a category.
54     *
55     * @param array<int, array<int, array<string, mixed>>> $childrenMap parentId => [childId => row]
56     * @return array<int>
57     */
58    public function getChildren(array $childrenMap, int $categoryId): array
59    {
60        return array_key_exists($categoryId, $childrenMap) ? array_keys($childrenMap[$categoryId]) : [];
61    }
62
63    /**
64     * Returns all descendant IDs of a category.
65     *
66     * @param array<int, array<int, array<string, mixed>>> $childrenMap
67     * @return array<int>
68     */
69    public function getChildNodes(array $childrenMap, int $categoryId): array
70    {
71        $result = [];
72        if (!array_key_exists($categoryId, $childrenMap)) {
73            return $result;
74        }
75
76        foreach (array_keys($childrenMap[$categoryId]) as $childId) {
77            $result[] = (int) $childId;
78            $result = array_merge($result, $this->getChildNodes($childrenMap, (int) $childId));
79        }
80
81        return $result;
82    }
83
84    /**
85     * Returns siblings (brothers) of a category including itself.
86     *
87     * @param array<int, array<string, mixed>> $categoryName
88     * @param array<int, array<int, array<string, mixed>>> $childrenMap
89     * @return array<int>
90     */
91    public function getBrothers(array $categoryName, array $childrenMap, int $categoryId): array
92    {
93        $parentId = (int) ($categoryName[$categoryId]['parent_id'] ?? 0);
94        return $this->getChildren($childrenMap, $parentId);
95    }
96
97    /**
98     * Computes the depth level of a category within the tree (root has 0).
99     *
100     * @param array<int, array<string, mixed>> $categoryName Map id => row (needs parent_id)
101     */
102    public function computeLevel(array $categoryName, int $categoryId): int
103    {
104        $alreadyListed = [$categoryId];
105        $level = 0;
106        while (
107            array_key_exists($categoryId, $categoryName)
108            && array_key_exists('parent_id', $categoryName[$categoryId])
109            && (int) $categoryName[$categoryId]['parent_id'] !== 0
110        ) {
111            ++$level;
112            $categoryId = (int) $categoryName[$categoryId]['parent_id'];
113            if (in_array($categoryId, $alreadyListed, strict: true)) {
114                break;
115            }
116
117            $alreadyListed[] = $categoryId;
118        }
119
120        return $level;
121    }
122
123    /**
124     * Gets valid parent ID for a category, or null if none exists.
125     *
126     * @param array<int, array<string, mixed>> $categoryName
127     */
128    private function getValidParentId(array $categoryName, int $currentCategoryId): ?int
129    {
130        if (!array_key_exists($currentCategoryId, $categoryName)) {
131            return null;
132        }
133
134        $parentId = (int) ($categoryName[$currentCategoryId]['parent_id'] ?? 0);
135
136        if ($parentId <= 0 || $parentId === $currentCategoryId) {
137            return null;
138        }
139
140        if (!array_key_exists($parentId, $categoryName)) {
141            return null;
142        }
143
144        return $parentId;
145    }
146}