Completed
Push — master ( de08bc...2cdfe0 )
by Marko
13s
created

CRUDController::editAction()   D

Complexity

Conditions 15
Paths 50

Size

Total Lines 113

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 113
rs 4.7333
c 0
b 0
f 0
cc 15
nc 50
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 57 and the first side effect is on line 51.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Sonata Project package.
7
 *
8
 * (c) Thomas Rabaix <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Sonata\AdminBundle\Controller;
15
16
use Doctrine\Common\Inflector\Inflector;
17
use Psr\Log\LoggerInterface;
18
use Psr\Log\NullLogger;
19
use Sonata\AdminBundle\Admin\AdminInterface;
20
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
21
use Sonata\AdminBundle\Exception\LockException;
22
use Sonata\AdminBundle\Exception\ModelManagerException;
23
use Sonata\AdminBundle\Templating\TemplateRegistryInterface;
24
use Sonata\AdminBundle\Util\AdminObjectAclData;
25
use Sonata\AdminBundle\Util\AdminObjectAclManipulator;
26
use Symfony\Bridge\Twig\AppVariable;
27
use Symfony\Bridge\Twig\Command\DebugCommand;
28
use Symfony\Bridge\Twig\Extension\FormExtension;
29
use Symfony\Bridge\Twig\Form\TwigRenderer;
30
use Symfony\Bundle\FrameworkBundle\Controller\ControllerTrait;
31
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
32
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
33
use Symfony\Component\DependencyInjection\ContainerInterface;
34
use Symfony\Component\Form\FormRenderer;
35
use Symfony\Component\Form\FormView;
36
use Symfony\Component\HttpFoundation\JsonResponse;
37
use Symfony\Component\HttpFoundation\RedirectResponse;
38
use Symfony\Component\HttpFoundation\Request;
39
use Symfony\Component\HttpFoundation\Response;
40
use Symfony\Component\HttpKernel\Exception\HttpException;
41
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
42
use Symfony\Component\PropertyAccess\PropertyAccess;
43
use Symfony\Component\PropertyAccess\PropertyPath;
44
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
45
use Symfony\Component\Security\Csrf\CsrfToken;
46
use function is_array;
47
48
// BC for Symfony < 3.3 where this trait does not exist
49
// NEXT_MAJOR: Remove the polyfill and inherit from \Symfony\Bundle\FrameworkBundle\Controller\Controller again
50
if (!trait_exists(ControllerTrait::class)) {
51
    require_once __DIR__.'/PolyfillControllerTrait.php';
52
}
53
54
/**
55
 * @author Thomas Rabaix <[email protected]>
56
 */
57
class CRUDController implements ContainerAwareInterface
58
{
59
    // NEXT_MAJOR: Don't use these traits anymore (inherit from Controller instead)
60
    use ControllerTrait, ContainerAwareTrait {
61
        ControllerTrait::render as originalRender;
62
    }
63
64
    /**
65
     * The related Admin class.
66
     *
67
     * @var AdminInterface
68
     */
69
    protected $admin;
70
71
    /**
72
     * The template registry of the related Admin class.
73
     *
74
     * @var TemplateRegistryInterface
75
     */
76
    private $templateRegistry;
77
78
    // BC for Symfony 3.3 where ControllerTrait exists but does not contain get() and has() methods.
79
    public function __call($method, $arguments)
80
    {
81
        if (in_array($method, ['get', 'has'])) {
82
            return call_user_func_array([$this->container, $method], $arguments);
83
        }
84
85
        if (method_exists($this, 'proxyToControllerClass')) {
86
            return $this->proxyToControllerClass($method, $arguments);
0 ignored issues
show
Documentation Bug introduced by
The method proxyToControllerClass does not exist on object<Sonata\AdminBundl...troller\CRUDController>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
87
        }
88
89
        throw new \LogicException('Call to undefined method '.__CLASS__.'::'.$method);
90
    }
91
92
    public function setContainer(ContainerInterface $container = null): void
93
    {
94
        $this->container = $container;
95
96
        $this->configure();
97
    }
98
99
    /**
100
     * NEXT_MAJOR: Remove this method.
101
     *
102
     * @see renderWithExtraParams()
103
     *
104
     * @param string $view       The view name
105
     * @param array  $parameters An array of parameters to pass to the view
106
     *
107
     * @return Response A Response instance
108
     *
109
     * @deprecated since version 3.27, to be removed in 4.0. Use Sonata\AdminBundle\Controller\CRUDController::renderWithExtraParams() instead.
110
     */
111
    public function render($view, array $parameters = [], Response $response = null)
112
    {
113
        @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
114
            'Method '.__CLASS__.'::render has been renamed to '.__CLASS__.'::renderWithExtraParams.',
115
            E_USER_DEPRECATED
116
        );
117
118
        return $this->renderWithExtraParams($view, $parameters, $response);
119
    }
120
121
    /**
122
     * Renders a view while passing mandatory parameters on to the template.
123
     *
124
     * @param string $view The view name
125
     *
126
     * @return Response A Response instance
127
     */
128
    public function renderWithExtraParams($view, array $parameters = [], Response $response = null)
129
    {
130
        if (!$this->isXmlHttpRequest()) {
131
            $parameters['breadcrumbs_builder'] = $this->get('sonata.admin.breadcrumbs_builder');
132
        }
133
        $parameters['admin'] = $parameters['admin'] ??
134
            $this->admin;
135
136
        $parameters['base_template'] = $parameters['base_template'] ??
137
            $this->getBaseTemplate();
138
139
        $parameters['admin_pool'] = $this->get('sonata.admin.pool');
140
141
        //NEXT_MAJOR: Remove method alias and use $this->render() directly.
142
        return $this->originalRender($view, $parameters, $response);
143
    }
144
145
    /**
146
     * List action.
147
     *
148
     * @throws AccessDeniedException If access is not granted
149
     *
150
     * @return Response
151
     */
152
    public function listAction()
153
    {
154
        $request = $this->getRequest();
155
156
        $this->admin->checkAccess('list');
157
158
        $preResponse = $this->preList($request);
159
        if (null !== $preResponse) {
160
            return $preResponse;
161
        }
162
163
        if ($listMode = $request->get('_list_mode')) {
164
            $this->admin->setListMode($listMode);
165
        }
166
167
        $datagrid = $this->admin->getDatagrid();
168
        $formView = $datagrid->getForm()->createView();
169
170
        // set the theme for the current Admin Form
171
        $this->setFormTheme($formView, $this->admin->getFilterTheme());
0 ignored issues
show
Documentation introduced by
$this->admin->getFilterTheme() is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
172
173
        // NEXT_MAJOR: Remove this line and use commented line below it instead
174
        $template = $this->admin->getTemplate('list');
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...nterface::getTemplate() has been deprecated with message: since 3.35. To be removed in 4.0. Use TemplateRegistry services instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
175
        // $template = $this->templateRegistry->getTemplate('list');
176
177
        return $this->renderWithExtraParams($template, [
178
            'action' => 'list',
179
            'form' => $formView,
180
            'datagrid' => $datagrid,
181
            'csrf_token' => $this->getCsrfToken('sonata.batch'),
182
            'export_formats' => $this->has('sonata.admin.admin_exporter') ?
183
                $this->get('sonata.admin.admin_exporter')->getAvailableFormats($this->admin) :
184
                $this->admin->getExportFormats(),
185
        ], null);
186
    }
