Completed
Pull Request — master (#64)
by Eric
14:46
created

Controller::updateAction()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 15
ccs 0
cts 9
cp 0
rs 9.2
cc 4
eloc 9
nc 3
nop 1
crap 20
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 && !$this->getSecurityChecker()->isGranted($action, $result)) {
210
            throw $this->createAccessDeniedException();
211
        }
212
213
        return $result;
214
    }
215
216
    /**
217
     * @param string|object|null $form
218
     * @param object|null        $object
219
     * @param mixed[]            $options
220
     *
221
     * @return FormInterface
222
     */
223
    protected function buildForm($form = null, $object = null, array $options = [])
224
    {
225
        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...
226
    }
227
228
    /**
229
     * @param FormInterface $form
230
     * @param Request       $request
231
     *
232
     * @return FormInterface
233
     */
234
    protected function submitForm(FormInterface $form, Request $request)
235
    {
236
        $bag = $request->isMethod('GET') || $form->getConfig()->getMethod() === 'GET'
237
            ? $request->query
238
            : $request->request;
239
240
        if ($this->getParameterResolver()->resolveApi()) {
241
            $data = $bag->all();
242
        } else {
243
            $data = $bag->get($form->getName(), []);
244
        }
245
246
        array_walk_recursive($data, function (&$value) {
247
            if ($value === false) {
248
                $value = 'false';
249
            }
250
        });
251
252
        return $form->submit($data, !$request->isMethod('PATCH'));
253
    }
254
255
    /**
256
     * @return GridInterface
257
     */
258
    protected function buildGrid()
259
    {
260
        return $this->getGridBuilder()->build($this->getParameterResolver()->resolveGrid($this->resource));
261
    }
262
263
    /**
264
     * @param GridInterface $grid
265
     * @param Request       $request
266
     *
267
     * @return FormInterface
268
     */
269
    protected function submitGrid(GridInterface $grid, Request $request)
270
    {
271
        return $this->submitForm($this->buildForm(GridType::class, null, ['grid' => $grid]), $request);
272
    }
273
274
    /**
275
     * @param string             $action
276
     * @param FormInterface|null $form
277
     * @param int                $statusCode
278
     *
279
     * @return Response
280
     */
281
    protected function processAction($action, FormInterface $form = null, $statusCode = Response::HTTP_NO_CONTENT)
282
    {
283
        $statusCode = $this->getParameterResolver()->resolveStatusCode($statusCode);
284
285
        $this->getRestEventDispatcher()->dispatch(
286
            RestEvents::ACTION,
287
            $event = new ActionEvent($this->resource, $action, $form, $statusCode)
288
        );
289
290
        $view = $event->getView();
291
        $statusCode = $view->getStatusCode();
292
293
        return $statusCode >= 300 && $statusCode < 400
294
            ? $this->handleView($view)
0 ignored issues
show
Bug introduced by
It seems like $view defined by $event->getView() on line 290 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...
295
            : $this->processView($action, $view);
0 ignored issues
show
Bug introduced by
It seems like $view defined by $event->getView() on line 290 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...
296
    }
297
298
    /**
299
     * @param string $action
300
     * @param View   $view
301
     *
302
     * @return Response
303
     */
304
    protected function processView($action, View $view)
305
    {
306
        $this->getRestEventDispatcher()->dispatch(
307
            RestEvents::VIEW,
308
            $event = new ViewEvent($this->resource, $action, $view)
309
        );
310
311
        return $this->handleView($event->getView());
312
    }
313
314
    /**
315
     * @param DomainException $domainException
316
     */
317
    protected function processException(DomainException $domainException)
318
    {
319
        if ($this->getParameterResolver()->resolveApi()) {
320
            throw new HttpException(
321
                $domainException->getStatusCode() ?: 500,
322
                $domainException->getMessage() ?: 'Internal Server Error',
323
                $domainException
324
            );
325
        }
326
    }
327
328
    /**
329
     * @return FormFactoryInterface
330
     */
331
    protected function getFormFactory()
332
    {
333
        return $this->get('lug.resource.form.factory');
334
    }
335
336
    /**
337
     * @return FactoryInterface
338
     */
339
    protected function getFactory()
340
    {
341
        return $this->get('lug.resource.registry.factory')[$this->resource->getName()];
342
    }
343
344
    /**
345
     * @return DomainManagerInterface
346
     */
347
    protected function getDomainManager()
348
    {
349
        return $this->get('lug.resource.registry.domain_manager')[$this->resource->getName()];
350
    }
351
352
    /**
353
     * @return SecurityCheckerInterface
354
     */
355
    protected function getSecurityChecker()
356
    {
357
        return $this->get('lug.resource.security.checker');
358
    }
359
360
    /**
361
     * @return ParameterResolverInterface
362
     */
363
    protected function getParameterResolver()
364
    {
365
        return $this->get('lug.resource.routing.parameter_resolver');
366
    }
367
368
    /**
369
     * @return GridBuilderInterface
370
     */
371
    protected function getGridBuilder()
372
    {
373
        return $this->get('lug.grid.builder');
374
    }
375
376
    /**
377
     * @return GridHandlerInterface
378
     */
379
    protected function getGridHandler()
380
    {
381
        return $this->get('lug.grid.handler');
382
    }
383
384
    /**
385
     * @return BatcherInterface
386
     */
387
    protected function getGridBatcher()
388
    {
389
        return $this->get('lug.grid.batcher');
390
    }
391
392
    /**
393
     * @return EventDispatcherInterface
394
     */
395
    protected function getRestEventDispatcher()
396
    {
397
        return $this->get('lug.resource.rest.event_dispatcher');
398
    }
399
}
400