1 | <?php |
||
2 | |||
3 | /** |
||
4 | * Copyright © 2016-present Spryker Systems GmbH. All rights reserved. |
||
5 | * Use of this software requires acceptance of the Evaluation License Agreement. See LICENSE file. |
||
6 | */ |
||
7 | |||
8 | namespace Spryker\Zed\Api\Communication\EventListener; |
||
9 | |||
10 | use Generated\Shared\Transfer\ApiRequestTransfer; |
||
11 | use Generated\Shared\Transfer\ApiResponseTransfer; |
||
12 | use JsonException; |
||
13 | use Spryker\Shared\Log\LoggerTrait; |
||
14 | use Spryker\Zed\Api\ApiConfig; |
||
15 | use Spryker\Zed\Api\Business\ApiFacadeInterface; |
||
16 | use Spryker\Zed\Api\Business\Http\HttpConstants; |
||
17 | use Spryker\Zed\Api\Communication\Controller\AbstractApiController; |
||
18 | use Spryker\Zed\Api\Communication\Transformer\TransformerInterface; |
||
19 | use Spryker\Zed\Api\Dependency\Service\ApiToUtilEncodingServiceInterface; |
||
20 | use Symfony\Component\HttpFoundation\Request; |
||
21 | use Symfony\Component\HttpFoundation\Response; |
||
22 | use Symfony\Component\HttpKernel\Event\ControllerEvent; |
||
23 | use Throwable; |
||
24 | |||
25 | class ApiControllerEventListener implements ApiControllerEventListenerInterface |
||
26 | { |
||
27 | use LoggerTrait; |
||
28 | |||
29 | /** |
||
30 | * @var string |
||
31 | */ |
||
32 | protected const REQUEST_URI = 'REQUEST_URI'; |
||
33 | |||
34 | /** |
||
35 | * @var \Spryker\Zed\Api\Communication\Transformer\TransformerInterface |
||
36 | */ |
||
37 | protected $transformer; |
||
38 | |||
39 | /** |
||
40 | * @var \Spryker\Zed\Api\Business\ApiFacadeInterface |
||
41 | */ |
||
42 | protected $apiFacade; |
||
43 | |||
44 | /** |
||
45 | * @var \Spryker\Zed\Api\Dependency\Service\ApiToUtilEncodingServiceInterface |
||
46 | */ |
||
47 | protected $utilEncodingService; |
||
48 | |||
49 | /** |
||
50 | * @param \Spryker\Zed\Api\Communication\Transformer\TransformerInterface $transformer |
||
51 | * @param \Spryker\Zed\Api\Business\ApiFacadeInterface $apiFacade |
||
52 | * @param \Spryker\Zed\Api\Dependency\Service\ApiToUtilEncodingServiceInterface $utilEncodingService |
||
53 | */ |
||
54 | public function __construct( |
||
55 | TransformerInterface $transformer, |
||
56 | ApiFacadeInterface $apiFacade, |
||
57 | ApiToUtilEncodingServiceInterface $utilEncodingService |
||
58 | ) { |
||
59 | $this->transformer = $transformer; |
||
60 | $this->apiFacade = $apiFacade; |
||
61 | $this->utilEncodingService = $utilEncodingService; |
||
62 | } |
||
63 | |||
64 | /** |
||
65 | * @param \Symfony\Component\HttpKernel\Event\ControllerEvent $controllerEvent |
||
66 | * |
||
67 | * @return void |
||
68 | */ |
||
69 | public function onKernelControllerEvent(ControllerEvent $controllerEvent): void |
||
70 | { |
||
71 | $request = $controllerEvent->getRequest(); |
||
72 | |||
73 | if (!$request->server->has(static::REQUEST_URI) || strpos($request->server->get(static::REQUEST_URI), ApiConfig::ROUTE_PREFIX_API_REST) !== 0) { |
||
74 | return; |
||
75 | } |
||
76 | |||
77 | /** @var array $currentController */ |
||
78 | $currentController = $controllerEvent->getController(); |
||
79 | [$controller, $action] = $currentController; |
||
80 | |||
81 | if (!$controller instanceof AbstractApiController) { |
||
82 | return; |
||
83 | } |
||
84 | |||
85 | $request = $controllerEvent->getRequest(); |
||
86 | try { |
||
87 | $apiController = function () use ($controller, $action, $request) { |
||
88 | return $this->executeControllerAction($request, $controller, $action); |
||
89 | }; |
||
90 | } catch (JsonException $e) { |
||
0 ignored issues
–
show
|
|||
91 | $apiController = $this->transformer->transformBadRequest(new ApiResponseTransfer(), new Response(), $e->getMessage()); |
||
92 | } |
||
93 | |||
94 | $controllerEvent->setController($apiController); |
||
95 | } |
||
96 | |||
97 | /** |
||
98 | * @param \Symfony\Component\HttpFoundation\Request $request |
||
99 | * @param \Spryker\Zed\Api\Communication\Controller\AbstractApiController $controller |
||
100 | * @param string $action |
||
101 | * |
||
102 | * @return \Symfony\Component\HttpFoundation\Response |
||
103 | */ |
||
104 | protected function executeControllerAction(Request $request, AbstractApiController $controller, string $action): Response |
||
105 | { |
||
106 | $apiRequestTransfer = $this->getApiRequestTransfer($request); |
||
107 | $this->logRequest($apiRequestTransfer); |
||
108 | |||
109 | try { |
||
110 | $responseTransfer = $controller->$action($apiRequestTransfer); |
||
111 | } catch (Throwable $exception) { |
||
112 | $responseTransfer = new ApiResponseTransfer(); |
||
113 | $responseTransfer->setCode($this->resolveStatusCode((int)$exception->getCode())); |
||
114 | $responseTransfer->setMessage($exception->getMessage()); |
||
115 | $responseTransfer->setStackTrace(sprintf( |
||
116 | '%s (%s, line %d): %s', |
||
117 | get_class($exception), |
||
118 | $exception->getFile(), |
||
119 | $exception->getLine(), |
||
120 | $exception->getTraceAsString(), |
||
121 | )); |
||
122 | } |
||
123 | |||
124 | $this->logResponse($responseTransfer); |
||
125 | |||
126 | return $this->transformer->transform($apiRequestTransfer, $responseTransfer, new Response()); |
||
127 | } |
||
128 | |||
129 | /** |
||
130 | * @param int $code |
||
131 | * |
||
132 | * @return int |
||
133 | */ |
||
134 | protected function resolveStatusCode(int $code): int |
||
135 | { |
||
136 | if ($code < ApiConfig::HTTP_CODE_SUCCESS || $code > ApiConfig::HTTP_CODE_INTERNAL_ERROR) { |
||
137 | return ApiConfig::HTTP_CODE_INTERNAL_ERROR; |
||
138 | } |
||
139 | |||
140 | return $code; |
||
141 | } |
||
142 | |||
143 | /** |
||
144 | * @param \Symfony\Component\HttpFoundation\Request $request |
||
145 | * |
||
146 | * @throws \JsonException |
||
147 | * |
||
148 | * @return \Generated\Shared\Transfer\ApiRequestTransfer |
||
149 | */ |
||
150 | protected function getApiRequestTransfer(Request $request): ApiRequestTransfer |
||
151 | { |
||
152 | $requestTransfer = new ApiRequestTransfer(); |
||
153 | |||
154 | $requestTransfer->setRequestType($request->getMethod()); |
||
155 | $requestTransfer->setQueryData($request->query->all()); |
||
156 | $requestTransfer->setHeaderData($request->headers->all()); |
||
157 | |||
158 | $serverData = $request->server->all(); |
||
159 | $requestTransfer->setServerData($serverData); |
||
160 | $requestTransfer->setRequestUri($serverData[static::REQUEST_URI]); |
||
161 | |||
162 | if (strpos((string)$request->headers->get(HttpConstants::HEADER_CONTENT_TYPE), 'application/json') === 0) { |
||
163 | /** |
||
164 | * @var string|resource $content |
||
165 | */ |
||
166 | $content = $request->getContent(); |
||
167 | if (is_resource($content)) { |
||
168 | $content = stream_get_contents($content); |
||
169 | $content = $content ?: ''; |
||
170 | } |
||
171 | |||
172 | try { |
||
173 | $data = $this->utilEncodingService->decodeJson($content, true, 512, JSON_THROW_ON_ERROR); |
||
174 | } catch (JsonException $exception) { |
||
175 | $this->logRequest($requestTransfer); |
||
176 | |||
177 | throw $exception; |
||
178 | } |
||
179 | $request->request->replace(is_array($data) && isset($data['data']) ? $data['data'] : []); |
||
180 | } |
||
181 | |||
182 | return $requestTransfer->setRequestData($request->request->all()); |
||
183 | } |
||
184 | |||
185 | /** |
||
186 | * @param \Generated\Shared\Transfer\ApiRequestTransfer $apiRequestTransfer |
||
187 | * |
||
188 | * @return void |
||
189 | */ |
||
190 | protected function logRequest(ApiRequestTransfer $apiRequestTransfer): void |
||
191 | { |
||
192 | $filteredApiRequestTransfer = $this->apiFacade->filterApiRequestTransfer($apiRequestTransfer); |
||
193 | |||
194 | $this->getLogger()->info(sprintf( |
||
195 | 'API request [%s %s]: %s', |
||
196 | $apiRequestTransfer->getRequestTypeOrFail(), |
||
197 | $apiRequestTransfer->getRequestUriOrFail(), |
||
198 | $this->utilEncodingService->encodeJson($filteredApiRequestTransfer->toArray()), |
||
199 | )); |
||
200 | } |
||
201 | |||
202 | /** |
||
203 | * @param \Generated\Shared\Transfer\ApiResponseTransfer $responseTransfer |
||
204 | * |
||
205 | * @return void |
||
206 | */ |
||
207 | protected function logResponse(ApiResponseTransfer $responseTransfer): void |
||
208 | { |
||
209 | $responseTransferData = $responseTransfer->toArray(); |
||
210 | unset($responseTransferData['request']); |
||
211 | |||
212 | $this->getLogger()->info(sprintf( |
||
213 | 'API response [code %s]: %s', |
||
214 | $responseTransfer->getCodeOrFail(), |
||
215 | $this->utilEncodingService->encodeJson($responseTransferData), |
||
216 | )); |
||
217 | } |
||
218 | } |
||
219 |
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.
Unreachable code is most often the result of
return
,die
orexit
statements that have been added for debug purposes.In the above example, the last
return false
will never be executed, because a return statement has already been met in every possible execution path.