187
188
    /**
189
     * Execute a batch delete.
190
     *
191
     * @throws AccessDeniedException If access is not granted
192
     *
193
     * @return RedirectResponse
194
     */
195
    public function batchActionDelete(ProxyQueryInterface $query)
196
    {
197
        $this->admin->checkAccess('batchDelete');
198
199
        $modelManager = $this->admin->getModelManager();
200
201
        try {
202
            $modelManager->batchDelete($this->admin->getClass(), $query);
203
            $this->addFlash(
204
                'sonata_flash_success',
205
                $this->trans('flash_batch_delete_success', [], 'SonataAdminBundle')
206
            );
207
        } catch (ModelManagerException $e) {
208
            $this->handleModelManagerException($e);
209
            $this->addFlash(
210
                'sonata_flash_error',
211
                $this->trans('flash_batch_delete_error', [], 'SonataAdminBundle')
212
            );
213
        }
214
215
        return $this->redirectToList();
216
    }
217
218
    /**
219
     * Delete action.
220
     *
221
     * @param int|string|null $id
222
     *
223
     * @throws NotFoundHttpException If the object does not exist
224
     * @throws AccessDeniedException If access is not granted
225
     *
226
     * @return Response|RedirectResponse
227
     */
228
    public function deleteAction($id)
0 ignored issues
show
Unused Code introduced by
The parameter $id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
229
    {
230
        $request = $this->getRequest();
231
        $id = $request->get($this->admin->getIdParameter());
232
        $object = $this->admin->getObject($id);
233
234
        if (!$object) {
235
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
236
        }
237
238
        $this->checkParentChildAssociation($request, $object);
239
240
        $this->admin->checkAccess('delete', $object);
241
242
        $preResponse = $this->preDelete($request, $object);
243
        if (null !== $preResponse) {
244
            return $preResponse;
245
        }
246
247
        if ('DELETE' == $this->getRestMethod()) {
248
            // check the csrf token
249
            $this->validateCsrfToken('sonata.delete');
250
251
            $objectName = $this->admin->toString($object);
252
253
            try {
254
                $this->admin->delete($object);
255
256
                if ($this->isXmlHttpRequest()) {
257
                    return $this->renderJson(['result' => 'ok'], 200, []);
258
                }
259
260
                $this->addFlash(
261
                    'sonata_flash_success',
262
                    $this->trans(
263
                        'flash_delete_success',
264
                        ['%name%' => $this->escapeHtml($objectName)],
265
                        'SonataAdminBundle'
266
                    )
267
                );
268
            } catch (ModelManagerException $e) {
269
                $this->handleModelManagerException($e);
270
271
                if ($this->isXmlHttpRequest()) {
272
                    return $this->renderJson(['result' => 'error'], 200, []);
273
                }
274
275
                $this->addFlash(
276
                    'sonata_flash_error',
277
                    $this->trans(
278
                        'flash_delete_error',
279
                        ['%name%' => $this->escapeHtml($objectName)],
280
                        'SonataAdminBundle'
281
                    )
282
                );
283
            }
284
285
            return $this->redirectTo($object);
286
        }
287
288
        // NEXT_MAJOR: Remove this line and use commented line below it instead
289
        $template = $this->admin->getTemplate('delete');
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...nterface::getTemplate() has been deprecated with message: since 3.35. To be removed in 4.0. Use TemplateRegistry services instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
290
        // $template = $this->templateRegistry->getTemplate('delete');
291
292
        return $this->renderWithExtraParams($template, [
293
            'object' => $object,
294
            'action' => 'delete',
295
            'csrf_token' => $this->getCsrfToken('sonata.delete'),
296
        ], null);
297
    }
298
299
    /**
300
     * Edit action.
301
     *
302
     * @param int|string|null $id
303
     *
304
     * @throws NotFoundHttpException If the object does not exist
305
     * @throws \RuntimeException     If no editable field is defined
306
     * @throws AccessDeniedException If access is not granted
307
     *
308
     * @return Response|RedirectResponse
309
     */
310
    public function editAction($id = null)
0 ignored issues
show
Unused Code introduced by
The parameter $id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
311
    {
312
        $request = $this->getRequest();
313
        // the key used to lookup the template
314
        $templateKey = 'edit';
315
316
        $id = $request->get($this->admin->getIdParameter());
317
        $existingObject = $this->admin->getObject($id);
318
319
        if (!$existingObject) {
320
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
321
        }
322
323
        $this->checkParentChildAssociation($request, $existingObject);
324
325
        $this->admin->checkAccess('edit', $existingObject);
326
327
        $preResponse = $this->preEdit($request, $existingObject);
328
        if (null !== $preResponse) {
329
            return $preResponse;
330
        }
331
332
        $this->admin->setSubject($existingObject);
333
        $objectId = $this->admin->getNormalizedIdentifier($existingObject);
334
335
        if (!is_array($fields = $this->admin->getForm()->all()) || 0 === count($fields)) {
336
            throw new \RuntimeException(
337
                'No editable field defined. Did you forget to implement the "configureFormFields" method?'
338
            );
339
        }
340
341
        /** @var $form Form */
342
        $form = $this->admin->getForm();
343
        $form->setData($existingObject);
344
        $form->handleRequest($request);
345
346
        if ($form->isSubmitted()) {
347
            $isFormValid = $form->isValid();
348
349
            // persist if the form was valid and if in preview mode the preview was approved
350
            if ($isFormValid && (!$this->isInPreviewMode() || $this->isPreviewApproved())) {
351
                $submittedObject = $form->getData();
352
                $this->admin->setSubject($submittedObject);
353
354
                try {
355
                    $existingObject = $this->admin->update($submittedObject);
356
357
                    if ($this->isXmlHttpRequest()) {
358
                        return $this->renderJson([
359
                            'result' => 'ok',
360
                            'objectId' => $objectId,
361
                            'objectName' => $this->escapeHtml($this->admin->toString($existingObject)),
362
                        ], 200, []);
363
                    }
364
365
                    $this->addFlash(
366
                        'sonata_flash_success',
367
                        $this->trans(
368
                            'flash_edit_success',
369
                            ['%name%' => $this->escapeHtml($this->admin->toString($existingObject))],
370
                            'SonataAdminBundle'
371
                        )
372
                    );
373
374
                    // redirect to edit mode
375
                    return $this->redirectTo($existingObject);
376
                } catch (ModelManagerException $e) {
377
                    $this->handleModelManagerException($e);
378
379
                    $isFormValid = false;
380
                } catch (LockException $e) {
381
                    $this->addFlash('sonata_flash_error', $this->trans('flash_lock_error', [
382
                        '%name%' => $this->escapeHtml($this->admin->toString($existingObject)),
383
                        '%link_start%' => '<a href="'.$this->admin->generateObjectUrl('edit', $existingObject).'">',
384
                        '%link_end%' => '</a>',
385
                    ], 'SonataAdminBundle'));
386
                }
387
            }
388
389
            // show an error message if the form failed validation
390
            if (!$isFormValid) {
391
                if (!$this->isXmlHttpRequest()) {
392
                    $this->addFlash(
393
                        'sonata_flash_error',
394
                        $this->trans(
395
                            'flash_edit_error',
396
                            ['%name%' => $this->escapeHtml($this->admin->toString($existingObject))],
397
                            'SonataAdminBundle'
398
                        )
399
                    );
400
                }
401
            } elseif ($this->isPreviewRequested()) {
402
                // enable the preview template if the form was valid and preview was requested
403
                $templateKey = 'preview';
404
                $this->admin->getShow();
405
            }
406
        }
407
408
        $formView = $form->createView();
409
        // set the theme for the current Admin Form
410
        $this->setFormTheme($formView, $this->admin->getFormTheme());
0 ignored issues
show
Documentation introduced by
$this->admin->getFormTheme() is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
411
412
        // NEXT_MAJOR: Remove this line and use commented line below it instead
413
        $template = $this->admin->getTemplate($templateKey);
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...nterface::getTemplate() has been deprecated with message: since 3.35. To be removed in 4.0. Use TemplateRegistry services instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
414
        // $template = $this->templateRegistry->getTemplate($templateKey);
415
416
        return $this->renderWithExtraParams($template, [
417
            'action' => 'edit',
418
            'form' => $formView,
419
            'object' => $existingObject,
420
            'objectId' => $objectId,
421
        ], null);
422
    }
