Controller::submitGrid()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
ccs 0
cts 2
cp 0
cc 1
eloc 2
nc 1
nop 2
crap 2
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'));
0 ignored issues
show
Bug introduced by
It seems like $this->find($action = 'update') targeting Lug\Bundle\ResourceBundl...ller\Controller::find() can also be of type array<integer,object>; however, Lug\Bundle\ResourceBundl...Controller::buildForm() does only seem to accept object|null, maybe add an additional type check?

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.

Loading history...
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'));
0 ignored issues
show
Bug introduced by
It seems like $this->find('delete') targeting Lug\Bundle\ResourceBundl...ller\Controller::find() can also be of type array<integer,object>; however, Lug\Component\Resource\D...agerInterface::delete() does only seem to accept object, maybe add an additional type check?

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.

Loading history...
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);
0 ignored issues
show
Bug introduced by
It seems like $form ?: $this->resource can also be of type object; however, Lug\Bundle\ResourceBundl...toryInterface::create() does only seem to accept string|object<Symfony\Co...ResourceInterface>|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
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)
0 ignored issues
show
Bug introduced by
It seems like $view defined by $event->getView() on line 293 can be null; however, FOS\RestBundle\Controlle...llerTrait::handleView() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
298
            : $this->processView($action, $view);
0 ignored issues
show
Bug introduced by
It seems like $view defined by $event->getView() on line 293 can be null; however, Lug\Bundle\ResourceBundl...ntroller::processView() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
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