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