423
424
    /**
425
     * Batch action.
426
     *
427
     * @throws NotFoundHttpException If the HTTP method is not POST
428
     * @throws \RuntimeException     If the batch action is not defined
429
     *
430
     * @return Response|RedirectResponse
431
     */
432
    public function batchAction()
433
    {
434
        $request = $this->getRequest();
435
        $restMethod = $this->getRestMethod();
436
437
        if ('POST' !== $restMethod) {
438
            throw $this->createNotFoundException(sprintf('Invalid request type "%s", POST expected', $restMethod));
439
        }
440
441
        // check the csrf token
442
        $this->validateCsrfToken('sonata.batch');
443
444
        $confirmation = $request->get('confirmation', false);
445
446
        if ($data = json_decode($request->get('data', ''), true)) {
447
            $action = $data['action'];
448
            $idx = $data['idx'];
449
            $allElements = $data['all_elements'];
450
            $request->request->replace(array_merge($request->request->all(), $data));
451
        } else {
452
            $request->request->set('idx', $request->get('idx', []));
453
            $request->request->set('all_elements', $request->get('all_elements', false));
454
455
            $action = $request->get('action');
456
            $idx = $request->get('idx');
457
            $allElements = $request->get('all_elements');
458
            $data = $request->request->all();
459
460
            unset($data['_sonata_csrf_token']);
461
        }
462
463
        $batchActions = $this->admin->getBatchActions();
464
        if (!array_key_exists($action, $batchActions)) {
465
            throw new \RuntimeException(sprintf('The `%s` batch action is not defined', $action));
466
        }
467
468
        $camelizedAction = Inflector::classify($action);
469
        $isRelevantAction = sprintf('batchAction%sIsRelevant', ucfirst($camelizedAction));
470
471
        if (method_exists($this, $isRelevantAction)) {
472
            $nonRelevantMessage = call_user_func([$this, $isRelevantAction], $idx, $allElements, $request);
473
        } else {
474
            $nonRelevantMessage = 0 != count($idx) || $allElements; // at least one item is selected
475
        }
476
477
        if (!$nonRelevantMessage) { // default non relevant message (if false of null)
478
            $nonRelevantMessage = 'flash_batch_empty';
479
        }
480
481
        $datagrid = $this->admin->getDatagrid();
482
        $datagrid->buildPager();
483
484
        if (true !== $nonRelevantMessage) {
485
            $this->addFlash(
486
                'sonata_flash_info',
487
                $this->trans($nonRelevantMessage, [], 'SonataAdminBundle')
488
            );
489
490
            return $this->redirectToList();
491
        }
492
493
        $askConfirmation = $batchActions[$action]['ask_confirmation'] ??
494
            true;
495
496
        if ($askConfirmation && 'ok' != $confirmation) {
497
            $actionLabel = $batchActions[$action]['label'];
498
            $batchTranslationDomain = $batchActions[$action]['translation_domain'] ??
499
                $this->admin->getTranslationDomain();
500
501
            $formView = $datagrid->getForm()->createView();
502
            $this->setFormTheme($formView, $this->admin->getFilterTheme());
0 ignored issues
show
Documentation introduced by
$this->admin->getFilterTheme() is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
503
504
            // NEXT_MAJOR: Remove this line and use commented line below it instead
505
            $template = $this->admin->getTemplate('batch_confirmation');
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...nterface::getTemplate() has been deprecated with message: since 3.35. To be removed in 4.0. Use TemplateRegistry services instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
506
            // $template = $this->templateRegistry->getTemplate('batch_confirmation');
507
508
            return $this->renderWithExtraParams($template, [
509
                'action' => 'list',
510
                'action_label' => $actionLabel,
511
                'batch_translation_domain' => $batchTranslationDomain,
512
                'datagrid' => $datagrid,
513
                'form' => $formView,
514
                'data' => $data,
515
                'csrf_token' => $this->getCsrfToken('sonata.batch'),
516
            ], null);
517
        }
518
519
        // execute the action, batchActionXxxxx
520
        $finalAction = sprintf('batchAction%s', $camelizedAction);
521
        if (!method_exists($this, $finalAction)) {
522
            throw new \RuntimeException(sprintf('A `%s::%s` method must be callable', get_class($this), $finalAction));
523
        }
524
525
        $query = $datagrid->getQuery();
526
527
        $query->setFirstResult(null);
528
        $query->setMaxResults(null);
529
530
        $this->admin->preBatchAction($action, $query, $idx, $allElements);
531
532
        if (count($idx) > 0) {
533
            $this->admin->getModelManager()->addIdentifiersToQuery($this->admin->getClass(), $query, $idx);
534
        } elseif (!$allElements) {
535
            $this->addFlash(
536
                'sonata_flash_info',
537
                $this->trans('flash_batch_no_elements_processed', [], 'SonataAdminBundle')
538
            );
539
540
            return $this->redirectToList();
541
        }
542
543
        return call_user_func([$this, $finalAction], $query, $request);
544
    }
545
546
    /**
547
     * Create action.
548
     *
549
     * @throws AccessDeniedException If access is not granted
550
     *
551
     * @return Response
552
     */
553
    public function createAction()
