Completed
Pull Request — master (#26)
by Eric
07:55
created

Controller::createAction()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 14
ccs 0
cts 8
cp 0
rs 9.2
cc 4
eloc 8
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\Component\Grid\Model\Builder\GridBuilderInterface;
27
use Lug\Component\Grid\Model\GridInterface;
28
use Lug\Component\Resource\Controller\ControllerInterface;
29
use Lug\Component\Resource\Domain\DomainManagerInterface;
30
use Lug\Component\Resource\Exception\DomainException;
31
use Lug\Component\Resource\Factory\FactoryInterface;
32
use Lug\Component\Resource\Model\ResourceInterface;
33
use Pagerfanta\Pagerfanta;
34
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
35
use Symfony\Component\Form\FormInterface;
36
use Symfony\Component\HttpFoundation\Request;
37
use Symfony\Component\HttpFoundation\Response;
38
use Symfony\Component\HttpKernel\Exception\HttpException;
39
40
/**
41
 * @author GeLo <[email protected]>
42
 */
43
class Controller extends FOSRestController implements ControllerInterface
44
{
45
    /**
46
     * @var ResourceInterface
47
     */
48
    private $resource;
49
50
    /**
51
     * @param ResourceInterface $resource
52
     */
53
    public function __construct(ResourceInterface $resource)
54
    {
55
        $this->resource = $resource;
56
    }
57
58
    /**
59
     * @param Request $request
60
     *
61
     * @return Response
62
     */
63
    public function indexAction(Request $request)
64
    {
65
        return $this->processView($this->view($this->find('index', false)));
66
    }
67
68
    /**
69
     * @param Request $request
70
     *
71
     * @return Response
72
     */
73
    public function gridAction(Request $request)
74
    {
75
        return $this->processView($this->view([
76
            'form' => $form = $this->submitGrid($grid = $this->buildGrid(), $request),
77
            'grid' => $this->getGridHandler()->handle($grid, $form),
78
        ]));
79
    }
80
81
    /**
82
     * @param Request $request
83
     *
84
     * @return Response
85
     */
86
    public function batchAction(Request $request)
87
    {
88
        $view = $this->getGridHandler()->handle(
89
            $grid = $this->buildGrid(),
90
            $form = $this->submitGrid($grid, $request)
91
        );
92
93
        if (($api = $this->getParameterResolver()->resolveApi()) && !$form->isValid()) {
94
            return $this->processAction($form);
95
        }
96
97
        $batchForm = $this->buildForm(GridBatchType::class, null, ['grid' => $view]);
98
99
        if ($this->submitForm($batchForm, $request)->isValid() && $form->isValid()) {
100
            try {
101
                $this->getGridBatcher()->batch($grid, $batchForm);
102
            } catch (DomainException $e) {
103
                $this->processException($e);
104
            }
105
        }
106
107
        if (!$api && !$batchForm->isValid()) {
108
            return $this->processView($this->view([
109
                'batch_form' => $batchForm,
110
                'form'       => $form,
111
                'grid'       => $view,
112
            ]));
113
        }
114
115
        return $this->processAction($batchForm);
116
    }
117
118
    /**
119
     * @param Request $request
120
     *
121
     * @return Response
122
     */
123
    public function showAction(Request $request)
124
    {
125
        return $this->processView($this->view($this->find('show')));
126
    }
127
128
    /**
129
     * @param Request $request
130
     *
131
     * @return Response
132
     */
133
    public function createAction(Request $request)
134
    {
135
        $form = $this->buildForm(null, $this->getFactory()->create());
136
137
        if ($request->isMethod('POST') && $this->submitForm($form, $request)->isValid()) {
138
            try {
139
                $this->getDomainManager()->create($form->getData());
140
            } catch (DomainException $e) {
141
                $this->processException($e);
142
            }
143
        }
144
145
        return $this->processAction($form, Response::HTTP_CREATED);
146
    }
147
148
    /**
149
     * @param Request $request
150
     *
151
     * @return Response
152
     */
153
    public function updateAction(Request $request)
154
    {
155
        $form = $this->buildForm(null, $this->find('update'));
0 ignored issues
show
Bug introduced by
It seems like $this->find('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...
156
157
        if (in_array($request->getMethod(), ['POST', 'PUT', 'PATCH'], true)
158
            && $this->submitForm($form, $request)->isValid()) {
159
            try {
160
                $this->getDomainManager()->update($form->getData());
161
            } catch (DomainException $e) {
162
                $this->processException($e);
163
            }
164
        }
165
166
        return $this->processAction($form);
167
    }
168
169
    /**
170
     * @param Request $request
171
     *
172
     * @return Response
173
     */
174
    public function deleteAction(Request $request)
175
    {
176
        if ($this->submitForm($this->buildForm(CsrfProtectionType::class), $request)->isValid()) {
177
            try {
178
                $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...
179
            } catch (DomainException $e) {
180
                $this->processException($e);
181
            }
182
        }
183
184
        return $this->processAction();
185
    }
186
187
    /**
188
     * @param string $action
189
     * @param bool   $mandatory
190
     *
191
     * @return object|object[]
192
     */
193
    private function find($action, $mandatory = true)
194
    {
195
        $repositoryMethod = $this->getParameterResolver()->resolveRepositoryMethod($action);
196
        $criteria = $this->getParameterResolver()->resolveCriteria($mandatory);
197
        $sorting = $this->getParameterResolver()->resolveSorting();
198
199
        if (($result = $this->getDomainManager()->find($action, $repositoryMethod, $criteria, $sorting)) === null) {
200
            throw $this->createNotFoundException(sprintf(
201
                'The %s does not exist (%s) (%s).',
202
                str_replace('_', ' ', $this->resource->getName()),
203
                json_encode($criteria),
204
                json_encode($sorting)
205
            ));
206
        }
207
208
        if ($result instanceof Pagerfanta) {
209
            $result->setCurrentPage($this->getParameterResolver()->resolveCurrentPage());
210
            $result->setMaxPerPage($this->getParameterResolver()->resolveMaxPerPage());
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
    private 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
    private 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
    private 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
    private function submitGrid(GridInterface $grid, Request $request)
270
    {
271
        return $this->submitForm($this->buildForm(GridType::class, null, ['grid' => $grid]), $request);
272
    }
273
274
    /**
275
     * @param FormInterface|null $form
276
     * @param int                $statusCode
277
     *
278
     * @return Response
279
     */
280
    private function processAction(FormInterface $form = null, $statusCode = Response::HTTP_NO_CONTENT)
281
    {
282
        $this->getEventDispatcher()->dispatch(
283
            RestEvents::ACTION,
284
            $event = new ActionEvent($this->resource, $form, $statusCode)
285
        );
286
287
        $view = $event->getView();
288
        $statusCode = $view->getStatusCode();
289
290
        return $statusCode >= 300 && $statusCode < 400
291
            ? $this->handleView($view)
0 ignored issues
show
Bug introduced by
It seems like $view defined by $event->getView() on line 287 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...
292
            : $this->processView($view);
0 ignored issues
show
Bug introduced by
It seems like $view defined by $event->getView() on line 287 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...
293
    }
294
295
    /**
296
     * @param View $view
297
     *
298
     * @return Response
299
     */
300
    private function processView(View $view)
301
    {
302
        $this->getEventDispatcher()->dispatch(RestEvents::VIEW, $event = new ViewEvent($this->resource, $view));
303
304
        return $this->handleView($event->getView());
305
    }
306
307
    /**
308
     * @param DomainException $domainException
309
     */
310
    private function processException(DomainException $domainException)
311
    {
312
        if ($this->getParameterResolver()->resolveApi()) {
313
            throw new HttpException(
314
                $domainException->getStatusCode() ?: 500,
315
                $domainException->getMessage() ?: 'Internal Server Error',
316
                $domainException
317
            );
318
        }
319
    }
320
321
    /**
322
     * @return EventDispatcherInterface
323
     */
324
    private function getEventDispatcher()
325
    {
326
        return $this->get('event_dispatcher');
327
    }
328
329
    /**
330
     * @return FormFactoryInterface
331
     */
332
    private function getFormFactory()
333
    {
334
        return $this->get('lug.resource.form.factory');
335
    }
336
337
    /**
338
     * @return FactoryInterface
339
     */
340
    private function getFactory()
341
    {
342
        return $this->get('lug.resource.registry.factory')[$this->resource->getName()];
343
    }
344
345
    /**
346
     * @return DomainManagerInterface
347
     */
348
    private function getDomainManager()
349
    {
350
        return $this->get('lug.resource.registry.domain_manager')[$this->resource->getName()];
351
    }
352
353
    /**
354
     * @return ParameterResolverInterface
355
     */
356
    private function getParameterResolver()
357
    {
358
        return $this->get('lug.resource.routing.parameter_resolver');
359
    }
360
361
    /**
362
     * @return GridBuilderInterface
363
     */
364
    private function getGridBuilder()
365
    {
366
        return $this->get('lug.grid.builder');
367
    }
368
369
    /**
370
     * @return GridHandlerInterface
371
     */
372
    private function getGridHandler()
373
    {
374
        return $this->get('lug.grid.handler');
375
    }
376
377
    /**
378
     * @return BatcherInterface
379
     */
380
    private function getGridBatcher()
381
    {
382
        return $this->get('lug.grid.batcher');
383
    }
384
}
385