1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Bankiru\Api\Rpc\Controller; |
4
|
|
|
|
5
|
|
|
use Bankiru\Api\Rpc\Event\FilterControllerEvent; |
6
|
|
|
use Bankiru\Api\Rpc\Event\FilterResponseEvent; |
7
|
|
|
use Bankiru\Api\Rpc\Event\FinishRequestEvent; |
8
|
|
|
use Bankiru\Api\Rpc\Event\GetExceptionResponseEvent; |
9
|
|
|
use Bankiru\Api\Rpc\Event\GetResponseEvent; |
10
|
|
|
use Bankiru\Api\Rpc\Event\ViewEvent; |
11
|
|
|
use Bankiru\Api\Rpc\Routing\ControllerResolver\ControllerResolverInterface; |
12
|
|
|
use Bankiru\Api\Rpc\Routing\Exception\MethodNotFoundException; |
13
|
|
|
use Bankiru\Api\Rpc\RpcEvents; |
14
|
|
|
use Bankiru\Api\Rpc\RpcRequestInterface; |
15
|
|
|
use ScayTrase\Api\Rpc\RpcResponseInterface; |
16
|
|
|
use Symfony\Component\DependencyInjection\ContainerAwareInterface; |
17
|
|
|
use Symfony\Component\DependencyInjection\ContainerInterface; |
18
|
|
|
use Symfony\Component\DependencyInjection\Exception\ExceptionInterface; |
19
|
|
|
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; |
20
|
|
|
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; |
21
|
|
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface; |
22
|
|
|
use Symfony\Component\HttpKernel\KernelInterface; |
23
|
|
|
|
24
|
|
|
abstract class RpcController implements ContainerAwareInterface |
25
|
|
|
{ |
26
|
|
|
/** @var ContainerInterface */ |
27
|
|
|
private $container; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* Sets the container. |
31
|
|
|
* |
32
|
|
|
* @param ContainerInterface|null $container A ContainerInterface instance or null |
33
|
|
|
* |
34
|
|
|
* @throws ServiceNotFoundException |
35
|
|
|
* @throws ServiceCircularReferenceException |
36
|
|
|
*/ |
37
|
9 |
|
public function setContainer(ContainerInterface $container = null) |
38
|
|
|
{ |
39
|
9 |
|
$this->container = $container; |
40
|
9 |
|
} |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* @param RpcRequestInterface $request |
44
|
|
|
* @param string $endpoint |
45
|
|
|
* |
46
|
|
|
* @return RpcResponseInterface |
47
|
|
|
* @throws \Exception |
48
|
|
|
*/ |
49
|
9 |
|
protected function getResponse(RpcRequestInterface $request, $endpoint) |
50
|
|
|
{ |
51
|
9 |
|
$request->getAttributes()->set('_endpoint', $endpoint); |
52
|
|
|
|
53
|
|
|
try { |
54
|
9 |
|
$rpcResponse = $this->handleSingleRequest($request); |
55
|
9 |
|
} catch (\Exception $e) { |
56
|
5 |
|
$rpcResponse = $this->handleException($e, $request); |
57
|
|
|
} |
58
|
|
|
|
59
|
4 |
|
return $rpcResponse; |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* @param RpcRequestInterface $request |
64
|
|
|
* |
65
|
|
|
* @return RpcResponseInterface |
66
|
|
|
* |
67
|
|
|
* @throws \RuntimeException |
68
|
|
|
* @throws \InvalidArgumentException |
69
|
|
|
* @throws \LogicException |
70
|
|
|
* @throws MethodNotFoundException |
71
|
|
|
*/ |
72
|
9 |
|
protected function handleSingleRequest(RpcRequestInterface $request) |
73
|
|
|
{ |
74
|
|
|
// request |
75
|
9 |
|
$event = new GetResponseEvent($this->getKernel(), $request); |
76
|
9 |
|
$this->getDispatcher()->dispatch(RpcEvents::REQUEST, $event); |
77
|
8 |
|
if ($event->hasResponse()) { |
78
|
|
|
return $this->filterResponse($event->getResponse(), $request); |
79
|
|
|
} |
80
|
|
|
// load controller |
81
|
8 |
|
if (false === $controller = $this->getResolver()->getController($request)) { |
82
|
|
|
throw new MethodNotFoundException($request->getMethod()); |
83
|
|
|
} |
84
|
8 |
|
$event = new FilterControllerEvent($this->getKernel(), $request, $controller); |
85
|
8 |
|
$this->getDispatcher()->dispatch(RpcEvents::CONTROLLER, $event); |
86
|
7 |
|
$controller = $event->getController(); |
87
|
|
|
// controller arguments |
88
|
7 |
|
$arguments = $this->getResolver()->getArguments($request, $controller); |
89
|
|
|
// call controller |
90
|
4 |
|
$response = call_user_func_array($controller, $arguments); |
91
|
|
|
// view |
92
|
4 |
|
if (!$response instanceof RpcResponseInterface) { |
93
|
|
|
$event = new ViewEvent($this->getKernel(), $request, $response); |
94
|
|
|
$this->getDispatcher()->dispatch(RpcEvents::VIEW, $event); |
95
|
|
|
if ($event->hasResponse()) { |
96
|
|
|
$response = $event->getResponse(); |
97
|
|
|
} |
98
|
|
|
/** @noinspection NotOptimalIfConditionsInspection */ |
99
|
|
|
if (!$response instanceof RpcResponseInterface) { |
100
|
|
|
$msg = sprintf( |
101
|
|
|
'The controller must return a RpcResponseInterface response (%s given).', |
102
|
|
|
$this->varToString($response) |
103
|
|
|
); |
104
|
|
|
// the user may have forgotten to return something |
105
|
|
|
if (null === $response) { |
106
|
|
|
$msg .= ' Did you forget to add a return statement somewhere in your controller?'; |
107
|
|
|
} |
108
|
|
|
throw new \LogicException($msg); |
109
|
|
|
} |
110
|
|
|
} |
111
|
|
|
|
112
|
4 |
|
return $this->filterResponse($response, $request); |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* @param $name |
117
|
|
|
* |
118
|
|
|
* @return object|null |
119
|
|
|
* @throws ServiceNotFoundException |
120
|
|
|
* @throws ServiceCircularReferenceException |
121
|
|
|
*/ |
122
|
9 |
|
protected function get($name) |
123
|
|
|
{ |
124
|
9 |
|
return $this->container->get($name); |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* Filters a response object. |
129
|
|
|
* |
130
|
|
|
* @param RpcResponseInterface $response A Response instance |
131
|
|
|
* @param RpcRequestInterface $request An error message in case the response is not a Response object |
132
|
|
|
* |
133
|
|
|
* @return RpcResponseInterface The filtered Response instance |
134
|
|
|
* @throws \RuntimeException |
135
|
|
|
*/ |
136
|
4 |
|
protected function filterResponse(RpcResponseInterface $response, RpcRequestInterface $request) |
137
|
|
|
{ |
138
|
4 |
|
$event = new FilterResponseEvent($this->getKernel(), $request, $response); |
139
|
4 |
|
$this->getDispatcher()->dispatch(RpcEvents::RESPONSE, $event); |
140
|
4 |
|
$this->finishRequest($request); |
141
|
|
|
|
142
|
4 |
|
return $event->getResponse(); |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* Publishes the finish request event, then pop the request from the stack. |
147
|
|
|
* |
148
|
|
|
* Note that the order of the operations is important here, otherwise |
149
|
|
|
* operations such as {@link RequestStack::getParentRequest()} can lead to |
150
|
|
|
* weird results. |
151
|
|
|
* |
152
|
|
|
* @param RpcRequestInterface $request |
153
|
|
|
* |
154
|
|
|
* @throws \RuntimeException |
155
|
|
|
*/ |
156
|
9 |
|
protected function finishRequest(RpcRequestInterface $request) |
157
|
|
|
{ |
158
|
9 |
|
$this->getDispatcher()->dispatch( |
159
|
9 |
|
RpcEvents::FINISH_REQUEST, |
160
|
9 |
|
new FinishRequestEvent($this->getKernel(), $request) |
161
|
9 |
|
); |
162
|
9 |
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* @return ControllerResolverInterface |
166
|
|
|
*/ |
167
|
|
|
abstract protected function getResolver(); |
168
|
|
|
|
169
|
|
|
/** |
170
|
|
|
* @param $var |
171
|
|
|
* |
172
|
|
|
* @return string |
173
|
|
|
*/ |
174
|
|
View Code Duplication |
protected function varToString($var) |
|
|
|
|
175
|
|
|
{ |
176
|
|
|
if (is_object($var)) { |
177
|
|
|
return sprintf('Object(%s)', get_class($var)); |
178
|
|
|
} |
179
|
|
|
if (is_array($var)) { |
180
|
|
|
$a = []; |
181
|
|
|
foreach ($var as $k => $v) { |
182
|
|
|
$a[] = sprintf('%s => %s', $k, $this->varToString($v)); |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
return sprintf('Array(%s)', implode(', ', $a)); |
186
|
|
|
} |
187
|
|
|
if (is_resource($var)) { |
188
|
|
|
return sprintf('Resource(%s)', get_resource_type($var)); |
189
|
|
|
} |
190
|
|
|
if (null === $var) { |
191
|
|
|
return 'null'; |
192
|
|
|
} |
193
|
|
|
if (false === $var) { |
194
|
|
|
return 'false'; |
195
|
|
|
} |
196
|
|
|
if (true === $var) { |
197
|
|
|
return 'true'; |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
return (string)$var; |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
/** |
204
|
|
|
* Handles an exception by trying to convert it to a Response. |
205
|
|
|
* |
206
|
|
|
* @param \Exception $e An \Exception instance |
207
|
|
|
* @param RpcRequestInterface $request A Request instance |
208
|
|
|
* |
209
|
|
|
* @return RpcResponseInterface A Response instance |
210
|
|
|
* |
211
|
|
|
* @throws \Exception |
212
|
|
|
*/ |
213
|
5 |
|
protected function handleException(\Exception $e, RpcRequestInterface $request) |
214
|
|
|
{ |
215
|
5 |
|
$event = new GetExceptionResponseEvent($this->getKernel(), $request, $e); |
216
|
5 |
|
$this->getDispatcher()->dispatch(RpcEvents::EXCEPTION, $event); |
217
|
|
|
// a listener might have replaced the exception |
218
|
5 |
|
$e = $event->getException(); |
219
|
5 |
|
if (!$event->hasResponse()) { |
220
|
5 |
|
$this->finishRequest($request); |
221
|
5 |
|
throw $e; |
222
|
|
|
} |
223
|
|
|
$response = $event->getResponse(); |
224
|
|
|
|
225
|
|
|
try { |
226
|
|
|
return $this->filterResponse($response, $request); |
227
|
|
|
} catch (\Exception $e) { |
228
|
|
|
return $response; |
229
|
|
|
} |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
/** |
233
|
|
|
* @return EventDispatcherInterface |
234
|
|
|
*/ |
235
|
9 |
|
protected function getDispatcher() |
236
|
|
|
{ |
237
|
9 |
|
return $this->get('event_dispatcher'); |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
/** |
241
|
|
|
* @return KernelInterface |
242
|
|
|
* @throws \RuntimeException |
243
|
|
|
*/ |
244
|
9 |
|
private function getKernel() |
245
|
|
|
{ |
246
|
|
|
try { |
247
|
|
|
/** @var KernelInterface|null $kernel */ |
248
|
9 |
|
$kernel = $this->get('kernel'); |
249
|
9 |
|
} catch (ExceptionInterface $e) { |
250
|
|
|
throw new \RuntimeException('Cannot obtain Kernel from container: ' . $e->getMessage(), $e->getCode(), $e); |
251
|
|
|
} |
252
|
9 |
|
if (null === $kernel) { |
253
|
|
|
throw new \RuntimeException('Cannot obtain Kernel from container'); |
254
|
|
|
} |
255
|
|
|
|
256
|
9 |
|
return $kernel; |
257
|
|
|
} |
258
|
|
|
} |
259
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.