Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
90.48% covered (success)
90.48%
57 / 63
70.00% covered (warning)
70.00%
7 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
RunAction
90.48% covered (success)
90.48%
57 / 63
70.00% covered (warning)
70.00%
7 / 10
26.58
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
60.00% covered (warning)
60.00%
6 / 10
0.00% covered (danger)
0.00%
0 / 1
10.14
 __invoke
95.83% covered (success)
95.83%
23 / 24
0.00% covered (danger)
0.00%
0 / 1
6
 getRouteAttributes
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 getNameToDisplay
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 getDescription
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getTags
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getInputType
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getOutputType
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getPossibleActionResponseStatuses
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2namespace Apie\Common\Actions;
3
4use Apie\Common\ValueObjects\DecryptedAuthenticatedUser;
5use Apie\Core\Actions\ActionResponse;
6use Apie\Core\Actions\ActionResponseStatus;
7use Apie\Core\Actions\ActionResponseStatusList;
8use Apie\Core\Actions\ApieFacadeInterface;
9use Apie\Core\Actions\MethodActionInterface;
10use Apie\Core\BoundedContext\BoundedContextId;
11use Apie\Core\Context\ApieContext;
12use Apie\Core\ContextConstants;
13use Apie\Core\Entities\EntityInterface;
14use Apie\Core\Lists\StringList;
15use Apie\Serializer\Exceptions\ValidationException;
16use LogicException;
17use ReflectionClass;
18use ReflectionMethod;
19
20/**
21 * Runs a global method and returns the return value of this method.
22 */
23final class RunAction implements MethodActionInterface
24{
25    public function __construct(private ApieFacadeInterface $apieFacade)
26    {
27    }
28
29    public static function isAuthorized(ApieContext $context, bool $runtimeChecks, bool $throwError = false): bool
30    {
31        $serviceClass = $context->getContext(ContextConstants::SERVICE_CLASS, $throwError);
32        $methodName = $context->getContext(ContextConstants::METHOD_NAME, $throwError);
33        if (!$serviceClass || !$methodName) {
34            return false;
35        }
36        $method = new ReflectionMethod($serviceClass, $methodName);
37        if (!$method->isStatic() && !$context->hasContext($serviceClass)) {
38            if ($throwError) {
39                throw new LogicException('Service ' . $serviceClass . ' is missing!');
40            }
41            return false;
42        }
43        return $context->appliesToContext($method, $runtimeChecks, $throwError ? new LogicException('Service class method not allowed') : null);
44    }
45
46    /**
47     * @param array<string|int, mixed> $rawContents
48     */
49    public function __invoke(ApieContext $context, array $rawContents): ActionResponse
50    {
51        $alreadyCalculated = $context->getContext(ContextConstants::ALREADY_CALCULATED, false);
52        if ($alreadyCalculated instanceof ActionResponse) {
53            return $alreadyCalculated;
54        }
55        $context->withContext(ContextConstants::APIE_ACTION, __CLASS__)->checkAuthorization();
56        $method = new ReflectionMethod(
57            $context->getContext(ContextConstants::SERVICE_CLASS),
58            $context->getContext(ContextConstants::METHOD_NAME)
59        );
60        $object = $method->isStatic()
61            ? null
62            : $context->getContext($context->getContext(ContextConstants::SERVICE_CLASS));
63        try {
64            $returnValue = $this->apieFacade->denormalizeOnMethodCall($rawContents, $object, $method, $context);
65            if ($context->getContext(ContextConstants::METHOD_NAME) === 'verifyAuthentication') {
66                if ($returnValue instanceof EntityInterface) {
67                    $userValue = DecryptedAuthenticatedUser::createFromEntity(
68                        $returnValue,
69                        new BoundedContextId($context->getContext(ContextConstants::BOUNDED_CONTEXT_ID, false) ?? 'unknown'),
70                        time() + 3600
71                    );
72                } else {
73                    $userValue = null;
74                }
75                $context = $context->withContext(DecryptedAuthenticatedUser::class, $userValue);
76            }
77        } catch (ValidationException $error) {
78            return ActionResponse::createClientError($this->apieFacade, $context, $error);
79        }
80        return ActionResponse::createRunSuccess($this->apieFacade, $context, $returnValue, $object);
81    }
82
83    public static function getRouteAttributes(ReflectionClass $class, ?ReflectionMethod $method = null): array
84    {
85        assert($method instanceof ReflectionMethod);
86        return [
87            ContextConstants::GLOBAL_METHOD => true,
88            ContextConstants::SERVICE_CLASS => $method->getDeclaringClass()->name,
89            ContextConstants::METHOD_NAME => $method->getName(),
90            ContextConstants::DISPLAY_FORM => true,
91        ];
92    }
93
94    private static function getNameToDisplay(?ReflectionMethod $method = null): string
95    {
96        if ($method === null) {
97            return 'null';
98        }
99        $methodName = $method->getName();
100        if ($methodName === '__invoke') {
101            return $method->getDeclaringClass()->getShortName();
102        }
103
104        return $methodName;
105    }
106
107    public static function getDescription(ReflectionClass $class, ?ReflectionMethod $method = null): string
108    {
109        return 'Calls method ' . self::getNameToDisplay($method) . ' and returns return value.';
110    }
111
112    public static function getTags(ReflectionClass $class, ?ReflectionMethod $method = null): StringList
113    {
114        $class = $method ? $method->getDeclaringClass() : $class;
115        return new StringList([$class->getShortName(), 'action']);
116    }
117
118    public static function getInputType(ReflectionClass $class, ?ReflectionMethod $method = null): ReflectionMethod
119    {
120        assert($method instanceof ReflectionMethod);
121        return $method;
122    }
123
124    public static function getOutputType(ReflectionClass $class, ?ReflectionMethod $method = null): ReflectionMethod
125    {
126        assert($method instanceof ReflectionMethod);
127        return $method;
128    }
129
130    public static function getPossibleActionResponseStatuses(?ReflectionMethod $method = null): ActionResponseStatusList
131    {
132        if (!$method || empty($method->getParameters())) {
133            return new ActionResponseStatusList([
134                ActionResponseStatus::SUCCESS,
135            ]);
136        }
137        return new ActionResponseStatusList([
138            ActionResponseStatus::CLIENT_ERROR,
139            ActionResponseStatus::SUCCESS
140        ]);
141    }
142}