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