Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.62% covered (success)
93.62%
44 / 47
77.78% covered (warning)
77.78%
7 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
GetItemAction
93.62% covered (success)
93.62%
44 / 47
77.78% covered (warning)
77.78%
7 / 9
17.08
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
 isAuthorized
83.33% covered (warning)
83.33%
10 / 12
0.00% covered (danger)
0.00%
0 / 1
7.23
 __invoke
94.44% covered (success)
94.44%
17 / 18
0.00% covered (danger)
0.00%
0 / 1
2.00
 getInputType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getOutputType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPossibleActionResponseStatuses
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 getDescription
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getTags
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRouteAttributes
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2namespace Apie\Common\Actions;
3
4use Apie\Common\Events\ApieResourceRead;
5use Apie\Common\Other\LockUtil;
6use Apie\Core\Actions\ActionInterface;
7use Apie\Core\Actions\ActionResponse;
8use Apie\Core\Actions\ActionResponseStatus;
9use Apie\Core\Actions\ActionResponseStatusList;
10use Apie\Core\Actions\ApieFacadeInterface;
11use Apie\Core\Attributes\Description;
12use Apie\Core\BoundedContext\BoundedContextId;
13use Apie\Core\Context\ApieContext;
14use Apie\Core\ContextConstants;
15use Apie\Core\Entities\EntityInterface;
16use Apie\Core\Exceptions\InvalidTypeException;
17use Apie\Core\IdentifierUtils;
18use Apie\Core\Lists\PermissionList;
19use Apie\Core\Lists\StringList;
20use Apie\Core\Permissions\PermissionInterface;
21use Apie\Core\Permissions\RequiresPermissionsInterface;
22use Apie\Core\Utils\EntityUtils;
23use LogicException;
24use ReflectionClass;
25
26/**
27 * Action to get a single item resource.
28 */
29final class GetItemAction implements ActionInterface
30{
31    public function __construct(private readonly ApieFacadeInterface $apieFacade)
32    {
33    }
34
35    public static function isAuthorized(ApieContext $context, bool $runtimeChecks, bool $throwError = false): bool
36    {
37        $refl = new ReflectionClass($context->getContext(ContextConstants::RESOURCE_NAME, $throwError));
38        $resource = $context->getContext(ContextConstants::RESOURCE, false);
39        if ($resource instanceof RequiresPermissionsInterface) {
40            $requiredPermissions = $resource->getRequiredPermissions();
41            $user = $context->getContext(ContextConstants::AUTHENTICATED_USER, false);
42            if ($user instanceof PermissionInterface) {
43                $hasPermisions = $user->getPermissionIdentifiers();
44                return $hasPermisions->hasOverlap($requiredPermissions);
45            }
46            return $requiredPermissions->hasOverlap(new PermissionList(['']));
47        }
48        if (EntityUtils::isPolymorphicEntity($refl) && $runtimeChecks && $resource) {
49            $refl = new ReflectionClass($resource);
50        }
51        return $context->appliesToContext($refl, $runtimeChecks, $throwError ? new LogicException('Operation is not allowed') : null);
52    }
53
54    /**
55     * @param array<string|int, mixed> $rawContents
56     */
57    public function __invoke(ApieContext $context, array $rawContents): ActionResponse
58    {
59        $context->withContext(ContextConstants::APIE_ACTION, __CLASS__)->checkAuthorization();
60        $resourceClass = new ReflectionClass($context->getContext(ContextConstants::RESOURCE_NAME));
61        $id = $context->getContext(ContextConstants::RESOURCE_ID);
62        if (!$resourceClass->implementsInterface(EntityInterface::class)) {
63            throw new InvalidTypeException($resourceClass->name, 'EntityInterface');
64        }
65        $lock = LockUtil::createLock(
66            $context,
67            [ContextConstants::BOUNDED_CONTEXT_ID, ContextConstants::RESOURCE_NAME, ContextConstants::RESOURCE_ID]
68        );
69        try {
70            $result = $this->apieFacade->find(
71                IdentifierUtils::idStringToIdentifier($id, $context),
72                new BoundedContextId($context->getContext(ContextConstants::BOUNDED_CONTEXT_ID))
73            );
74            $context = $context->withContext(ContextConstants::RESOURCE, $result);
75            $context->withContext(ContextConstants::APIE_ACTION, __CLASS__)->checkAuthorization();
76        } finally {
77            $lock->release();
78        }
79        $context->dispatchEvent(new ApieResourceRead($result, $context));
80        return ActionResponse::createRunSuccess($this->apieFacade, $context, $result, $result);
81    }
82
83    /**
84     * @return ReflectionClass<EntityInterface>
85     */
86    public static function getInputType(ReflectionClass $class): ReflectionClass
87    {
88        return $class;
89    }
90
91    /**
92     * @return ReflectionClass<EntityInterface>
93     */
94    public static function getOutputType(ReflectionClass $class): ReflectionClass
95    {
96        return $class;
97    }
98
99    public static function getPossibleActionResponseStatuses(): ActionResponseStatusList
100    {
101        return new ActionResponseStatusList([
102            ActionResponseStatus::SUCCESS,
103            ActionResponseStatus::AUTHORIZATION_ERROR,
104            ActionResponseStatus::NOT_FOUND
105        ]);
106    }
107
108    public static function getDescription(ReflectionClass $class): string
109    {
110        $description = 'Gets a resource of ' . $class->getShortName() . ' with a specific id';
111        foreach ($class->getAttributes(Description::class) as $attribute) {
112            $description .= '. ' . $attribute->newInstance()->description;
113        }
114
115        return $description;
116    }
117    
118    public static function getTags(ReflectionClass $class): StringList
119    {
120        return new StringList([$class->getShortName(), 'resource']);
121    }
122
123    public static function getRouteAttributes(ReflectionClass $class): array
124    {
125        return [
126            ContextConstants::GET_OBJECT => true,
127            ContextConstants::RESOURCE_NAME => $class->name,
128        ];
129    }
130}