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