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