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