Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
83.02% covered (warning)
83.02%
44 / 53
77.78% covered (warning)
77.78%
7 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
RemoveObjectAction
83.02% covered (warning)
83.02%
44 / 53
77.78% covered (warning)
77.78%
7 / 9
25.59
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
71.43% covered (warning)
71.43%
10 / 14
0.00% covered (danger)
0.00%
0 / 1
15.36
 __invoke
79.17% covered (warning)
79.17%
19 / 24
0.00% covered (danger)
0.00%
0 / 1
4.14
 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%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 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\IntegrationTestLogger;
5use Apie\Common\Other\LockUtil;
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\RemovalCheck;
12use Apie\Core\BoundedContext\BoundedContextId;
13use Apie\Core\Context\ApieContext;
14use Apie\Core\ContextConstants;
15use Apie\Core\Entities\EntityInterface;
16use Apie\Core\Exceptions\EntityNotFoundException;
17use Apie\Core\Exceptions\InvalidTypeException;
18use Apie\Core\IdentifierUtils;
19use Apie\Core\Lists\StringList;
20use Apie\Core\Utils\EntityUtils;
21use Apie\Core\ValueObjects\Exceptions\InvalidStringForValueObjectException;
22use LogicException;
23use ReflectionClass;
24
25/**
26 * Action to remove an existing object.
27 */
28final class RemoveObjectAction 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 (EntityUtils::isPolymorphicEntity($refl) && $runtimeChecks && $context->hasContext(ContextConstants::RESOURCE)) {
38            $refl = new ReflectionClass($context->getContext(ContextConstants::RESOURCE, $throwError));
39        }
40        if (!$context->appliesToContext($refl, $runtimeChecks, $throwError ? new LogicException('Class does not allow it') : null)) {
41            return false;
42        }
43        $returnValue = false;
44        foreach ($refl->getAttributes(RemovalCheck::class) as $removeAttribute) {
45            $returnValue = true;
46            $removeCheck = $removeAttribute->newInstance();
47            if ($removeCheck->isStaticCheck() && !$removeCheck->applies($context)) {
48                return false;
49            }
50            if ($runtimeChecks && $removeCheck->isRuntimeCheck() && !$removeCheck->applies($context)) {
51                return false;
52            }
53        }
54        return $returnValue;
55    }
56    
57    /**
58     * @param array<string|int, mixed> $rawContents
59     */
60    public function __invoke(ApieContext $context, array $rawContents): ActionResponse
61    {
62        $context->withContext(ContextConstants::APIE_ACTION, __CLASS__)->checkAuthorization();
63        $resourceClass = new ReflectionClass($context->getContext(ContextConstants::RESOURCE_NAME));
64        $id = $context->getContext(ContextConstants::RESOURCE_ID);
65        if (!$resourceClass->implementsInterface(EntityInterface::class)) {
66            throw new InvalidTypeException($resourceClass->name, 'EntityInterface');
67        }
68        $lock = LockUtil::createLock(
69            $context,
70            [ContextConstants::BOUNDED_CONTEXT_ID, ContextConstants::RESOURCE_NAME, ContextConstants::RESOURCE_ID],
71            write: true
72        );
73        try {
74            $boundedContextId = new BoundedContextId($context->getContext(ContextConstants::BOUNDED_CONTEXT_ID));
75            try {
76                $resource = $this->apieFacade->find(
77                    IdentifierUtils::idStringToIdentifier($id, $context),
78                    $boundedContextId
79                );
80            } catch (InvalidStringForValueObjectException|EntityNotFoundException $error) {
81                IntegrationTestLogger::logException($error);
82                return ActionResponse::createClientError($this->apieFacade, $context, $error);
83            }
84            $context = $context->withContext(ContextConstants::RESOURCE, $resource);
85
86            if (!$lock->isAcquired()) {
87                throw new \LogicException('Lock was released before modification was finished!');
88            }
89            $this->apieFacade->removeExisting($resource, $boundedContextId);
90        } finally {
91            $lock->release();
92        }
93
94        return ActionResponse::createRemovedSuccess($this->apieFacade, $context);
95    }
96
97    /**
98     * @return ReflectionClass<EntityInterface>
99     */
100    public static function getInputType(ReflectionClass $class): ReflectionClass
101    {
102        return $class;
103    }
104
105    /**
106     * @return ReflectionClass<EntityInterface>
107     */
108    public static function getOutputType(ReflectionClass $class): ReflectionClass
109    {
110        return $class;
111    }
112
113    public static function getPossibleActionResponseStatuses(): ActionResponseStatusList
114    {
115        return new ActionResponseStatusList([
116            ActionResponseStatus::DELETED,
117            ActionResponseStatus::CLIENT_ERROR,
118            ActionResponseStatus::PERISTENCE_ERROR
119        ]);
120    }
121
122    /**
123     * @param ReflectionClass<object> $class
124     */
125    public static function getDescription(ReflectionClass $class): string
126    {
127        return 'Removes an instance of ' . $class->getShortName();
128    }
129    
130    /**
131     * @param ReflectionClass<object> $class
132     */
133    public static function getTags(ReflectionClass $class): StringList
134    {
135        return new StringList([$class->getShortName(), 'resource']);
136    }
137
138    /**
139     * @param ReflectionClass<object> $class
140     */
141    public static function getRouteAttributes(ReflectionClass $class): array
142    {
143        return [
144            ContextConstants::EDIT_OBJECT => true,
145            ContextConstants::RESOURCE_NAME => $class->name,
146            ContextConstants::DISPLAY_FORM => true,
147        ];
148    }
149}