554
    {
555
        $request = $this->getRequest();
556
        // the key used to lookup the template
557
        $templateKey = 'edit';
558
559
        $this->admin->checkAccess('create');
560
561
        $class = new \ReflectionClass($this->admin->hasActiveSubClass() ? $this->admin->getActiveSubClass() : $this->admin->getClass());
562
563
        if ($class->isAbstract()) {
564
            return $this->renderWithExtraParams(
565
                '@SonataAdmin/CRUD/select_subclass.html.twig',
566
                [
567
                    'base_template' => $this->getBaseTemplate(),
568
                    'admin' => $this->admin,
569
                    'action' => 'create',
570
                ],
571
                null
572
            );
573
        }
574
575
        $newObject = $this->admin->getNewInstance();
576
577
        $preResponse = $this->preCreate($request, $newObject);
578
        if (null !== $preResponse) {
579
            return $preResponse;
580
        }
581
582
        $this->admin->setSubject($newObject);
583
584
        /** @var $form \Symfony\Component\Form\Form */
585
        $form = $this->admin->getForm();
586
        $form->setData($newObject);
587
        $form->handleRequest($request);
588
589
        if ($form->isSubmitted()) {
590
            $isFormValid = $form->isValid();
591
592
            // persist if the form was valid and if in preview mode the preview was approved
593
            if ($isFormValid && (!$this->isInPreviewMode() || $this->isPreviewApproved())) {
594
                $submittedObject = $form->getData();
595
                $this->admin->setSubject($submittedObject);
596
                $this->admin->checkAccess('create', $submittedObject);
597
598
                try {
599
                    $newObject = $this->admin->create($submittedObject);
600
601
                    if ($this->isXmlHttpRequest()) {
602
                        return $this->renderJson([
603
                            'result' => 'ok',
604
                            'objectId' => $this->admin->getNormalizedIdentifier($newObject),
605
                        ], 200, []);
606
                    }
607
608
                    $this->addFlash(
609
                        'sonata_flash_success',
610
                        $this->trans(
611
                            'flash_create_success',
612
                            ['%name%' => $this->escapeHtml($this->admin->toString($newObject))],
613
                            'SonataAdminBundle'
614
                        )
615
                    );
616
617
                    // redirect to edit mode
618
                    return $this->redirectTo($newObject);
619
                } catch (ModelManagerException $e) {
620
                    $this->handleModelManagerException($e);
621
622
                    $isFormValid = false;
623
                }
624
            }
625
626
            // show an error message if the form failed validation
627
            if (!$isFormValid) {
628
                if (!$this->isXmlHttpRequest()) {
629
                    $this->addFlash(
630
                        'sonata_flash_error',
631
                        $this->trans(
632
                            'flash_create_error',
633
                            ['%name%' => $this->escapeHtml($this->admin->toString($newObject))],
634
                            'SonataAdminBundle'
635
                        )
636
                    );
637
                }
638
            } elseif ($this->isPreviewRequested()) {
639
                // pick the preview template if the form was valid and preview was requested
640
                $templateKey = 'preview';
641
                $this->admin->getShow();
642
            }
643
        }
644
645
        $formView = $form->createView();
646
        // set the theme for the current Admin Form
647
        $this->setFormTheme($formView, $this->admin->getFormTheme());
0 ignored issues
show
Documentation introduced by
$this->admin->getFormTheme() is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
648
649
        // NEXT_MAJOR: Remove this line and use commented line below it instead
650
        $template = $this->admin->getTemplate($templateKey);
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...nterface::getTemplate() has been deprecated with message: since 3.35. To be removed in 4.0. Use TemplateRegistry services instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
651
        // $template = $this->templateRegistry->getTemplate($templateKey);
652
653
        return $this->renderWithExtraParams($template, [
654
            'action' => 'create',
655
            'form' => $formView,
656
            'object' => $newObject,
657
            'objectId' => null,
658
        ], null);
659
    }
660
661
    /**
662
     * Show action.
663
     *
664
     * @param int|string|null $id
665
     *
666
     * @throws NotFoundHttpException If the object does not exist
667
     * @throws AccessDeniedException If access is not granted
668
     *
669
     * @return Response
670
     */
671
    public function showAction($id = null)
0 ignored issues
show
Unused Code introduced by
The parameter $id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
672
    {
673
        $request = $this->getRequest();
674
        $id = $request->get($this->admin->getIdParameter());
675
676
        $object = $this->admin->getObject($id);
677
678
        if (!$object) {
679
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
680
        }
681
682
        $this->checkParentChildAssociation($request, $object);
683
684
        $this->admin->checkAccess('show', $object);
685
686
        $preResponse = $this->preShow($request, $object);
687
        if (null !== $preResponse) {
688
            return $preResponse;
689
        }
690
691
        $this->admin->setSubject($object);
692
693
        // NEXT_MAJOR: Remove this line and use commented line below it instead
694
        $template = $this->admin->getTemplate('show');
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...nterface::getTemplate() has been deprecated with message: since 3.35. To be removed in 4.0. Use TemplateRegistry services instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
695
        //$template = $this->templateRegistry->getTemplate('show');
696
697
        return $this->renderWithExtraParams($template, [
698
            'action' => 'show',
699
            'object' => $object,
700
            'elements' => $this->admin->getShow(),
701
        ], null);
702
    }
703
704
    /**
705
     * Show history revisions for object.
706
     *
707
     * @param int|string|null $id
708
     *
709
     * @throws AccessDeniedException If access is not granted
710
     * @throws NotFoundHttpException If the object does not exist or the audit reader is not available
711
     *
712
     * @return Response
713
     */
714
    public function historyAction($id = null)
0 ignored issues
show
Unused Code introduced by
The parameter $id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
715
    {
716
        $request = $this->getRequest();
717
        $id = $request->get($this->admin->getIdParameter());
718
719
        $object = $this->admin->getObject($id);
720
721
        if (!$object) {
722
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
723
        }
724
725
        $this->admin->checkAccess('history', $object);
726
727
        $manager = $this->get('sonata.admin.audit.manager');
728
729
        if (!$manager->hasReader($this->admin->getClass())) {
730
            throw $this->createNotFoundException(
731
                sprintf(
732
                    'unable to find the audit reader for class : %s',
733
                    $this->admin->getClass()
734
                )
735
            );
736
        }
737
738
        $reader = $manager->getReader($this->admin->getClass());
739
740
        $revisions = $reader->findRevisions($this->admin->getClass(), $id);
741
742
        // NEXT_MAJOR: Remove this line and use commented line below it instead
743
        $template = $this->admin->getTemplate('history');
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...nterface::getTemplate() has been deprecated with message: since 3.35. To be removed in 4.0. Use TemplateRegistry services instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
744
        // $template = $this->templateRegistry->getTemplate('history');
745
746
        return $this->renderWithExtraParams($template, [
747
            'action' => 'history',
748
            'object' => $object,
749
            'revisions' => $revisions,
750
            'currentRevision' => $revisions ? current($revisions) : false,
751
        ], null);
752
    }
753
754
    /**
755
     * View history revision of object.
756
     *
757
     * @param int|string|null $id
758
     * @param string|null     $revision
759
     *
760
     * @throws AccessDeniedException If access is not granted
761
     * @throws NotFoundHttpException If the object or revision does not exist or the audit reader is not available
762
     *
763
     * @return Response
764
     */
765
    public function historyViewRevisionAction($id = null, $revision = null)
