tarlepp /
symfony-flex-backend
| 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
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 |