Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
StructureTreeBuilder
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
4 / 4
8
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
 buildTree
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 buildFlattenedTreeForSelect
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 flattenNode
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2
3declare(strict_types=1);
4
5/** @license For full copyright and license information, please see the LICENSE.md file. */
6
7namespace App\Modules\Structure\Application\Service;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11use App\Modules\Structure\Domain\Model\StructureNode;
12use App\Modules\Structure\Domain\Repository\StructureRepositoryInterface;
13
14/**
15 * Structure Tree Builder Application Service.
16 *
17 * Assembles and formats organizational tree nodes for presentation views,
18 * DataGrids and hierarchical selectors.
19 *
20 * @package App\Modules\Structure\Application\Service
21 */
22final readonly class StructureTreeBuilder implements StructureTreeBuilderInterface
23{
24    /**
25     * StructureTreeBuilder constructor.
26     *
27     * @param StructureRepositoryInterface $structureRepo Repository instance.
28     */
29    public function __construct(
30        private StructureRepositoryInterface $structureRepo
31    ) {
32    }
33
34    /**
35     * Builds full active organizational structure hierarchy tree.
36     *
37     * @return array<int, array<string, mixed>> Tree array representation.
38     */
39    public function buildTree(): array
40    {
41        $rootNodes = $this->structureRepo->getTree();
42
43        return array_map(
44            static fn(StructureNode $node): array => $node->toArray(),
45            $rootNodes
46        );
47    }
48
49    /**
50     * Builds flat list with depth indentation for dropdown selects.
51     *
52     * @param int|null $excludeId Optional node ID to exclude (e.g. self when moving parent).
53     * @return array<int, array{id: int, label: string, depth: int, disabled: bool, users_count: int}>
54     */
55    public function buildFlattenedTreeForSelect(?int $excludeId = null): array
56    {
57        $rootNodes = $this->structureRepo->getTree();
58        $result = [];
59
60        foreach ($rootNodes as $node) {
61            $this->flattenNode($node, 0, $result, $excludeId);
62        }
63
64        return $result;
65    }
66
67    /**
68     * Recursively flattens tree nodes into indented list.
69     *
70     * @param StructureNode $node Current node.
71     * @param int $depth Indentation depth.
72     * @param array<int, array<string, mixed>> $result Accumulator list.
73     * @param int|null $excludeId Excluded branch ID.
74     * @return void
75     */
76    private function flattenNode(
77        StructureNode $node,
78        int $depth,
79        array &$result,
80        ?int $excludeId
81    ): void {
82        $nodeId = (int)$node->getId();
83        if ($excludeId !== null && $nodeId === $excludeId) {
84            return;
85        }
86
87        $indent = str_repeat('— ', $depth);
88        $result[] = [
89            'id'          => $nodeId,
90            'label'       => $indent . $node->getName(),
91            'depth'       => $depth,
92            'type'        => $node->getStructureType()->value,
93            'type_label'  => $node->getStructureType()->label(),
94            'disabled'    => !$node->hasAssignedUsers(),
95            'users_count' => $node->getAssignedUsersCount(),
96            'user_names'  => $node->getAssignedUserNames(),
97        ];
98
99        foreach ($node->getChildren() as $child) {
100            $this->flattenNode($child, $depth + 1, $result, $excludeId);
101        }
102    }
103}