0 ignored issues
show
Unused Code introduced by
The parameter $id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
766
    {
767
        $request = $this->getRequest();
768
        $id = $request->get($this->admin->getIdParameter());
769
770
        $object = $this->admin->getObject($id);
771
772
        if (!$object) {
773
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
774
        }
775
776
        $this->admin->checkAccess('historyViewRevision', $object);
777
778
        $manager = $this->get('sonata.admin.audit.manager');
779
780
        if (!$manager->hasReader($this->admin->getClass())) {
781
            throw $this->createNotFoundException(
782
                sprintf(
783
                    'unable to find the audit reader for class : %s',
784
                    $this->admin->getClass()
785
                )
786
            );
787
        }
788
789
        $reader = $manager->getReader($this->admin->getClass());
790
791
        // retrieve the revisioned object
792
        $object = $reader->find($this->admin->getClass(), $id, $revision);
793
794
        if (!$object) {
795
            throw $this->createNotFoundException(
796
                sprintf(
797
                    'unable to find the targeted object `%s` from the revision `%s` with classname : `%s`',
798
                    $id,
799
                    $revision,
800
                    $this->admin->getClass()
801
                )
802
            );
803
        }
804
805
        $this->admin->setSubject($object);
806
807
        // NEXT_MAJOR: Remove this line and use commented line below it instead
808
        $template = $this->admin->getTemplate('show');
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...nterface::getTemplate() has been deprecated with message: since 3.35. To be removed in 4.0. Use TemplateRegistry services instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
809
        // $template = $this->templateRegistry->getTemplate('show');
810
811
        return $this->renderWithExtraParams($template, [
812
            'action' => 'show',
813
            'object' => $object,
814
            'elements' => $this->admin->getShow(),
815
        ], null);
816
    }
817
818
    /**
819
     * Compare history revisions of object.
820
     *
821
     * @param int|string|null $id
822
     * @param int|string|null $base_revision
823
     * @param int|string|null $compare_revision
824
     *
825
     * @throws AccessDeniedException If access is not granted
826
     * @throws NotFoundHttpException If the object or revision does not exist or the audit reader is not available
827
     *
828
     * @return Response
829
     */
830
    public function historyCompareRevisionsAction($id = null, $base_revision = null, $compare_revision = null)
0 ignored issues
show
Unused Code introduced by
The parameter $id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
831
    {
832
        $request = $this->getRequest();
833
834
        $this->admin->checkAccess('historyCompareRevisions');
835
836
        $id = $request->get($this->admin->getIdParameter());
837
838
        $object = $this->admin->getObject($id);
839
840
        if (!$object) {
841
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
842
        }
843
844
        $manager = $this->get('sonata.admin.audit.manager');
845
846
        if (!$manager->hasReader($this->admin->getClass())) {
847
            throw $this->createNotFoundException(
848
                sprintf(
849
                    'unable to find the audit reader for class : %s',
850
                    $this->admin->getClass()
851
                )
852
            );
853
        }
854
855
        $reader = $manager->getReader($this->admin->getClass());
856
857
        // retrieve the base revision
858
        $base_object = $reader->find($this->admin->getClass(), $id, $base_revision);
859
        if (!$base_object) {
860
            throw $this->createNotFoundException(
861
                sprintf(
862
                    'unable to find the targeted object `%s` from the revision `%s` with classname : `%s`',
863
                    $id,
864
                    $base_revision,
865
                    $this->admin->getClass()
866
                )
867
            );
868
        }
869
870
        // retrieve the compare revision
871
        $compare_object = $reader->find($this->admin->getClass(), $id, $compare_revision);
872
        if (!$compare_object) {
873
            throw $this->createNotFoundException(
874
                sprintf(
875
                    'unable to find the targeted object `%s` from the revision `%s` with classname : `%s`',
876
                    $id,
877
                    $compare_revision,
878
                    $this->admin->getClass()
879
                )
880
            );
881
        }
882
883
        $this->admin->setSubject($base_object);
884
885
        // NEXT_MAJOR: Remove this line and use commented line below it instead
886
        $template = $this->admin->getTemplate('show_compare');
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...nterface::getTemplate() has been deprecated with message: since 3.35. To be removed in 4.0. Use TemplateRegistry services instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
887
        // $template = $this->templateRegistry->getTemplate('show_compare');
888
889
        return $this->renderWithExtraParams($template, [
890
            'action' => 'show',
891
            'object' => $base_object,
892
            'object_compare' => $compare_object,
893
            'elements' => $this->admin->getShow(),
894
        ], null);
895
    }
896
897
    /**
898
     * Export data to specified format.
899
     *
900
     * @throws AccessDeniedException If access is not granted
901
     * @throws \RuntimeException     If the export format is invalid
902
     *
903
     * @return Response
904
     */
905
    public function exportAction(Request $request)
906
    {
907
        $this->admin->checkAccess('export');
908
909
        $format = $request->get('format');
910
911
        // NEXT_MAJOR: remove the check
912
        if (!$this->has('sonata.admin.admin_exporter')) {
913
            @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
914
                'Not registering the exporter bundle is deprecated since version 3.14.'
915
                .' You must register it to be able to use the export action in 4.0.',
916
                E_USER_DEPRECATED
917
            );
918
            $allowedExportFormats = (array) $this->admin->getExportFormats();
919
920
            $class = $this->admin->getClass();
921
            $filename = sprintf(
922
                'export_%s_%s.%s',
923
                strtolower(substr($class, strripos($class, '\\') + 1)),
924
                date('Y_m_d_H_i_s', strtotime('now')),
925
                $format
926
            );
927
            $exporter = $this->get('sonata.admin.exporter');
928
        } else {
929
            $adminExporter = $this->get('sonata.admin.admin_exporter');
930
            $allowedExportFormats = $adminExporter->getAvailableFormats($this->admin);
931
            $filename = $adminExporter->getExportFilename($this->admin, $format);
932
            $exporter = $this->get('sonata.exporter.exporter');
933
        }
934
935
        if (!in_array($format, $allowedExportFormats)) {
936
            throw new \RuntimeException(
937
                sprintf(
938
                    'Export in format `%s` is not allowed for class: `%s`. Allowed formats are: `%s`',
939
                    $format,
940
                    $this->admin->getClass(),
941
                    implode(', ', $allowedExportFormats)
942
                )
943
            );
944
        }
945
946
        return $exporter->getResponse(
947
            $format,
948
            $filename,
949
            $this->admin->getDataSourceIterator()
950
        );
951
    }
952
953
    /**
954
     * Returns the Response object associated to the acl action.
955
     *
956
     * @param int|string|null $id
957
     *
958
     * @throws AccessDeniedException If access is not granted
959
     * @throws NotFoundHttpException If the object does not exist or the ACL is not enabled
960
     *
961
     * @return Response|RedirectResponse
962
     */
963
    public function aclAction($id = null)
