1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* This file is part of the Lug package. |
5
|
|
|
* |
6
|
|
|
* (c) Eric GELOEN <[email protected]> |
7
|
|
|
* |
8
|
|
|
* For the full copyright and license information, please read the LICENSE |
9
|
|
|
* file that was distributed with this source code. |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
namespace Lug\Bundle\ResourceBundle\Controller; |
13
|
|
|
|
14
|
|
|
use FOS\RestBundle\Controller\FOSRestController; |
15
|
|
|
use FOS\RestBundle\View\View; |
16
|
|
|
use Lug\Bundle\GridBundle\Batch\BatcherInterface; |
17
|
|
|
use Lug\Bundle\GridBundle\Form\Type\Batch\GridBatchType; |
18
|
|
|
use Lug\Bundle\GridBundle\Form\Type\GridType; |
19
|
|
|
use Lug\Bundle\GridBundle\Handler\GridHandlerInterface; |
20
|
|
|
use Lug\Bundle\ResourceBundle\Form\FormFactoryInterface; |
21
|
|
|
use Lug\Bundle\ResourceBundle\Form\Type\CsrfProtectionType; |
22
|
|
|
use Lug\Bundle\ResourceBundle\Rest\Action\ActionEvent; |
23
|
|
|
use Lug\Bundle\ResourceBundle\Rest\RestEvents; |
24
|
|
|
use Lug\Bundle\ResourceBundle\Rest\View\ViewEvent; |
25
|
|
|
use Lug\Bundle\ResourceBundle\Routing\ParameterResolverInterface; |
26
|
|
|
use Lug\Bundle\ResourceBundle\Security\SecurityCheckerInterface; |
27
|
|
|
use Lug\Component\Grid\Model\Builder\GridBuilderInterface; |
28
|
|
|
use Lug\Component\Grid\Model\GridInterface; |
29
|
|
|
use Lug\Component\Resource\Controller\ControllerInterface; |
30
|
|
|
use Lug\Component\Resource\Domain\DomainManagerInterface; |
31
|
|
|
use Lug\Component\Resource\Exception\DomainException; |
32
|
|
|
use Lug\Component\Resource\Factory\FactoryInterface; |
33
|
|
|
use Lug\Component\Resource\Model\ResourceInterface; |
34
|
|
|
use Pagerfanta\Pagerfanta; |
35
|
|
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface; |
36
|
|
|
use Symfony\Component\Form\FormInterface; |
37
|
|
|
use Symfony\Component\HttpFoundation\Request; |
38
|
|
|
use Symfony\Component\HttpFoundation\Response; |
39
|
|
|
use Symfony\Component\HttpKernel\Exception\HttpException; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* @author GeLo <[email protected]> |
43
|
|
|
*/ |
44
|
|
|
class Controller extends FOSRestController implements ControllerInterface |
45
|
|
|
{ |
46
|
|
|
/** |
47
|
|
|
* @var ResourceInterface |
48
|
|
|
*/ |
49
|
|
|
protected $resource; |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* @param ResourceInterface $resource |
53
|
|
|
*/ |
54
|
|
|
public function __construct(ResourceInterface $resource) |
55
|
|
|
{ |
56
|
|
|
$this->resource = $resource; |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* @param Request $request |
61
|
|
|
* |
62
|
|
|
* @return Response |
63
|
|
|
*/ |
64
|
|
|
public function indexAction(Request $request) |
65
|
|
|
{ |
66
|
|
|
return $this->processView($action = 'index', $this->view($this->find($action, false))); |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* @param Request $request |
71
|
|
|
* |
72
|
|
|
* @return Response |
73
|
|
|
*/ |
74
|
|
|
public function gridAction(Request $request) |
75
|
|
|
{ |
76
|
|
|
return $this->processView('grid', $this->view([ |
77
|
|
|
'form' => $form = $this->submitGrid($grid = $this->buildGrid(), $request), |
78
|
|
|
'grid' => $this->getGridHandler()->handle($grid, $form), |
79
|
|
|
])); |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* @param Request $request |
84
|
|
|
* |
85
|
|
|
* @return Response |
86
|
|
|
*/ |
87
|
|
|
public function batchAction(Request $request) |
88
|
|
|
{ |
89
|
|
|
$view = $this->getGridHandler()->handle( |
90
|
|
|
$grid = $this->buildGrid(), |
91
|
|
|
$form = $this->submitGrid($grid, $request) |
92
|
|
|
); |
93
|
|
|
|
94
|
|
|
if (($api = $this->getParameterResolver()->resolveApi()) && !$form->isValid()) { |
95
|
|
|
return $this->processAction('batch', $form); |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
$batchForm = $this->buildForm(GridBatchType::class, null, ['grid' => $view]); |
99
|
|
|
|
100
|
|
|
if ($this->submitForm($batchForm, $request)->isValid() && $form->isValid()) { |
101
|
|
|
try { |
102
|
|
|
$this->getGridBatcher()->batch($grid, $batchForm); |
103
|
|
|
} catch (DomainException $e) { |
104
|
|
|
$this->processException($e); |
105
|
|
|
} |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
if (!$api && !$batchForm->isValid()) { |
109
|
|
|
return $this->processView('batch', $this->view([ |
110
|
|
|
'batch_form' => $batchForm, |
111
|
|
|
'form' => $form, |
112
|
|
|
'grid' => $view, |
113
|
|
|
])); |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
return $this->processAction('batch', $batchForm); |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* @param Request $request |
121
|
|
|
* |
122
|
|
|
* @return Response |
123
|
|
|
*/ |
124
|
|
|
public function showAction(Request $request) |
125
|
|
|
{ |
126
|
|
|
return $this->processView($action = 'show', $this->view($this->find($action))); |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* @param Request $request |
131
|
|
|
* |
132
|
|
|
* @return Response |
133
|
|
|
*/ |
134
|
|
|
public function createAction(Request $request) |
135
|
|
|
{ |
136
|
|
|
$form = $this->buildForm(); |
137
|
|
|
|
138
|
|
|
if ($request->isMethod('POST') && $this->submitForm($form, $request)->isValid()) { |
139
|
|
|
try { |
140
|
|
|
$this->getDomainManager()->create($form->getData()); |
141
|
|
|
} catch (DomainException $e) { |
142
|
|
|
$this->processException($e); |
143
|
|
|
} |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
return $this->processAction('create', $form, Response::HTTP_CREATED); |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
/** |
150
|
|
|
* @param Request $request |
151
|
|
|
* |
152
|
|
|
* @return Response |
153
|
|
|
*/ |
154
|
|
|
public function updateAction(Request $request) |
155
|
|
|
{ |
156
|
|
|
$form = $this->buildForm(null, $this->find($action = 'update')); |
|
|
|
|
157
|
|
|
|
158
|
|
|
if (in_array($request->getMethod(), ['POST', 'PUT', 'PATCH'], true) |
159
|
|
|
&& $this->submitForm($form, $request)->isValid()) { |
160
|
|
|
try { |
161
|
|
|
$this->getDomainManager()->update($form->getData()); |
162
|
|
|
} catch (DomainException $e) { |
163
|
|
|
$this->processException($e); |
164
|
|
|
} |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
return $this->processAction($action, $form); |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
/** |
171
|
|
|
* @param Request $request |
172
|
|
|
* |
173
|
|
|
* @return Response |
174
|
|
|
*/ |
175
|
|
|
public function deleteAction(Request $request) |
176
|
|
|
{ |
177
|
|
|
if ($this->submitForm($this->buildForm(CsrfProtectionType::class), $request)->isValid()) { |
178
|
|
|
try { |
179
|
|
|
$this->getDomainManager()->delete($this->find('delete')); |
|
|
|
|
180
|
|
|
} catch (DomainException $e) { |
181
|
|
|
$this->processException($e); |
182
|
|
|
} |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
return $this->processAction('delete'); |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* @param string $action |
190
|
|
|
* @param bool $mandatory |
191
|
|
|
* |
192
|
|
|
* @return object|object[] |
193
|
|
|
*/ |
194
|
|
|
protected function find($action, $mandatory = true) |
195
|
|
|
{ |
196
|
|
|
$repositoryMethod = $this->getParameterResolver()->resolveRepositoryMethod($action); |
197
|
|
|
$criteria = $this->getParameterResolver()->resolveCriteria($mandatory); |
198
|
|
|
$sorting = $this->getParameterResolver()->resolveSorting(); |
199
|
|
|
|
200
|
|
|
if (($result = $this->getDomainManager()->find($action, $repositoryMethod, $criteria, $sorting)) === null) { |
201
|
|
|
throw $this->createNotFoundException(sprintf( |
202
|
|
|
'The %s does not exist (%s) (%s).', |
203
|
|
|
str_replace('_', ' ', $this->resource->getName()), |
204
|
|
|
json_encode($criteria), |
205
|
|
|
json_encode($sorting) |
206
|
|
|
)); |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
if ($result instanceof Pagerfanta) { |
210
|
|
|
$result->setCurrentPage($this->getParameterResolver()->resolveCurrentPage()); |
211
|
|
|
$result->setMaxPerPage($this->getParameterResolver()->resolveMaxPerPage()); |
212
|
|
|
} elseif (!$this->getSecurityChecker()->isGranted($action, $result)) { |
213
|
|
|
throw $this->createAccessDeniedException(); |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
return $result; |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
/** |
220
|
|
|
* @param string|object|null $form |
221
|
|
|
* @param object|null $object |
222
|
|
|
* @param mixed[] $options |
223
|
|
|
* |
224
|
|
|
* @return FormInterface |
225
|
|
|
*/ |
226
|
|
|
protected function buildForm($form = null, $object = null, array $options = []) |
227
|
|
|
{ |
228
|
|
|
return $this->getFormFactory()->create($form ?: $this->resource, $object, $options); |
|
|
|
|
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
/** |
232
|
|
|
* @param FormInterface $form |
233
|
|
|
* @param Request $request |
234
|
|
|
* |
235
|
|
|
* @return FormInterface |
236
|
|
|
*/ |
237
|
|
|
protected function submitForm(FormInterface $form, Request $request) |
238
|
|
|
{ |
239
|
|
|
$bag = $request->isMethod('GET') || $form->getConfig()->getMethod() === 'GET' |
240
|
|
|
? $request->query |
241
|
|
|
: $request->request; |
242
|
|
|
|
243
|
|
|
if ($this->getParameterResolver()->resolveApi()) { |
244
|
|
|
$data = array_merge($bag->all(), $request->files->all()); |
245
|
|
|
} else { |
246
|
|
|
$data = array_merge($bag->get($form->getName(), []), $request->files->get($form->getName(), [])); |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
array_walk_recursive($data, function (&$value) { |
250
|
|
|
if ($value === false) { |
251
|
|
|
$value = 'false'; |
252
|
|
|
} |
253
|
|
|
}); |
254
|
|
|
|
255
|
|
|
return $form->submit($data, !$request->isMethod('PATCH')); |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
/** |
259
|
|
|
* @return GridInterface |
260
|
|
|
*/ |
261
|
|
|
protected function buildGrid() |
262
|
|
|
{ |
263
|
|
|
return $this->getGridBuilder()->build($this->getParameterResolver()->resolveGrid($this->resource)); |
264
|
|
|
} |
265
|
|
|
|
266
|
|
|
/** |
267
|
|
|
* @param GridInterface $grid |
268
|
|
|
* @param Request $request |
269
|
|
|
* |
270
|
|
|
* @return FormInterface |
271
|
|
|
*/ |
272
|
|
|
protected function submitGrid(GridInterface $grid, Request $request) |
273
|
|
|
{ |
274
|
|
|
return $this->submitForm($this->buildForm(GridType::class, null, ['grid' => $grid]), $request); |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
/** |
278
|
|
|
* @param string $action |
279
|
|
|
* @param FormInterface|null $form |
280
|
|
|
* @param int $statusCode |
281
|
|
|
* |
282
|
|
|
* @return Response |
283
|
|
|
*/ |
284
|
|
|
protected function processAction($action, FormInterface $form = null, $statusCode = Response::HTTP_NO_CONTENT) |
285
|
|
|
{ |
286
|
|
|
$statusCode = $this->getParameterResolver()->resolveStatusCode($statusCode); |
287
|
|
|
|
288
|
|
|
$this->getRestEventDispatcher()->dispatch( |
289
|
|
|
RestEvents::ACTION, |
290
|
|
|
$event = new ActionEvent($this->resource, $action, $form, $statusCode) |
291
|
|
|
); |
292
|
|
|
|
293
|
|
|
$view = $event->getView(); |
294
|
|
|
$statusCode = $view->getStatusCode(); |
295
|
|
|
|
296
|
|
|
return $statusCode >= 300 && $statusCode < 400 |
297
|
|
|
? $this->handleView($view) |
|
|
|
|
298
|
|
|
: $this->processView($action, $view); |
|
|
|
|
299
|
|
|
} |
300
|
|
|
|
301
|
|
|
/** |
302
|
|
|
* @param string $action |
303
|
|
|
* @param View $view |
304
|
|
|
* |
305
|
|
|
* @return Response |
306
|
|
|
*/ |
307
|
|
|
protected function processView($action, View $view) |
308
|
|
|
{ |
309
|
|
|
$this->getRestEventDispatcher()->dispatch( |
310
|
|
|
RestEvents::VIEW, |
311
|
|
|
$event = new ViewEvent($this->resource, $action, $view) |
312
|
|
|
); |
313
|
|
|
|
314
|
|
|
return $this->handleView($event->getView()); |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
/** |
318
|
|
|
* @param DomainException $domainException |
319
|
|
|
*/ |
320
|
|
|
protected function processException(DomainException $domainException) |
321
|
|
|
{ |
322
|
|
|
if ($this->getParameterResolver()->resolveApi()) { |
323
|
|
|
throw new HttpException( |
324
|
|
|
$domainException->getStatusCode() ?: 500, |
325
|
|
|
$domainException->getMessage() ?: 'Internal Server Error', |
326
|
|
|
$domainException |
327
|
|
|
); |
328
|
|
|
} |
329
|
|
|
} |
330
|
|
|
|
331
|
|
|
/** |
332
|
|
|
* @return FormFactoryInterface |
333
|
|
|
*/ |
334
|
|
|
protected function getFormFactory() |
335
|
|
|
{ |
336
|
|
|
return $this->get('lug.resource.form.factory'); |
337
|
|
|
} |
338
|
|
|
|
339
|
|
|
/** |
340
|
|
|
* @return FactoryInterface |
341
|
|
|
*/ |
342
|
|
|
protected function getFactory() |
343
|
|
|
{ |
344
|
|
|
return $this->get('lug.resource.registry.factory')[$this->resource->getName()]; |
345
|
|
|
} |
346
|
|
|
|
347
|
|
|
/** |
348
|
|
|
* @return DomainManagerInterface |
349
|
|
|
*/ |
350
|
|
|
protected function getDomainManager() |
351
|
|
|
{ |
352
|
|
|
return $this->get('lug.resource.registry.domain_manager')[$this->resource->getName()]; |
353
|
|
|
} |
354
|
|
|
|
355
|
|
|
/** |
356
|
|
|
* @return SecurityCheckerInterface |
357
|
|
|
*/ |
358
|
|
|
protected function getSecurityChecker() |
359
|
|
|
{ |
360
|
|
|
return $this->get('lug.resource.security.checker'); |
361
|
|
|
} |
362
|
|
|
|
363
|
|
|
/** |
364
|
|
|
* @return ParameterResolverInterface |
365
|
|
|
*/ |
366
|
|
|
protected function getParameterResolver() |
367
|
|
|
{ |
368
|
|
|
return $this->get('lug.resource.routing.parameter_resolver'); |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
/** |
372
|
|
|
* @return GridBuilderInterface |
373
|
|
|
*/ |
374
|
|
|
protected function getGridBuilder() |
375
|
|
|
{ |
376
|
|
|
return $this->get('lug.grid.builder'); |
377
|
|
|
} |
378
|
|
|
|
379
|
|
|
/** |
380
|
|
|
* @return GridHandlerInterface |
381
|
|
|
*/ |
382
|
|
|
protected function getGridHandler() |
383
|
|
|
{ |
384
|
|
|
return $this->get('lug.grid.handler'); |
385
|
|
|
} |
386
|
|
|
|
387
|
|
|
/** |
388
|
|
|
* @return BatcherInterface |
389
|
|
|
*/ |
390
|
|
|
protected function getGridBatcher() |
391
|
|
|
{ |
392
|
|
|
return $this->get('lug.grid.batcher'); |
393
|
|
|
} |
394
|
|
|
|
395
|
|
|
/** |
396
|
|
|
* @return EventDispatcherInterface |
397
|
|
|
*/ |
398
|
|
|
protected function getRestEventDispatcher() |
399
|
|
|
{ |
400
|
|
|
return $this->get('lug.resource.rest.event_dispatcher'); |
401
|
|
|
} |
402
|
|
|
} |
403
|
|
|
|
This check looks at variables that are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.