Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
82.69% |
43 / 52 |
|
77.78% |
7 / 9 |
CRAP | |
0.00% |
0 / 1 |
ModifyObjectAction | |
82.69% |
43 / 52 |
|
77.78% |
7 / 9 |
19.68 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isAuthorized | |
55.56% |
5 / 9 |
|
0.00% |
0 / 1 |
11.30 | |||
__invoke | |
82.14% |
23 / 28 |
|
0.00% |
0 / 1 |
4.09 | |||
getInputType | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getOutputType | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getPossibleActionResponseStatuses | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
getDescription | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getTags | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getRouteAttributes | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | namespace Apie\Common\Actions; |
3 | |
4 | use Apie\Common\IntegrationTestLogger; |
5 | use Apie\Common\Other\LockUtil; |
6 | use Apie\Core\Actions\ActionInterface; |
7 | use Apie\Core\Actions\ActionResponse; |
8 | use Apie\Core\Actions\ActionResponseStatus; |
9 | use Apie\Core\Actions\ActionResponseStatusList; |
10 | use Apie\Core\Actions\ApieFacadeInterface; |
11 | use Apie\Core\BoundedContext\BoundedContextId; |
12 | use Apie\Core\Context\ApieContext; |
13 | use Apie\Core\ContextConstants; |
14 | use Apie\Core\Entities\EntityInterface; |
15 | use Apie\Core\Exceptions\EntityNotFoundException; |
16 | use Apie\Core\Exceptions\InvalidTypeException; |
17 | use Apie\Core\IdentifierUtils; |
18 | use Apie\Core\Lists\ItemHashmap; |
19 | use Apie\Core\Lists\StringList; |
20 | use Apie\Core\Metadata\MetadataFactory; |
21 | use Apie\Core\Utils\EntityUtils; |
22 | use Apie\Core\ValueObjects\Exceptions\InvalidStringForValueObjectException; |
23 | use LogicException; |
24 | use ReflectionClass; |
25 | |
26 | /** |
27 | * Action to modify an existing object. |
28 | */ |
29 | final class ModifyObjectAction implements ActionInterface |
30 | { |
31 | public function __construct(private readonly ApieFacadeInterface $apieFacade) |
32 | { |
33 | } |
34 | |
35 | public static function isAuthorized(ApieContext $context, bool $runtimeChecks, bool $throwError = false): bool |
36 | { |
37 | $refl = new ReflectionClass($context->getContext(ContextConstants::RESOURCE_NAME, $throwError)); |
38 | if (EntityUtils::isPolymorphicEntity($refl) && $runtimeChecks && $context->hasContext(ContextConstants::RESOURCE)) { |
39 | $refl = new ReflectionClass($context->getContext(ContextConstants::RESOURCE, $throwError)); |
40 | } |
41 | $metadata = MetadataFactory::getModificationMetadata($refl, $context); |
42 | if ($metadata->getHashmap()->count() === 0) { |
43 | if ($throwError) { |
44 | throw new LogicException('Metadata for ' . $refl->getShortName() . ' has no fields to edit.'); |
45 | } |
46 | return false; |
47 | } |
48 | return $context->appliesToContext($refl, $runtimeChecks, $throwError ? new LogicException('Operation on ' . $refl->getShortName() . ' is not allowed') : null); |
49 | } |
50 | |
51 | /** |
52 | * @param array<string|int, mixed> $rawContents |
53 | */ |
54 | public function __invoke(ApieContext $context, array $rawContents): ActionResponse |
55 | { |
56 | $context->withContext(ContextConstants::APIE_ACTION, __CLASS__)->checkAuthorization(); |
57 | $resourceClass = new ReflectionClass($context->getContext(ContextConstants::RESOURCE_NAME)); |
58 | $id = $context->getContext(ContextConstants::RESOURCE_ID); |
59 | if (!$resourceClass->implementsInterface(EntityInterface::class)) { |
60 | throw new InvalidTypeException($resourceClass->name, 'EntityInterface'); |
61 | } |
62 | $lock = LockUtil::createLock( |
63 | $context, |
64 | [ContextConstants::BOUNDED_CONTEXT_ID, ContextConstants::RESOURCE_NAME, ContextConstants::RESOURCE_ID], |
65 | write: true |
66 | ); |
67 | try { |
68 | try { |
69 | $resource = $this->apieFacade->find( |
70 | IdentifierUtils::idStringToIdentifier($id, $context), |
71 | new BoundedContextId($context->getContext(ContextConstants::BOUNDED_CONTEXT_ID)) |
72 | ); |
73 | } catch (InvalidStringForValueObjectException|EntityNotFoundException $error) { |
74 | IntegrationTestLogger::logException($error); |
75 | return ActionResponse::createClientError($this->apieFacade, $context, $error); |
76 | } |
77 | $context = $context->withContext(ContextConstants::RESOURCE, $resource); |
78 | $resource = $this->apieFacade->denormalizeOnExistingObject( |
79 | new ItemHashmap($rawContents), |
80 | $resource, |
81 | $context, |
82 | ); |
83 | if (!$lock->isAcquired()) { |
84 | throw new \LogicException('Lock was released before modification was finished!'); |
85 | } |
86 | $resource = $this->apieFacade->persistExisting($resource, new BoundedContextId($context->getContext(ContextConstants::BOUNDED_CONTEXT_ID))); |
87 | } finally { |
88 | $lock->release(); |
89 | } |
90 | return ActionResponse::createRunSuccess($this->apieFacade, $context, $resource, $resource); |
91 | } |
92 | |
93 | /** |
94 | * @return ReflectionClass<EntityInterface> |
95 | */ |
96 | public static function getInputType(ReflectionClass $class): ReflectionClass |
97 | { |
98 | return $class; |
99 | } |
100 | |
101 | /** |
102 | * @return ReflectionClass<EntityInterface> |
103 | */ |
104 | public static function getOutputType(ReflectionClass $class): ReflectionClass |
105 | { |
106 | return $class; |
107 | } |
108 | |
109 | public static function getPossibleActionResponseStatuses(): ActionResponseStatusList |
110 | { |
111 | return new ActionResponseStatusList([ |
112 | ActionResponseStatus::SUCCESS, |
113 | ActionResponseStatus::CLIENT_ERROR, |
114 | ActionResponseStatus::PERISTENCE_ERROR |
115 | ]); |
116 | } |
117 | |
118 | /** |
119 | * @param ReflectionClass<object> $class |
120 | */ |
121 | public static function getDescription(ReflectionClass $class): string |
122 | { |
123 | return 'Modifies an instance of ' . $class->getShortName(); |
124 | } |
125 | |
126 | /** |
127 | * @param ReflectionClass<object> $class |
128 | */ |
129 | public static function getTags(ReflectionClass $class): StringList |
130 | { |
131 | return new StringList([$class->getShortName(), 'resource']); |
132 | } |
133 | |
134 | /** |
135 | * @param ReflectionClass<object> $class |
136 | */ |
137 | public static function getRouteAttributes(ReflectionClass $class): array |
138 | { |
139 | return [ |
140 | ContextConstants::EDIT_OBJECT => true, |
141 | ContextConstants::RESOURCE_NAME => $class->name, |
142 | ContextConstants::DISPLAY_FORM => true, |
143 | ]; |
144 | } |
145 | } |