0 ignored issues
show
Unused Code introduced by
The parameter $id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
964
    {
965
        $request = $this->getRequest();
966
967
        if (!$this->admin->isAclEnabled()) {
968
            throw $this->createNotFoundException('ACL are not enabled for this admin');
969
        }
970
971
        $id = $request->get($this->admin->getIdParameter());
972
973
        $object = $this->admin->getObject($id);
974
975
        if (!$object) {
976
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
977
        }
978
979
        $this->admin->checkAccess('acl', $object);
980
981
        $this->admin->setSubject($object);
982
        $aclUsers = $this->getAclUsers();
983
        $aclRoles = $this->getAclRoles();
984
985
        $adminObjectAclManipulator = $this->get('sonata.admin.object.manipulator.acl.admin');
986
        $adminObjectAclData = new AdminObjectAclData(
987
            $this->admin,
988
            $object,
989
            $aclUsers,
990
            $adminObjectAclManipulator->getMaskBuilderClass(),
991
            $aclRoles
992
        );
993
994
        $aclUsersForm = $adminObjectAclManipulator->createAclUsersForm($adminObjectAclData);
995
        $aclRolesForm = $adminObjectAclManipulator->createAclRolesForm($adminObjectAclData);
996
997
        if ('POST' === $request->getMethod()) {
998
            if ($request->request->has(AdminObjectAclManipulator::ACL_USERS_FORM_NAME)) {
999
                $form = $aclUsersForm;
1000
                $updateMethod = 'updateAclUsers';
1001
            } elseif ($request->request->has(AdminObjectAclManipulator::ACL_ROLES_FORM_NAME)) {
1002
                $form = $aclRolesForm;
1003
                $updateMethod = 'updateAclRoles';
1004
            }
1005
1006
            if (isset($form)) {
1007
                $form->handleRequest($request);
1008
1009
                if ($form->isValid()) {
1010
                    $adminObjectAclManipulator->$updateMethod($adminObjectAclData);
0 ignored issues
show
Bug introduced by
The variable $updateMethod does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1011
                    $this->addFlash(
1012
                        'sonata_flash_success',
1013
                        $this->trans('flash_acl_edit_success', [], 'SonataAdminBundle')
1014
                    );
1015
1016
                    return new RedirectResponse($this->admin->generateObjectUrl('acl', $object));
1017
                }
1018
            }
1019
        }
1020
1021
        // NEXT_MAJOR: Remove this line and use commented line below it instead
1022
        $template = $this->admin->getTemplate('acl');
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...nterface::getTemplate() has been deprecated with message: since 3.35. To be removed in 4.0. Use TemplateRegistry services instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1023
        // $template = $this->templateRegistry->getTemplate('acl');
1024
1025
        return $this->renderWithExtraParams($template, [
1026
            'action' => 'acl',
1027
            'permissions' => $adminObjectAclData->getUserPermissions(),
1028
            'object' => $object,
1029
            'users' => $aclUsers,
1030
            'roles' => $aclRoles,
1031
            'aclUsersForm' => $aclUsersForm->createView(),
1032
            'aclRolesForm' => $aclRolesForm->createView(),
1033
        ], null);
1034
    }
1035
1036
    /**
1037
     * @return Request
1038
     */
1039
    public function getRequest()
1040
    {
1041
        return $this->container->get('request_stack')->getCurrentRequest();
1042
    }
1043
1044
    /**
1045
     * Gets a container configuration parameter by its name.
1046
     *
1047
     * @param string $name The parameter name
1048
     *
1049
     * @return mixed
1050
     */
1051
    protected function getParameter($name)
1052
    {
1053
        return $this->container->getParameter($name);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Psr\Container\ContainerInterface as the method getParameter() does only exist in the following implementations of said interface: Container14\ProjectServiceContainer, ProjectServiceContainer, Symfony\Component\Depend...urationContainerBuilder, Symfony\Component\DependencyInjection\Container, Symfony\Component\Depend...ection\ContainerBuilder, Symfony\Component\Depend...\NoConstructorContainer, Symfony\Component\Depend...tainers\CustomContainer, Symfony\Component\Depend...ProjectServiceContainer, Symfony\Component\Depend...ProjectServiceContainer, Symfony_DI_PhpDumper_Test_Almost_Circular_Private, Symfony_DI_PhpDumper_Test_Almost_Circular_Public, Symfony_DI_PhpDumper_Test_Base64Parameters, Symfony_DI_PhpDumper_Test_EnvParameters, Symfony_DI_PhpDumper_Test_Legacy_Privates, Symfony_DI_PhpDumper_Test_Rot13Parameters, Symfony_DI_PhpDumper_Test_Uninitialized_Reference.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1054
    }
1055
1056
    /**
1057
     * Render JSON.
1058
     *
1059
     * @param mixed $data
1060
     * @param int   $status
1061
     * @param array $headers
1062
     *
1063
     * @return Response with json encoded data
1064
     */
1065
    protected function renderJson($data, $status = 200, $headers = [])
1066
    {
1067
        return new JsonResponse($data, $status, $headers);
1068
    }
1069
1070
    /**
1071
     * Returns true if the request is a XMLHttpRequest.
1072
     *
1073
     * @return bool True if the request is an XMLHttpRequest, false otherwise
1074
     */
1075
    protected function isXmlHttpRequest()
1076
    {
1077
        $request = $this->getRequest();
1078
1079
        return $request->isXmlHttpRequest() || $request->get('_xml_http_request');
1080
    }
1081
1082
    /**
1083
     * Returns the correct RESTful verb, given either by the request itself or
1084
     * via the "_method" parameter.
1085
     *
1086
     * @return string HTTP method, either
1087
     */
1088
    protected function getRestMethod()
1089
    {
1090
        $request = $this->getRequest();
1091
1092
        if (Request::getHttpMethodParameterOverride() || !$request->request->has('_method')) {
1093
            return $request->getMethod();
1094
        }
1095
1096
        return $request->request->get('_method');
1097
    }
1098
1099
    /**
1100
     * Contextualize the admin class depends on the current request.
1101
     *
1102
     * @throws \RuntimeException
1103
     */
1104
    protected function configure(): void
1105
    {
1106
        $request = $this->getRequest();
1107
1108
        $adminCode = $request->get('_sonata_admin');
1109
1110
        if (!$adminCode) {
1111
            throw new \RuntimeException(sprintf(
1112
                'There is no `_sonata_admin` defined for the controller `%s` and the current route `%s`',
1113
                get_class($this),
1114
                $request->get('_route')
1115
            ));
1116
        }
1117
1118
        $this->admin = $this->container->get('sonata.admin.pool')->getAdminByAdminCode($adminCode);
1119
1120
        if (!$this->admin) {
1121
            throw new \RuntimeException(sprintf(
1122
                'Unable to find the admin class related to the current controller (%s)',
1123
                get_class($this)
1124
            ));
1125
        }
1126
1127
        $this->templateRegistry = $this->container->get($this->admin->getCode().'.template_registry');
1128
        if (!$this->templateRegistry instanceof TemplateRegistryInterface) {
1129
            throw new \RuntimeException(sprintf(
1130
                'Unable to find the template registry related to the current admin (%s)',
1131
                $this->admin->getCode()
1132
            ));
1133
        }
1134
1135
        $rootAdmin = $this->admin;
1136
1137
        while ($rootAdmin->isChild()) {
1138
            $rootAdmin->setCurrentChild(true);
1139
            $rootAdmin = $rootAdmin->getParent();
1140
        }
1141
1142
        $rootAdmin->setRequest($request);
1143
1144
        if ($request->get('uniqid')) {
1145
            $this->admin->setUniqid($request->get('uniqid'));
1146
        }
1147
    }
1148
1149
    /**
1150
     * Proxy for the logger service of the container.
1151
     * If no such service is found, a NullLogger is returned.
1152
     *
1153
     * @return LoggerInterface
1154
     */
1155
    protected function getLogger()
1156
    {
1157
        if ($this->container->has('logger')) {
1158
            return $this->container->get('logger');
1159
        }
1160
1161
        return new NullLogger();
1162
    }
1163
1164
    /**
1165
     * Returns the base template name.
1166
     *
1167
     * @return string The template name
1168
     */
1169
    protected function getBaseTemplate()
