Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.74% covered (success)
95.74%
45 / 47
77.78% covered (warning)
77.78%
7 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
CreateObjectAction
95.74% covered (success)
95.74%
45 / 47
77.78% covered (warning)
77.78%
7 / 9
21
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
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
10.08
 __invoke
94.44% covered (success)
94.44%
17 / 18
0.00% covered (danger)
0.00%
0 / 1
3.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%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2namespace Apie\Common\Actions;
3
4use Apie\Common\Events\ApieResourceCreated;
5use Apie\Common\IntegrationTestLogger;
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\Enums\RequestMethod;
17use Apie\Core\Exceptions\IndexNotFoundException;
18use Apie\Core\Lists\StringList;
19use Apie\Core\Utils\EntityUtils;
20use Apie\Core\ValueObjects\Utils;
21use Exception;
22use LogicException;
23use ReflectionClass;
24
25/**
26 * Action to create a new object.
27 */
28final class CreateObjectAction implements ActionInterface
29{
30    public function __construct(private readonly ApieFacadeInterface $apieFacade)
31    {
32    }
33
34    public static function isAuthorized(ApieContext $context, bool $runtimeChecks, bool $throwError = false): bool
35    {
36        $refl = new ReflectionClass($context->getContext(ContextConstants::RESOURCE_NAME, $throwError));
37        if (!$context->appliesToContext($refl, $runtimeChecks, $throwError ? new LogicException('Class access is not allowed') : null)) {
38            return false;
39        }
40        if (EntityUtils::isPolymorphicEntity($refl) && $runtimeChecks && $context->hasContext(ContextConstants::RAW_CONTENTS)) {
41            $rawContents = Utils::toArray($context->getContext(ContextConstants::RAW_CONTENTS, $throwError));
42            try {
43                $refl = EntityUtils::findClass($rawContents, $refl);
44            } catch (IndexNotFoundException) {
45            }
46        }
47        $constructor = $refl->getConstructor();
48        if ($constructor && !$context->appliesToContext($constructor, $runtimeChecks, $throwError ? new LogicException('Class instantiation is not allowed') : null)) {
49            return false;
50        }
51        return true;
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::RAW_CONTENTS, $rawContents)
60            ->withContext(ContextConstants::APIE_ACTION, __CLASS__)
61            ->checkAuthorization();
62        try {
63            $resource = $this->apieFacade->denormalizeNewObject(
64                $rawContents,
65                $context->getContext(ContextConstants::RESOURCE_NAME),
66                $context
67            );
68        } catch (Exception $error) {
69            IntegrationTestLogger::logException($error);
70            return ActionResponse::createClientError($this->apieFacade, $context, $error);
71        }
72        $context = $context->withContext(ContextConstants::RESOURCE, $resource);
73        if ($context->getContext(RequestMethod::class, false) === RequestMethod::PUT) {
74            /** @phpstan-ignore argument.templateType */
75            $resource = $this->apieFacade->upsert($resource, new BoundedContextId($context->getContext(ContextConstants::BOUNDED_CONTEXT_ID)));
76        } else {
77            $resource = $this->apieFacade->persistNew($resource, new BoundedContextId($context->getContext(ContextConstants::BOUNDED_CONTEXT_ID)));
78        }
79        $context = $context->withContext(ContextConstants::RESOURCE, $resource);
80        $context->dispatchEvent(new ApieResourceCreated($resource));
81        return ActionResponse::createCreationSuccess($this->apieFacade, $context, $resource, $resource);
82    }
83
84    /**
85     * @return ReflectionClass<EntityInterface>
86     */
87    public static function getInputType(ReflectionClass $class): ReflectionClass
88    {
89        return $class;
90    }
91
92    /**
93     * @return ReflectionClass<EntityInterface>
94     */
95    public static function getOutputType(ReflectionClass $class): ReflectionClass
96    {
97        return $class;
98    }
99
100    public static function getPossibleActionResponseStatuses(): ActionResponseStatusList
101    {
102        return new ActionResponseStatusList([
103            ActionResponseStatus::CREATED,
104            ActionResponseStatus::CLIENT_ERROR,
105            ActionResponseStatus::PERISTENCE_ERROR
106        ]);
107    }
108
109    /**
110     * @param ReflectionClass<object> $class
111     */
112    public static function getDescription(ReflectionClass $class): string
113    {
114        $description = 'Creates an instance of ' . $class->getShortName();
115        foreach ($class->getAttributes(Description::class) as $attribute) {
116            $description .= '. ' . $attribute->newInstance()->description;
117        }
118
119        return $description;
120    }
121    
122    /**
123     * @param ReflectionClass<object> $class
124     */
125    public static function getTags(ReflectionClass $class): StringList
126    {
127        return new StringList([$class->getShortName(), 'resource']);
128    }
129
130    /**
131     * @param ReflectionClass<object> $class
132     */
133    public static function getRouteAttributes(ReflectionClass $class): array
134    {
135        return [
136            ContextConstants::CREATE_OBJECT => true,
137            ContextConstants::RESOURCE_NAME => $class->name,
138            ContextConstants::DISPLAY_FORM => true,
139        ];
140    }
141}