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
![]() |
|||
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 |