1170
    {
1171
        if ($this->isXmlHttpRequest()) {
1172
            // NEXT_MAJOR: Remove this line and use commented line below it instead
1173
            return $this->admin->getTemplate('ajax');
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...nterface::getTemplate() has been deprecated with message: since 3.35. To be removed in 4.0. Use TemplateRegistry services instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1174
            // return $this->templateRegistry->getTemplate('ajax');
1175
        }
1176
1177
        // NEXT_MAJOR: Remove this line and use commented line below it instead
1178
        return $this->admin->getTemplate('layout');
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...nterface::getTemplate() has been deprecated with message: since 3.35. To be removed in 4.0. Use TemplateRegistry services instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1179
        // return $this->templateRegistry->getTemplate('layout');
1180
    }
1181
1182
    /**
1183
     * @throws \Exception
1184
     */
1185
    protected function handleModelManagerException(\Exception $e): void
1186
    {
1187
        if ($this->get('kernel')->isDebug()) {
1188
            throw $e;
1189
        }
1190
1191
        $context = ['exception' => $e];
1192
        if ($e->getPrevious()) {
1193
            $context['previous_exception_message'] = $e->getPrevious()->getMessage();
1194
        }
1195
        $this->getLogger()->error($e->getMessage(), $context);
1196
    }
1197
1198
    /**
1199
     * Redirect the user depend on this choice.
1200
     *
1201
     * @param object $object
1202
     *
1203
     * @return RedirectResponse
1204
     */
1205
    protected function redirectTo($object)
1206
    {
1207
        $request = $this->getRequest();
1208
1209
        $url = false;
1210
1211
        if (null !== $request->get('btn_update_and_list')) {
1212
            return $this->redirectToList();
1213
        }
1214
        if (null !== $request->get('btn_create_and_list')) {
1215
            return $this->redirectToList();
1216
        }
1217
1218
        if (null !== $request->get('btn_create_and_create')) {
1219
            $params = [];
1220
            if ($this->admin->hasActiveSubClass()) {
1221
                $params['subclass'] = $request->get('subclass');
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $params['subclass'] is correct as $request->get('subclass') (which targets Symfony\Component\HttpFoundation\Request::get()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
1222
            }
1223
            $url = $this->admin->generateUrl('create', $params);
1224
        }
1225
1226
        if ('DELETE' === $this->getRestMethod()) {
1227
            return $this->redirectToList();
1228
        }
1229
1230
        if (!$url) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $url of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1231
            foreach (['edit', 'show'] as $route) {
1232
                if ($this->admin->hasRoute($route) && $this->admin->hasAccess($route, $object)) {
1233
                    $url = $this->admin->generateObjectUrl($route, $object);
1234
1235
                    break;
1236
                }
1237
            }
1238
        }
1239
1240
        if (!$url) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $url of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1241
            return $this->redirectToList();
1242
        }
1243
1244
        return new RedirectResponse($url);
1245
    }
1246
1247
    /**
1248
     * Redirects the user to the list view.
1249
     *
1250
     * @return RedirectResponse
1251
     */
1252
    final protected function redirectToList()
1253
    {
1254
        $parameters = [];
1255
1256
        if ($filter = $this->admin->getFilterParameters()) {
1257
            $parameters['filter'] = $filter;
1258
        }
1259
1260
        return $this->redirect($this->admin->generateUrl('list', $parameters));
1261
    }
1262
1263
    /**
1264
     * Returns true if the preview is requested to be shown.
1265
     *
1266
     * @return bool
1267
     */
1268
    protected function isPreviewRequested()
1269
    {
1270
        $request = $this->getRequest();
1271
1272
        return null !== $request->get('btn_preview');
1273
    }
1274
1275
    /**
1276
     * Returns true if the preview has been approved.
1277
     *
1278
     * @return bool
1279
     */
1280
    protected function isPreviewApproved()
1281
    {
1282
        $request = $this->getRequest();
1283
1284
        return null !== $request->get('btn_preview_approve');
1285
    }
1286
1287
    /**
1288
     * Returns true if the request is in the preview workflow.
1289
     *
1290
     * That means either a preview is requested or the preview has already been shown
1291
     * and it got approved/declined.
1292
     *
1293
     * @return bool
1294
     */
1295
    protected function isInPreviewMode()
1296
    {
1297
        return $this->admin->supportsPreviewMode()
1298
        && ($this->isPreviewRequested()
1299
            || $this->isPreviewApproved()
1300
            || $this->isPreviewDeclined());
1301
    }
1302
1303
    /**
1304
     * Returns true if the preview has been declined.
1305
     *
1306
     * @return bool
1307
     */
1308
    protected function isPreviewDeclined()
1309
    {
1310
        $request = $this->getRequest();
1311
1312
        return null !== $request->get('btn_preview_decline');
1313
    }
1314
1315
    /**
1316
     * Gets ACL users.
1317
     *
1318
     * @return \Traversable
1319
     */
1320
    protected function getAclUsers()
1321
    {
1322
        $aclUsers = [];
1323
1324
        $userManagerServiceName = $this->container->getParameter('sonata.admin.security.acl_user_manager');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Psr\Container\ContainerInterface as the method getParameter() does only exist in the following implementations of said interface: Container14\ProjectServiceContainer, ProjectServiceContainer, Symfony\Component\Depend...urationContainerBuilder, Symfony\Component\DependencyInjection\Container, Symfony\Component\Depend...ection\ContainerBuilder, Symfony\Component\Depend...\NoConstructorContainer, Symfony\Component\Depend...tainers\CustomContainer, Symfony\Component\Depend...ProjectServiceContainer, Symfony\Component\Depend...ProjectServiceContainer, Symfony_DI_PhpDumper_Test_Almost_Circular_Private, Symfony_DI_PhpDumper_Test_Almost_Circular_Public, Symfony_DI_PhpDumper_Test_Base64Parameters, Symfony_DI_PhpDumper_Test_EnvParameters, Symfony_DI_PhpDumper_Test_Legacy_Privates, Symfony_DI_PhpDumper_Test_Rot13Parameters, Symfony_DI_PhpDumper_Test_Uninitialized_Reference.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1325
        if (null !== $userManagerServiceName && $this->has($userManagerServiceName)) {
1326
            $userManager = $this->get($userManagerServiceName);
1327
1328
            if (method_exists($userManager, 'findUsers')) {
1329
                $aclUsers = $userManager->findUsers();
1330
            }
1331
        }
1332
1333
        return is_array($aclUsers) ? new \ArrayIterator($aclUsers) : $aclUsers;
1334
    }
1335
1336
    /**
1337
     * Gets ACL roles.
1338
     *
1339
     * @return \Traversable
1340
     */
1341
    protected function getAclRoles()
