Lines 89.65% 26 / 29
Methods 66.66% 6 / 9
Classes 0.00% 0 / 1
Covered by tests of size
Name Lines Methods CRAP
 __construct 100.00% 3 / 3 100.00% 1 / 1 1
 buildAdminCategoryTree 87.50% 7 / 8 0.00% 0 / 1 4.03
 buildLinearTree 91.66% 11 / 12 0.00% 0 / 1 4.01
 getNodes 100.00% 1 / 1 100.00% 1 / 1 1
 getChildren 100.00% 1 / 1 100.00% 1 / 1 1
 getChildNodes 100.00% 1 / 1 100.00% 1 / 1 1
 getBrothers 0.00% 0 / 1 0.00% 0 / 1 2
 buildTree 100.00% 1 / 1 100.00% 1 / 1 1
 computeLevel 100.00% 1 / 1 100.00% 1 / 1 1
22class TreeBuilder implements TreeBuilderInterface
23{
24    private TreePathResolver $treePathResolver;
25
26    private TreeVisualizer $treeVisualizer;
27
28    private CategoryValidator $categoryValidator;
29
30    public function __construct()
31    {
32        $this->treePathResolver = new TreePathResolver();
33        $this->treeVisualizer = new TreeVisualizer($this->treePathResolver);
34        $this->categoryValidator = new CategoryValidator();
35    }
36
37    /**
38     * Builds the category tree for the admin category overview.
39     * Pure function: does not mutate input.
40     *
41     * @param array<int, array<string, mixed>> $categories Flat list keyed or not by id, each with id,parent_id
42     * @param int $parentId Parent id to expand from
43     * @return array<int, array<array-key, mixed>> Map of categoryId => recursive subtree
44     */
45    public function buildAdminCategoryTree(array $categories, int $parentId = 0): array
46    {
47        $result = [];
48
49        foreach ($categories as $category) {
50            if (!$this->categoryValidator->isValidCategory($category)) {
51                continue;
52            }
53
54            if ((int) $category['parent_id'] === $parentId) {
55                $categoryId = (int) $category['id'];
56                $result[$categoryId] = $this->buildAdminCategoryTree($categories, $categoryId);
57            }
58        }
59
60        return $result;
61    }
62
63    /**
64     * Builds a linear tree (array) with indentation information.
65     * Matches legacy Category::buildCategoryTree output shape.
66     *
67     * @param array<int, array<string, mixed>> $categories
68     * @return array<int, array<string, mixed>>
69     */
70    public function buildLinearTree(array $categories, int $parentId = 0, int $indent = 0): array
71    {
72        $childrenIds = $this->categoryValidator->collectDirectChildren($categories, $parentId);
73
74        if ($childrenIds === []) {
75            return [];
76        }
77
78        $catTree = [];
79        foreach ($childrenIds as $childId) {
80            if (!array_key_exists($childId, $categories)) {
81                continue;
82            }
83
84            $row = $categories[$childId];
85            $row['indent'] = $indent;
86            $catTree[] = $row;
87            $catTree = array_merge($catTree, $this->buildLinearTree($categories, (int) $row['id'], $indent + 1));
88        }
89
90        return $catTree;
91    }
92
93    /**
94     * Delegates to TreePathResolver::getNodes()
95     *
96     * @param array<int, array<string, mixed>> $categoryNames Map id => row (needs parent_id)
97     * @return array<int>
98     */
99    public function getNodes(array $categoryNames, int $categoryId): array
100    {
101        return $this->treePathResolver->getNodes($categoryNames, $categoryId);
102    }
103
104    /**
105     * Delegates to TreePathResolver::getChildren()
106     *
107     * @param array<int, array<int, array<string, mixed>>> $childrenMap parentId => [childId => row]
108     * @return array<int>
109     */
110    public function getChildren(array $childrenMap, int $categoryId): array
111    {
112        return $this->treePathResolver->getChildren($childrenMap, $categoryId);
113    }
114
115    /**
116     * Delegates to TreePathResolver::getChildNodes()
117     *
118     * @param array<int, array<int, array<string, mixed>>> $childrenMap
119     * @return array<int>
120     */
121    public function getChildNodes(array $childrenMap, int $categoryId): array
122    {
123        return $this->treePathResolver->getChildNodes($childrenMap, $categoryId);
124    }
125
126    /**
127     * Delegates to TreePathResolver::getBrothers()
128     *
129     * @param array<int, array<string, mixed>> $categoryNames
130     * @param array<int, array<int, array<string, mixed>>> $children
131     * @return array<int>
132     */
133    public function getBrothers(array $categoryNames, array $children, int $categoryId): array
134    {
135        return $this->treePathResolver->getBrothers($categoryNames, $children, $categoryId);
136    }
137
138    /**
139     * Delegates to TreeVisualizer::buildTree()
140     *
141     * @param array<int, array<string, mixed>> $categoryNames
142     * @param array<int, array<int, array<string, mixed>>> $children
143     * @return array<int, string>
144     */
145    public function buildTree(array $categoryNames, array $children, int $categoryId): array
146    {
147        return $this->treeVisualizer->buildTree($categoryNames, $children, $categoryId);
148    }
149
150    /**
151     * Delegates to TreePathResolver::computeLevel()
152     *
153     * @param array<int, array<string, mixed>> $categoryNames Map id => row (needs parent_id)
154     */
155    public function computeLevel(array $categoryNames, int $categoryId): int
156    {
157        return $this->treePathResolver->computeLevel($categoryNames, $categoryId);
158    }
159}