Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
86.96% covered (warning)
86.96%
20 / 23
66.67% covered (warning)
66.67%
2 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
RecordHierarchyApiController
86.36% covered (warning)
86.36%
19 / 22
66.67% covered (warning)
66.67%
2 / 3
6.09
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
 actionHierarchy
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
2
 handleHierarchyException
40.00% covered (danger)
40.00%
2 / 5
0.00% covered (danger)
0.00%
0 / 1
4.94
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\Core\Engine\Presentation\Api;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11use App\Core\Engine\Application\Security\PermissionGuard;
12use App\Core\Engine\Application\Service\HierarchyService;
13use App\Core\Engine\Domain\Exception\ModuleNotFoundException;
14use App\Core\Engine\Domain\Exception\PermissionDeniedException;
15use App\Core\Engine\Domain\Model\PermissionContext;
16use App\Core\Engine\Domain\Repository\MetadataRepositoryInterface;
17use App\Shared\Infrastructure\Http\ApiResponseTrait;
18use Nyholm\Psr7\Factory\Psr17Factory;
19use Psr\Http\Message\ResponseInterface;
20
21/**
22 * Record Hierarchy REST API Controller.
23 *
24 * Provides endpoints for retrieving hierarchical record trees and related record counts.
25 *
26 * Routes:
27 *   GET /api/v1/engine/{module}/{id}/hierarchy -> actionHierarchy()
28 *
29 * @package App\Core\Engine\Presentation\Api
30 */
31final readonly class RecordHierarchyApiController
32{
33    use ApiResponseTrait;
34
35    /**
36     * @param MetadataRepositoryInterface $metadata         Metadata repository.
37     * @param PermissionGuard             $guard            Access control guard.
38     * @param HierarchyService            $hierarchyService Hierarchy calculation service.
39     * @param Psr17Factory                $psr17            PSR-17 response factory.
40     */
41    public function __construct(
42        private MetadataRepositoryInterface $metadata,
43        private PermissionGuard             $guard,
44        private HierarchyService            $hierarchyService,
45        private Psr17Factory                $psr17
46    ) {
47    }
48
49    /**
50     * Handles GET /api/v1/engine/{module}/{id}/hierarchy.
51     *
52     * @param string            $moduleName Module machine name.
53     * @param int               $id         Record primary key.
54     * @param PermissionContext $context    Security context.
55     * @return ResponseInterface JSON response with tree payload.
56     */
57    public function actionHierarchy(
58        string            $moduleName,
59        int               $id,
60        PermissionContext $context
61    ): ResponseInterface {
62        try {
63            $module = $this->metadata->findModule($moduleName);
64            $this->guard->assertReadAccess($module, $context);
65
66            $fields = $this->metadata->findFields($module->id);
67            $result = $this->hierarchyService->getHierarchy(
68                $module->name,
69                $id,
70                $module->tableName,
71                $fields,
72                $module->routeUrl
73            );
74
75            return $this->jsonSuccess($this->psr17, array_merge([
76                'module'    => $module->name,
77                'record_id' => $id,
78            ], $result));
79        } catch (\Throwable $e) {
80            return $this->handleHierarchyException($e);
81        }
82    }
83
84    /**
85     * Maps exceptions to appropriate JSON error responses.
86     *
87     * @param \Throwable $e Caught exception.
88     * @return ResponseInterface Error response.
89     */
90    private function handleHierarchyException(\Throwable $e): ResponseInterface
91    {
92        if ($e instanceof ModuleNotFoundException) {
93            return $this->jsonError($this->psr17, 'Module not found.', 404);
94        }
95        if ($e instanceof PermissionDeniedException) {
96            return $this->jsonError($this->psr17, 'Access denied.', 403);
97        }
98        return $this->jsonError($this->psr17, 'Server error: ' . $e->getMessage(), 500);
99    }
100}