1342
    {
1343
        $aclRoles = [];
1344
        $roleHierarchy = $this->container->getParameter('security.role_hierarchy.roles');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Psr\Container\ContainerInterface as the method getParameter() does only exist in the following implementations of said interface: Container14\ProjectServiceContainer, ProjectServiceContainer, Symfony\Component\Depend...urationContainerBuilder, Symfony\Component\DependencyInjection\Container, Symfony\Component\Depend...ection\ContainerBuilder, Symfony\Component\Depend...\NoConstructorContainer, Symfony\Component\Depend...tainers\CustomContainer, Symfony\Component\Depend...ProjectServiceContainer, Symfony\Component\Depend...ProjectServiceContainer, Symfony_DI_PhpDumper_Test_Almost_Circular_Private, Symfony_DI_PhpDumper_Test_Almost_Circular_Public, Symfony_DI_PhpDumper_Test_Base64Parameters, Symfony_DI_PhpDumper_Test_EnvParameters, Symfony_DI_PhpDumper_Test_Legacy_Privates, Symfony_DI_PhpDumper_Test_Rot13Parameters, Symfony_DI_PhpDumper_Test_Uninitialized_Reference.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1345
        $pool = $this->container->get('sonata.admin.pool');
1346
1347
        foreach ($pool->getAdminServiceIds() as $id) {
1348
            try {
1349
                $admin = $pool->getInstance($id);
1350
            } catch (\Exception $e) {
1351
                continue;
1352
            }
1353
1354
            $baseRole = $admin->getSecurityHandler()->getBaseRole($admin);
1355
            foreach ($admin->getSecurityInformation() as $role => $permissions) {
1356
                $role = sprintf($baseRole, $role);
1357
                $aclRoles[] = $role;
1358
            }
1359
        }
1360
1361
        foreach ($roleHierarchy as $name => $roles) {
1362
            $aclRoles[] = $name;
1363
            $aclRoles = array_merge($aclRoles, $roles);
1364
        }
1365
1366
        $aclRoles = array_unique($aclRoles);
1367
1368
        return is_array($aclRoles) ? new \ArrayIterator($aclRoles) : $aclRoles;
1369
    }
1370
1371
    /**
1372
     * Validate CSRF token for action without form.
1373
     *
1374
     * @param string $intention
1375
     *
1376
     * @throws HttpException
1377
     */
1378
    protected function validateCsrfToken($intention): void
1379
    {
1380
        $request = $this->getRequest();
1381
        $token = $request->request->get('_sonata_csrf_token', false);
1382
1383
        if ($this->container->has('security.csrf.token_manager')) {
1384
            $valid = $this->container->get('security.csrf.token_manager')->isTokenValid(new CsrfToken($intention, $token));
1385
        } else {
1386
            return;
1387
        }
1388
1389
        if (!$valid) {
1390
            throw new HttpException(400, 'The csrf token is not valid, CSRF attack?');
1391
        }
1392
    }
1393
1394
    /**
1395
     * Escape string for html output.
1396
     *
1397
     * @param string $s
1398
     *
1399
     * @return string
1400
     */
1401
    protected function escapeHtml($s)
1402
    {
1403
        return htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
1404
    }
1405
1406
    /**
1407
     * Get CSRF token.
1408
     *
1409
     * @param string $intention
1410
     *
1411
     * @return string|false
1412
     */
1413
    protected function getCsrfToken($intention)
1414
    {
1415
        if ($this->container->has('security.csrf.token_manager')) {
1416
            return $this->container->get('security.csrf.token_manager')->getToken($intention)->getValue();
1417
        }
1418
1419
        return false;
1420
    }
1421
1422
    /**
1423
     * This method can be overloaded in your custom CRUD controller.
1424
     * It's called from createAction.
1425
     *
1426
     * @param mixed $object
1427
     *
1428
     * @return Response|null
1429
     */
1430
    protected function preCreate(Request $request, $object)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1431
    {
1432
    }
1433
1434
    /**
1435
     * This method can be overloaded in your custom CRUD controller.
1436
     * It's called from editAction.
1437
     *
1438
     * @param mixed $object
1439
     *
1440
     * @return Response|null
1441
     */
1442
    protected function preEdit(Request $request, $object)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1443
    {
1444
    }
1445
1446
    /**
1447
     * This method can be overloaded in your custom CRUD controller.
1448
     * It's called from deleteAction.
1449
     *
1450
     * @param mixed $object
1451
     *
1452
     * @return Response|null
1453
     */
1454
    protected function preDelete(Request $request, $object)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1455
    {
1456
    }
1457
1458
    /**
1459
     * This method can be overloaded in your custom CRUD controller.
1460
     * It's called from showAction.
1461
     *
1462
     * @param mixed $object
1463
     *
1464
     * @return Response|null
1465
     */
1466
    protected function preShow(Request $request, $object)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1467
    {
1468
    }
1469
1470
    /**
1471
     * This method can be overloaded in your custom CRUD controller.
1472
     * It's called from listAction.
1473
     *
1474
     * @return Response|null
1475
     */
1476
    protected function preList(Request $request)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1477
    {
1478
    }
1479
1480
    /**
1481
     * Translate a message id.
1482
     *
1483
     * @param string $id
1484
     * @param string $domain
1485
     * @param string $locale
1486
     *
1487
     * @return string translated string
1488
     */
1489
    final protected function trans($id, array $parameters = [], $domain = null, $locale = null)
1490
    {
1491
        $domain = $domain ?: $this->admin->getTranslationDomain();
1492
1493
        return $this->get('translator')->trans($id, $parameters, $domain, $locale);
1494
    }
1495
1496
    private function checkParentChildAssociation(Request $request, $object): void
1497
    {
1498
        if (!($parentAdmin = $this->admin->getParent())) {
1499
            return;
1500
        }
1501
1502
        // NEXT_MAJOR: remove this check
1503
        if (!$this->admin->getParentAssociationMapping()) {
0 ignored issues
show
Bug introduced by
The method getParentAssociationMapping() does not exist on Sonata\AdminBundle\Admin\AdminInterface. Did you maybe mean getParent()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
1504
            return;
1505
        }
1506
1507
        $parentId = $request->get($parentAdmin->getIdParameter());
1508
1509
        $propertyAccessor = PropertyAccess::createPropertyAccessor();
1510
        $propertyPath = new PropertyPath($this->admin->getParentAssociationMapping());
0 ignored issues
show
Bug introduced by
The method getParentAssociationMapping() does not exist on Sonata\AdminBundle\Admin\AdminInterface. Did you maybe mean getParent()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
1511
1512
        if ($parentAdmin->getObject($parentId) !== $propertyAccessor->getValue($object, $propertyPath)) {
1513
            // NEXT_MAJOR: make this exception
1514
            @trigger_error("Accessing a child that isn't connected to a given parent is deprecated since 3.34"
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1515
                ." and won't be allowed in 4.0.",
1516
                E_USER_DEPRECATED
1517
            );
1518
        }
1519
    }
1520
1521
    /**
1522
     * Sets the admin form theme to form view. Used for compatibility between Symfony versions.
1523
     *
1524
     * @param string $theme
1525
     */
1526
    private function setFormTheme(FormView $formView, $theme): void
1527
    {
1528
        $twig = $this->get('twig');
1529
1530
        // BC for Symfony < 3.2 where this runtime does not exists
1531
        if (!method_exists(AppVariable::class, 'getToken')) {
1532
            $twig->getExtension(FormExtension::class)->renderer->setTheme($formView, $theme);
1533
1534
            return;
1535
        }
1536
1537
        // BC for Symfony < 3.4 where runtime should be TwigRenderer
1538
        if (!method_exists(DebugCommand::class, 'getLoaderPaths')) {
1539
            $twig->getRuntime(TwigRenderer::class)->setTheme($formView, $theme);
1540
1541
            return;
1542
        }
1543
1544
        $twig->getRuntime(FormRenderer::class)->setTheme($formView, $theme);
1545
    }
1546
}
1547