RestMethodHelper::getDtoClass()   A
last analyzed

Complexity

Conditions 5
Paths 8

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 11
nc 8
nop 1
dl 0
loc 19
ccs 13
cts 13
cp 1
crap 5
rs 9.6111
c 0
b 0
f 0
1
<?php
2
declare(strict_types = 1);
3
/**
4
 * /src/Rest/Traits/RestMethodHelper.php
5
 *
6
 * @author TLe, Tarmo Leppänen <[email protected]>
7
 */
8
9
namespace App\Rest\Traits;
10
11
use App\DTO\RestDtoInterface;
12
use App\Rest\Interfaces\ControllerInterface;
13
use App\Rest\Traits\Methods\RestMethodProcessCriteria;
14
use Doctrine\ORM\NonUniqueResultException;
15
use Doctrine\ORM\NoResultException;
16
use Doctrine\ORM\UnitOfWork;
17
use LogicException;
18
use Symfony\Component\HttpFoundation\Request;
19
use Symfony\Component\HttpFoundation\Response;
20
use Symfony\Component\HttpKernel\Exception\HttpException;
21
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
22
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
23
use Throwable;
24
use UnexpectedValueException;
25
use function array_key_exists;
26
use function class_implements;
27
use function in_array;
28
use function is_array;
29
use function is_int;
30
use function sprintf;
31
32
/**
33
 * Trait RestMethodHelper
34
 *
35
 * @package App\Rest\Traits\Methods
36
 * @author TLe, Tarmo Leppänen <[email protected]>
37
 */
38
trait RestMethodHelper
39
{
40
    use RestMethodProcessCriteria;
41
42
    /**
43
     * Method + DTO class names (key + value)
44
     *
45
     * @var array<string, string>
46
     */
47
    protected static array $dtoClasses = [];
48
49 77
    public function getDtoClass(?string $method = null): string
50
    {
51 77
        $dtoClass = $method !== null && array_key_exists($method, static::$dtoClasses)
52 75
            ? static::$dtoClasses[$method]
53 2
            : $this->getResource()->getDtoClass();
0 ignored issues
show
Bug introduced by
It seems like getResource() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

53
            : $this->/** @scrutinizer ignore-call */ getResource()->getDtoClass();
Loading history...
54
55 77
        $interfaces = class_implements($dtoClass);
56
57 77
        if (is_array($interfaces) && !in_array(RestDtoInterface::class, $interfaces, true)) {
58 1
            $message = sprintf(
59 1
                'Given DTO class \'%s\' is not implementing \'%s\' interface.',
60 1
                $dtoClass,
61 1
                RestDtoInterface::class,
62 1
            );
63
64 1
            throw new UnexpectedValueException($message);
65
        }
66
67 76
        return $dtoClass;
68
    }
69
70
    /**
71
     * @param array<int, string> $allowedHttpMethods
72
     */
73 293
    public function validateRestMethod(Request $request, array $allowedHttpMethods): void
74
    {
75
        // Make sure that we have everything we need to make this work
76 293
        if (!($this instanceof ControllerInterface)) {
77 8
            $message = sprintf(
78 8
                'You cannot use \'%s\' controller class with REST traits if that does not implement \'%s\'',
79 8
                static::class,
80 8
                ControllerInterface::class,
81 8
            );
82
83 8
            throw new LogicException($message);
84
        }
85
86 285
        if (!in_array($request->getMethod(), $allowedHttpMethods, true)) {
87 75
            throw new MethodNotAllowedHttpException($allowedHttpMethods);
88
        }
89
    }
90
91
    /**
92
     * @throws Throwable
93
     */
94 87
    public function handleRestMethodException(Throwable $exception, ?string $id = null): Throwable
95
    {
96 87
        if ($id !== null) {
97 45
            $this->detachEntityFromManager($id);
98
        }
99
100 87
        return $this->determineOutputAndStatusCodeForRestMethodException($exception);
101
    }
102
103
    /**
104
     * Getter method for exception code with fallback to `400` bad response.
105
     */
106 87
    private function getExceptionCode(Throwable $exception): int
107
    {
108 87
        $code = $exception->getCode();
109
110 87
        return is_int($code) && $code !== 0 ? $code : Response::HTTP_BAD_REQUEST;
111
    }
112
113
    /**
114
     * Method to detach entity from entity manager so possible changes to it
115
     * won't be saved.
116
     *
117
     * @throws Throwable
118
     */
119 45
    private function detachEntityFromManager(string $id): void
120
    {
121 45
        $currentResource = $this->getResource();
122 45
        $entityManager = $currentResource->getRepository()->getEntityManager();
123
124
        // Fetch entity
125 45
        $entity = $currentResource->getRepository()->find($id);
126
127
        // Detach entity from manager if it's been managed by it
128 45
        if ($entity !== null
129
            /* @scrutinizer ignore-call */
130 45
            && $entityManager->getUnitOfWork()->getEntityState($entity) === UnitOfWork::STATE_MANAGED
131
        ) {
132 17
            $entityManager->clear();
133
        }
134
    }
135
136 87
    private function determineOutputAndStatusCodeForRestMethodException(Throwable $exception): Throwable
137
    {
138 87
        $code = $this->getExceptionCode($exception);
139
140 87
        $output = new HttpException($code, $exception->getMessage(), $exception, [], $code);
141
142 87
        if ($exception instanceof NoResultException || $exception instanceof NotFoundHttpException) {
143 16
            $code = Response::HTTP_NOT_FOUND;
144
145 16
            $output = new HttpException($code, 'Not found', $exception, [], $code);
146 71
        } elseif ($exception instanceof NonUniqueResultException) {
147 8
            $code = Response::HTTP_INTERNAL_SERVER_ERROR;
148
149 8
            $output = new HttpException($code, $exception->getMessage(), $exception, [], $code);
150 63
        } elseif ($exception instanceof HttpException) {
151 16
            if ($exception->getCode() === 0) {
152 8
                $output = new HttpException(
153 8
                    $exception->getStatusCode(),
154 8
                    $exception->getMessage(),
155 8
                    $exception->getPrevious(),
156 8
                    $exception->getHeaders(),
157 8
                    $code,
158 8
                );
159
            } else {
160 8
                $output = $exception;
161
            }
162
        }
163
164 87
        return $output;
165
    }
166
}
167