Completed
Push — master ( fc5ec4...4cc816 )
by Grégoire
23s queued 17s
created

CRUDController::deleteAction()   C

Complexity

Conditions 7
Paths 13

Size

Total Lines 70
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 70
rs 6.8519
c 0
b 0
f 0
cc 7
eloc 41
nc 13
nop 1

How to fix   Long Method   

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 56 and the first side effect is on line 50.

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
47
// BC for Symfony < 3.3 where this trait does not exist
48
// NEXT_MAJOR: Remove the polyfill and inherit from \Symfony\Bundle\FrameworkBundle\Controller\Controller again
49
if (!trait_exists(ControllerTrait::class)) {
50
    require_once __DIR__.'/PolyfillControllerTrait.php';
51
}
52
53
/**
54
 * @author Thomas Rabaix <[email protected]>
55
 */
56
class CRUDController implements ContainerAwareInterface
57
{
58
    // NEXT_MAJOR: Don't use these traits anymore (inherit from Controller instead)
59
    use ControllerTrait, ContainerAwareTrait {
60
        ControllerTrait::render as originalRender;
61
    }
62
63
    /**
64
     * The related Admin class.
65
     *
66
     * @var AdminInterface
67
     */
68
    protected $admin;
69
70
    /**
71
     * The template registry of the related Admin class.
72
     *
73
     * @var TemplateRegistryInterface
74
     */
75
    private $templateRegistry;
76
77
    // BC for Symfony 3.3 where ControllerTrait exists but does not contain get() and has() methods.
78
    public function __call($method, $arguments)
79
    {
80
        if (in_array($method, ['get', 'has'])) {
81
            return call_user_func_array([$this->container, $method], $arguments);
82
        }
83
84
        if (method_exists($this, 'proxyToControllerClass')) {
85
            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...
86
        }
87
88
        throw new \LogicException('Call to undefined method '.__CLASS__.'::'.$method);
89
    }
90
91
    public function setContainer(ContainerInterface $container = null): void
92
    {
93
        $this->container = $container;
94
95
        $this->configure();
96
    }
97
98
    /**
99
     * NEXT_MAJOR: Remove this method.
100
     *
101
     * @see renderWithExtraParams()
102
     *
103
     * @param string $view       The view name
104
     * @param array  $parameters An array of parameters to pass to the view
105
     *
106
     * @return Response A Response instance
107
     *
108
     * @deprecated since version 3.27, to be removed in 4.0. Use Sonata\AdminBundle\Controller\CRUDController::renderWithExtraParams() instead.
109
     */
110
    public function render($view, array $parameters = [], Response $response = null)
111
    {
112
        @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...
113
            'Method '.__CLASS__.'::render has been renamed to '.__CLASS__.'::renderWithExtraParams.',
114
            E_USER_DEPRECATED
115
        );
116
117
        return $this->renderWithExtraParams($view, $parameters, $response);
118
    }
119
120
    /**
121
     * Renders a view while passing mandatory parameters on to the template.
122
     *
123
     * @param string $view The view name
124
     *
125
     * @return Response A Response instance
126
     */
127
    public function renderWithExtraParams($view, array $parameters = [], Response $response = null)
128
    {
129
        if (!$this->isXmlHttpRequest()) {
130
            $parameters['breadcrumbs_builder'] = $this->get('sonata.admin.breadcrumbs_builder');
131
        }
132
        $parameters['admin'] = $parameters['admin'] ??
133
            $this->admin;
134
135
        $parameters['base_template'] = $parameters['base_template'] ??
136
            $this->getBaseTemplate();
137
138
        $parameters['admin_pool'] = $this->get('sonata.admin.pool');
139
140
        //NEXT_MAJOR: Remove method alias and use $this->render() directly.
141
        return $this->originalRender($view, $parameters, $response);
142
    }
143
144
    /**
145
     * List action.
146
     *
147
     * @throws AccessDeniedException If access is not granted
148
     *
149
     * @return Response
150
     */
151
    public function listAction()
152
    {
153
        $request = $this->getRequest();
154
155
        $this->admin->checkAccess('list');
156
157
        $preResponse = $this->preList($request);
158
        if (null !== $preResponse) {
159
            return $preResponse;
160
        }
161
162
        if ($listMode = $request->get('_list_mode')) {
163
            $this->admin->setListMode($listMode);
164
        }
165
166
        $datagrid = $this->admin->getDatagrid();
167
        $formView = $datagrid->getForm()->createView();
168
169
        // set the theme for the current Admin Form
170
        $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...
171
172
        // NEXT_MAJOR: Remove this line and use commented line below it instead
173
        $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.x. 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...
174
        // $template = $this->templateRegistry->getTemplate('list');
175
176
        return $this->renderWithExtraParams($template, [
177
            'action' => 'list',
178
            'form' => $formView,
179
            'datagrid' => $datagrid,
180
            'csrf_token' => $this->getCsrfToken('sonata.batch'),
181
            'export_formats' => $this->has('sonata.admin.admin_exporter') ?
182
                $this->get('sonata.admin.admin_exporter')->getAvailableFormats($this->admin) :
183
                $this->admin->getExportFormats(),
184
        ], null);
185
    }
186
187
    /**
188
     * Execute a batch delete.
189
     *
190
     * @throws AccessDeniedException If access is not granted
191
     *
192
     * @return RedirectResponse
193
     */
194
    public function batchActionDelete(ProxyQueryInterface $query)
195
    {
196
        $this->admin->checkAccess('batchDelete');
197
198
        $modelManager = $this->admin->getModelManager();
199
200
        try {
201
            $modelManager->batchDelete($this->admin->getClass(), $query);
202
            $this->addFlash(
203
                'sonata_flash_success',
204
                $this->trans('flash_batch_delete_success', [], 'SonataAdminBundle')
205
            );
206
        } catch (ModelManagerException $e) {
207
            $this->handleModelManagerException($e);
208
            $this->addFlash(
209
                'sonata_flash_error',
210
                $this->trans('flash_batch_delete_error', [], 'SonataAdminBundle')
211
            );
212
        }
213
214
        return $this->redirectToList();
215
    }
216
217
    /**
218
     * Delete action.
219
     *
220
     * @param int|string|null $id
221
     *
222
     * @throws NotFoundHttpException If the object does not exist
223
     * @throws AccessDeniedException If access is not granted
224
     *
225
     * @return Response|RedirectResponse
226
     */
227
    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...
228
    {
229
        $request = $this->getRequest();
230
        $id = $request->get($this->admin->getIdParameter());
231
        $object = $this->admin->getObject($id);
232
233
        if (!$object) {
234
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
235
        }
236
237
        $this->checkParentChildAssociation($request, $object);
238
239
        $this->admin->checkAccess('delete', $object);
240
241
        $preResponse = $this->preDelete($request, $object);
242
        if (null !== $preResponse) {
243
            return $preResponse;
244
        }
245
246
        if ('DELETE' == $this->getRestMethod()) {
247
            // check the csrf token
248
            $this->validateCsrfToken('sonata.delete');
249
250
            $objectName = $this->admin->toString($object);
251
252
            try {
253
                $this->admin->delete($object);
254
255
                if ($this->isXmlHttpRequest()) {
256
                    return $this->renderJson(['result' => 'ok'], 200, []);
257
                }
258
259
                $this->addFlash(
260
                    'sonata_flash_success',
261
                    $this->trans(
262
                        'flash_delete_success',
263
                        ['%name%' => $this->escapeHtml($objectName)],
264
                        'SonataAdminBundle'
265
                    )
266
                );
267
            } catch (ModelManagerException $e) {
268
                $this->handleModelManagerException($e);
269
270
                if ($this->isXmlHttpRequest()) {
271
                    return $this->renderJson(['result' => 'error'], 200, []);
272
                }
273
274
                $this->addFlash(
275
                    'sonata_flash_error',
276
                    $this->trans(
277
                        'flash_delete_error',
278
                        ['%name%' => $this->escapeHtml($objectName)],
279
                        'SonataAdminBundle'
280
                    )
281
                );
282
            }
283
284
            return $this->redirectTo($object);
285
        }
286
287
        // NEXT_MAJOR: Remove this line and use commented line below it instead
288
        $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.x. 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...
289
        // $template = $this->templateRegistry->getTemplate('delete');
290
291
        return $this->renderWithExtraParams($template, [
292
            'object' => $object,
293
            'action' => 'delete',
294
            'csrf_token' => $this->getCsrfToken('sonata.delete'),
295
        ], null);
296
    }
297
298
    /**
299
     * Edit action.
300
     *
301
     * @param int|string|null $id
302
     *
303
     * @throws NotFoundHttpException If the object does not exist
304
     * @throws AccessDeniedException If access is not granted
305
     *
306
     * @return Response|RedirectResponse
307
     */
308
    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...
309
    {
310
        $request = $this->getRequest();
311
        // the key used to lookup the template
312
        $templateKey = 'edit';
313
314
        $id = $request->get($this->admin->getIdParameter());
315
        $existingObject = $this->admin->getObject($id);
316
317
        if (!$existingObject) {
318
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
319
        }
320
321
        $this->checkParentChildAssociation($request, $existingObject);
322
323
        $this->admin->checkAccess('edit', $existingObject);
324
325
        $preResponse = $this->preEdit($request, $existingObject);
326
        if (null !== $preResponse) {
327
            return $preResponse;
328
        }
329
330
        $this->admin->setSubject($existingObject);
331
        $objectId = $this->admin->getNormalizedIdentifier($existingObject);
332
333
        /** @var $form Form */
334
        $form = $this->admin->getForm();
335
        $form->setData($existingObject);
336
        $form->handleRequest($request);
337
338
        if ($form->isSubmitted()) {
339
            $isFormValid = $form->isValid();
340
341
            // persist if the form was valid and if in preview mode the preview was approved
342
            if ($isFormValid && (!$this->isInPreviewMode() || $this->isPreviewApproved())) {
343
                $submittedObject = $form->getData();
344
                $this->admin->setSubject($submittedObject);
345
346
                try {
347
                    $existingObject = $this->admin->update($submittedObject);
348
349
                    if ($this->isXmlHttpRequest()) {
350
                        return $this->renderJson([
351
                            'result' => 'ok',
352
                            'objectId' => $objectId,
353
                            'objectName' => $this->escapeHtml($this->admin->toString($existingObject)),
354
                        ], 200, []);
355
                    }
356
357
                    $this->addFlash(
358
                        'sonata_flash_success',
359
                        $this->trans(
360
                            'flash_edit_success',
361
                            ['%name%' => $this->escapeHtml($this->admin->toString($existingObject))],
362
                            'SonataAdminBundle'
363
                        )
364
                    );
365
366
                    // redirect to edit mode
367
                    return $this->redirectTo($existingObject);
368
                } catch (ModelManagerException $e) {
369
                    $this->handleModelManagerException($e);
370
371
                    $isFormValid = false;
372
                } catch (LockException $e) {
373
                    $this->addFlash('sonata_flash_error', $this->trans('flash_lock_error', [
374
                        '%name%' => $this->escapeHtml($this->admin->toString($existingObject)),
375
                        '%link_start%' => '<a href="'.$this->admin->generateObjectUrl('edit', $existingObject).'">',
376
                        '%link_end%' => '</a>',
377
                    ], 'SonataAdminBundle'));
378
                }
379
            }
380
381
            // show an error message if the form failed validation
382
            if (!$isFormValid) {
383
                if (!$this->isXmlHttpRequest()) {
384
                    $this->addFlash(
385
                        'sonata_flash_error',
386
                        $this->trans(
387
                            'flash_edit_error',
388
                            ['%name%' => $this->escapeHtml($this->admin->toString($existingObject))],
389
                            'SonataAdminBundle'
390
                        )
391
                    );
392
                }
393
            } elseif ($this->isPreviewRequested()) {
394
                // enable the preview template if the form was valid and preview was requested
395
                $templateKey = 'preview';
396
                $this->admin->getShow();
397
            }
398
        }
399
400
        $formView = $form->createView();
401
        // set the theme for the current Admin Form
402
        $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...
403
404
        // NEXT_MAJOR: Remove this line and use commented line below it instead
405
        $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.x. 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...
406
        // $template = $this->templateRegistry->getTemplate($templateKey);
407
408
        return $this->renderWithExtraParams($template, [
409
            'action' => 'edit',
410
            'form' => $formView,
411
            'object' => $existingObject,
412
            'objectId' => $objectId,
413
        ], null);
414
    }
415
416
    /**
417
     * Batch action.
418
     *
419
     * @throws NotFoundHttpException If the HTTP method is not POST
420
     * @throws \RuntimeException     If the batch action is not defined
421
     *
422
     * @return Response|RedirectResponse
423
     */
424
    public function batchAction()
425
    {
426
        $request = $this->getRequest();
427
        $restMethod = $this->getRestMethod();
428
429
        if ('POST' !== $restMethod) {
430
            throw $this->createNotFoundException(sprintf('Invalid request type "%s", POST expected', $restMethod));
431
        }
432
433
        // check the csrf token
434
        $this->validateCsrfToken('sonata.batch');
435
436
        $confirmation = $request->get('confirmation', false);
437
438
        if ($data = json_decode($request->get('data', ''), true)) {
439
            $action = $data['action'];
440
            $idx = $data['idx'];
441
            $allElements = $data['all_elements'];
442
            $request->request->replace(array_merge($request->request->all(), $data));
443
        } else {
444
            $request->request->set('idx', $request->get('idx', []));
445
            $request->request->set('all_elements', $request->get('all_elements', false));
446
447
            $action = $request->get('action');
448
            $idx = $request->get('idx');
449
            $allElements = $request->get('all_elements');
450
            $data = $request->request->all();
451
452
            unset($data['_sonata_csrf_token']);
453
        }
454
455
        $batchActions = $this->admin->getBatchActions();
456
        if (!array_key_exists($action, $batchActions)) {
457
            throw new \RuntimeException(sprintf('The `%s` batch action is not defined', $action));
458
        }
459
460
        $camelizedAction = Inflector::classify($action);
461
        $isRelevantAction = sprintf('batchAction%sIsRelevant', ucfirst($camelizedAction));
462
463
        if (method_exists($this, $isRelevantAction)) {
464
            $nonRelevantMessage = call_user_func([$this, $isRelevantAction], $idx, $allElements, $request);
465
        } else {
466
            $nonRelevantMessage = 0 != count($idx) || $allElements; // at least one item is selected
467
        }
468
469
        if (!$nonRelevantMessage) { // default non relevant message (if false of null)
470
            $nonRelevantMessage = 'flash_batch_empty';
471
        }
472
473
        $datagrid = $this->admin->getDatagrid();
474
        $datagrid->buildPager();
475
476
        if (true !== $nonRelevantMessage) {
477
            $this->addFlash(
478
                'sonata_flash_info',
479
                $this->trans($nonRelevantMessage, [], 'SonataAdminBundle')
480
            );
481
482
            return $this->redirectToList();
483
        }
484
485
        $askConfirmation = $batchActions[$action]['ask_confirmation'] ??
486
            true;
487
488
        if ($askConfirmation && 'ok' != $confirmation) {
489
            $actionLabel = $batchActions[$action]['label'];
490
            $batchTranslationDomain = $batchActions[$action]['translation_domain'] ??
491
                $this->admin->getTranslationDomain();
492
493
            $formView = $datagrid->getForm()->createView();
494
            $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...
495
496
            // NEXT_MAJOR: Remove this line and use commented line below it instead
497
            $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.x. 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...
498
            // $template = $this->templateRegistry->getTemplate('batch_confirmation');
499
500
            return $this->renderWithExtraParams($template, [
501
                'action' => 'list',
502
                'action_label' => $actionLabel,
503
                'batch_translation_domain' => $batchTranslationDomain,
504
                'datagrid' => $datagrid,
505
                'form' => $formView,
506
                'data' => $data,
507
                'csrf_token' => $this->getCsrfToken('sonata.batch'),
508
            ], null);
509
        }
510
511
        // execute the action, batchActionXxxxx
512
        $finalAction = sprintf('batchAction%s', $camelizedAction);
513
        if (!method_exists($this, $finalAction)) {
514
            throw new \RuntimeException(sprintf('A `%s::%s` method must be callable', get_class($this), $finalAction));
515
        }
516
517
        $query = $datagrid->getQuery();
518
519
        $query->setFirstResult(null);
520
        $query->setMaxResults(null);
521
522
        $this->admin->preBatchAction($action, $query, $idx, $allElements);
523
524
        if (count($idx) > 0) {
525
            $this->admin->getModelManager()->addIdentifiersToQuery($this->admin->getClass(), $query, $idx);
526
        } elseif (!$allElements) {
527
            $this->addFlash(
528
                'sonata_flash_info',
529
                $this->trans('flash_batch_no_elements_processed', [], 'SonataAdminBundle')
530
            );
531
532
            return $this->redirectToList();
533
        }
534
535
        return call_user_func([$this, $finalAction], $query, $request);
536
    }
537
538
    /**
539
     * Create action.
540
     *
541
     * @throws AccessDeniedException If access is not granted
542
     *
543
     * @return Response
544
     */
545
    public function createAction()
546
    {
547
        $request = $this->getRequest();
548
        // the key used to lookup the template
549
        $templateKey = 'edit';
550
551
        $this->admin->checkAccess('create');
552
553
        $class = new \ReflectionClass($this->admin->hasActiveSubClass() ? $this->admin->getActiveSubClass() : $this->admin->getClass());
554
555
        if ($class->isAbstract()) {
556
            return $this->renderWithExtraParams(
557
                '@SonataAdmin/CRUD/select_subclass.html.twig',
558
                [
559
                    'base_template' => $this->getBaseTemplate(),
560
                    'admin' => $this->admin,
561
                    'action' => 'create',
562
                ],
563
                null
564
            );
565
        }
566
567
        $newObject = $this->admin->getNewInstance();
568
569
        $preResponse = $this->preCreate($request, $newObject);
570
        if (null !== $preResponse) {
571
            return $preResponse;
572
        }
573
574
        $this->admin->setSubject($newObject);
575
576
        /** @var $form \Symfony\Component\Form\Form */
577
        $form = $this->admin->getForm();
578
        $form->setData($newObject);
579
        $form->handleRequest($request);
580
581
        if ($form->isSubmitted()) {
582
            $isFormValid = $form->isValid();
583
584
            // persist if the form was valid and if in preview mode the preview was approved
585
            if ($isFormValid && (!$this->isInPreviewMode() || $this->isPreviewApproved())) {
586
                $submittedObject = $form->getData();
587
                $this->admin->setSubject($submittedObject);
588
                $this->admin->checkAccess('create', $submittedObject);
589
590
                try {
591
                    $newObject = $this->admin->create($submittedObject);
592
593
                    if ($this->isXmlHttpRequest()) {
594
                        return $this->renderJson([
595
                            'result' => 'ok',
596
                            'objectId' => $this->admin->getNormalizedIdentifier($newObject),
597
                        ], 200, []);
598
                    }
599
600
                    $this->addFlash(
601
                        'sonata_flash_success',
602
                        $this->trans(
603
                            'flash_create_success',
604
                            ['%name%' => $this->escapeHtml($this->admin->toString($newObject))],
605
                            'SonataAdminBundle'
606
                        )
607
                    );
608
609
                    // redirect to edit mode
610
                    return $this->redirectTo($newObject);
611
                } catch (ModelManagerException $e) {
612
                    $this->handleModelManagerException($e);
613
614
                    $isFormValid = false;
615
                }
616
            }
617
618
            // show an error message if the form failed validation
619
            if (!$isFormValid) {
620
                if (!$this->isXmlHttpRequest()) {
621
                    $this->addFlash(
622
                        'sonata_flash_error',
623
                        $this->trans(
624
                            'flash_create_error',
625
                            ['%name%' => $this->escapeHtml($this->admin->toString($newObject))],
626
                            'SonataAdminBundle'
627
                        )
628
                    );
629
                }
630
            } elseif ($this->isPreviewRequested()) {
631
                // pick the preview template if the form was valid and preview was requested
632
                $templateKey = 'preview';
633
                $this->admin->getShow();
634
            }
635
        }
636
637
        $formView = $form->createView();
638
        // set the theme for the current Admin Form
639
        $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...
640
641
        // NEXT_MAJOR: Remove this line and use commented line below it instead
642
        $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.x. 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...
643
        // $template = $this->templateRegistry->getTemplate($templateKey);
644
645
        return $this->renderWithExtraParams($template, [
646
            'action' => 'create',
647
            'form' => $formView,
648
            'object' => $newObject,
649
            'objectId' => null,
650
        ], null);
651
    }
652
653
    /**
654
     * Show action.
655
     *
656
     * @param int|string|null $id
657
     *
658
     * @throws NotFoundHttpException If the object does not exist
659
     * @throws AccessDeniedException If access is not granted
660
     *
661
     * @return Response
662
     */
663
    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...
664
    {
665
        $request = $this->getRequest();
666
        $id = $request->get($this->admin->getIdParameter());
667
668
        $object = $this->admin->getObject($id);
669
670
        if (!$object) {
671
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
672
        }
673
674
        $this->checkParentChildAssociation($request, $object);
675
676
        $this->admin->checkAccess('show', $object);
677
678
        $preResponse = $this->preShow($request, $object);
679
        if (null !== $preResponse) {
680
            return $preResponse;
681
        }
682
683
        $this->admin->setSubject($object);
684
685
        // NEXT_MAJOR: Remove this line and use commented line below it instead
686
        $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.x. 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...
687
        //$template = $this->templateRegistry->getTemplate('show');
688
689
        return $this->renderWithExtraParams($template, [
690
            'action' => 'show',
691
            'object' => $object,
692
            'elements' => $this->admin->getShow(),
693
        ], null);
694
    }
695
696
    /**
697
     * Show history revisions for object.
698
     *
699
     * @param int|string|null $id
700
     *
701
     * @throws AccessDeniedException If access is not granted
702
     * @throws NotFoundHttpException If the object does not exist or the audit reader is not available
703
     *
704
     * @return Response
705
     */
706
    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...
707
    {
708
        $request = $this->getRequest();
709
        $id = $request->get($this->admin->getIdParameter());
710
711
        $object = $this->admin->getObject($id);
712
713
        if (!$object) {
714
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
715
        }
716
717
        $this->admin->checkAccess('history', $object);
718
719
        $manager = $this->get('sonata.admin.audit.manager');
720
721
        if (!$manager->hasReader($this->admin->getClass())) {
722
            throw $this->createNotFoundException(
723
                sprintf(
724
                    'unable to find the audit reader for class : %s',
725
                    $this->admin->getClass()
726
                )
727
            );
728
        }
729
730
        $reader = $manager->getReader($this->admin->getClass());
731
732
        $revisions = $reader->findRevisions($this->admin->getClass(), $id);
733
734
        // NEXT_MAJOR: Remove this line and use commented line below it instead
735
        $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.x. 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...
736
        // $template = $this->templateRegistry->getTemplate('history');
737
738
        return $this->renderWithExtraParams($template, [
739
            'action' => 'history',
740
            'object' => $object,
741
            'revisions' => $revisions,
742
            'currentRevision' => $revisions ? current($revisions) : false,
743
        ], null);
744
    }
745
746
    /**
747
     * View history revision of object.
748
     *
749
     * @param int|string|null $id
750
     * @param string|null     $revision
751
     *
752
     * @throws AccessDeniedException If access is not granted
753
     * @throws NotFoundHttpException If the object or revision does not exist or the audit reader is not available
754
     *
755
     * @return Response
756
     */
757
    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...
758
    {
759
        $request = $this->getRequest();
760
        $id = $request->get($this->admin->getIdParameter());
761
762
        $object = $this->admin->getObject($id);
763
764
        if (!$object) {
765
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
766
        }
767
768
        $this->admin->checkAccess('historyViewRevision', $object);
769
770
        $manager = $this->get('sonata.admin.audit.manager');
771
772
        if (!$manager->hasReader($this->admin->getClass())) {
773
            throw $this->createNotFoundException(
774
                sprintf(
775
                    'unable to find the audit reader for class : %s',
776
                    $this->admin->getClass()
777
                )
778
            );
779
        }
780
781
        $reader = $manager->getReader($this->admin->getClass());
782
783
        // retrieve the revisioned object
784
        $object = $reader->find($this->admin->getClass(), $id, $revision);
785
786
        if (!$object) {
787
            throw $this->createNotFoundException(
788
                sprintf(
789
                    'unable to find the targeted object `%s` from the revision `%s` with classname : `%s`',
790
                    $id,
791
                    $revision,
792
                    $this->admin->getClass()
793
                )
794
            );
795
        }
796
797
        $this->admin->setSubject($object);
798
799
        // NEXT_MAJOR: Remove this line and use commented line below it instead
800
        $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.x. 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...
801
        // $template = $this->templateRegistry->getTemplate('show');
802
803
        return $this->renderWithExtraParams($template, [
804
            'action' => 'show',
805
            'object' => $object,
806
            'elements' => $this->admin->getShow(),
807
        ], null);
808
    }
809
810
    /**
811
     * Compare history revisions of object.
812
     *
813
     * @param int|string|null $id
814
     * @param int|string|null $base_revision
815
     * @param int|string|null $compare_revision
816
     *
817
     * @throws AccessDeniedException If access is not granted
818
     * @throws NotFoundHttpException If the object or revision does not exist or the audit reader is not available
819
     *
820
     * @return Response
821
     */
822
    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...
823
    {
824
        $request = $this->getRequest();
825
826
        $this->admin->checkAccess('historyCompareRevisions');
827
828
        $id = $request->get($this->admin->getIdParameter());
829
830
        $object = $this->admin->getObject($id);
831
832
        if (!$object) {
833
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
834
        }
835
836
        $manager = $this->get('sonata.admin.audit.manager');
837
838
        if (!$manager->hasReader($this->admin->getClass())) {
839
            throw $this->createNotFoundException(
840
                sprintf(
841
                    'unable to find the audit reader for class : %s',
842
                    $this->admin->getClass()
843
                )
844
            );
845
        }
846
847
        $reader = $manager->getReader($this->admin->getClass());
848
849
        // retrieve the base revision
850
        $base_object = $reader->find($this->admin->getClass(), $id, $base_revision);
851
        if (!$base_object) {
852
            throw $this->createNotFoundException(
853
                sprintf(
854
                    'unable to find the targeted object `%s` from the revision `%s` with classname : `%s`',
855
                    $id,
856
                    $base_revision,
857
                    $this->admin->getClass()
858
                )
859
            );
860
        }
861
862
        // retrieve the compare revision
863
        $compare_object = $reader->find($this->admin->getClass(), $id, $compare_revision);
864
        if (!$compare_object) {
865
            throw $this->createNotFoundException(
866
                sprintf(
867
                    'unable to find the targeted object `%s` from the revision `%s` with classname : `%s`',
868
                    $id,
869
                    $compare_revision,
870
                    $this->admin->getClass()
871
                )
872
            );
873
        }
874
875
        $this->admin->setSubject($base_object);
876
877
        // NEXT_MAJOR: Remove this line and use commented line below it instead
878
        $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.x. 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...
879
        // $template = $this->templateRegistry->getTemplate('show_compare');
880
881
        return $this->renderWithExtraParams($template, [
882
            'action' => 'show',
883
            'object' => $base_object,
884
            'object_compare' => $compare_object,
885
            'elements' => $this->admin->getShow(),
886
        ], null);
887
    }
888
889
    /**
890
     * Export data to specified format.
891
     *
892
     * @throws AccessDeniedException If access is not granted
893
     * @throws \RuntimeException     If the export format is invalid
894
     *
895
     * @return Response
896
     */
897
    public function exportAction(Request $request)
898
    {
899
        $this->admin->checkAccess('export');
900
901
        $format = $request->get('format');
902
903
        // NEXT_MAJOR: remove the check
904
        if (!$this->has('sonata.admin.admin_exporter')) {
905
            @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...
906
                'Not registering the exporter bundle is deprecated since version 3.14.'
907
                .' You must register it to be able to use the export action in 4.0.',
908
                E_USER_DEPRECATED
909
            );
910
            $allowedExportFormats = (array) $this->admin->getExportFormats();
911
912
            $class = $this->admin->getClass();
913
            $filename = sprintf(
914
                'export_%s_%s.%s',
915
                strtolower(substr($class, strripos($class, '\\') + 1)),
916
                date('Y_m_d_H_i_s', strtotime('now')),
917
                $format
918
            );
919
            $exporter = $this->get('sonata.admin.exporter');
920
        } else {
921
            $adminExporter = $this->get('sonata.admin.admin_exporter');
922
            $allowedExportFormats = $adminExporter->getAvailableFormats($this->admin);
923
            $filename = $adminExporter->getExportFilename($this->admin, $format);
924
            $exporter = $this->get('sonata.exporter.exporter');
925
        }
926
927
        if (!in_array($format, $allowedExportFormats)) {
928
            throw new \RuntimeException(
929
                sprintf(
930
                    'Export in format `%s` is not allowed for class: `%s`. Allowed formats are: `%s`',
931
                    $format,
932
                    $this->admin->getClass(),
933
                    implode(', ', $allowedExportFormats)
934
                )
935
            );
936
        }
937
938
        return $exporter->getResponse(
939
            $format,
940
            $filename,
941
            $this->admin->getDataSourceIterator()
942
        );
943
    }
944
945
    /**
946
     * Returns the Response object associated to the acl action.
947
     *
948
     * @param int|string|null $id
949
     *
950
     * @throws AccessDeniedException If access is not granted
951
     * @throws NotFoundHttpException If the object does not exist or the ACL is not enabled
952
     *
953
     * @return Response|RedirectResponse
954
     */
955
    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...
956
    {
957
        $request = $this->getRequest();
958
959
        if (!$this->admin->isAclEnabled()) {
960
            throw $this->createNotFoundException('ACL are not enabled for this admin');
961
        }
962
963
        $id = $request->get($this->admin->getIdParameter());
964
965
        $object = $this->admin->getObject($id);
966
967
        if (!$object) {
968
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
969
        }
970
971
        $this->admin->checkAccess('acl', $object);
972
973
        $this->admin->setSubject($object);
974
        $aclUsers = $this->getAclUsers();
975
        $aclRoles = $this->getAclRoles();
976
977
        $adminObjectAclManipulator = $this->get('sonata.admin.object.manipulator.acl.admin');
978
        $adminObjectAclData = new AdminObjectAclData(
979
            $this->admin,
980
            $object,
981
            $aclUsers,
982
            $adminObjectAclManipulator->getMaskBuilderClass(),
983
            $aclRoles
984
        );
985
986
        $aclUsersForm = $adminObjectAclManipulator->createAclUsersForm($adminObjectAclData);
987
        $aclRolesForm = $adminObjectAclManipulator->createAclRolesForm($adminObjectAclData);
988
989
        if ('POST' === $request->getMethod()) {
990
            if ($request->request->has(AdminObjectAclManipulator::ACL_USERS_FORM_NAME)) {
991
                $form = $aclUsersForm;
992
                $updateMethod = 'updateAclUsers';
993
            } elseif ($request->request->has(AdminObjectAclManipulator::ACL_ROLES_FORM_NAME)) {
994
                $form = $aclRolesForm;
995
                $updateMethod = 'updateAclRoles';
996
            }
997
998
            if (isset($form)) {
999
                $form->handleRequest($request);
1000
1001
                if ($form->isValid()) {
1002
                    $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...
1003
                    $this->addFlash(
1004
                        'sonata_flash_success',
1005
                        $this->trans('flash_acl_edit_success', [], 'SonataAdminBundle')
1006
                    );
1007
1008
                    return new RedirectResponse($this->admin->generateObjectUrl('acl', $object));
1009
                }
1010
            }
1011
        }
1012
1013
        // NEXT_MAJOR: Remove this line and use commented line below it instead
1014
        $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.x. 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...
1015
        // $template = $this->templateRegistry->getTemplate('acl');
1016
1017
        return $this->renderWithExtraParams($template, [
1018
            'action' => 'acl',
1019
            'permissions' => $adminObjectAclData->getUserPermissions(),
1020
            'object' => $object,
1021
            'users' => $aclUsers,
1022
            'roles' => $aclRoles,
1023
            'aclUsersForm' => $aclUsersForm->createView(),
1024
            'aclRolesForm' => $aclRolesForm->createView(),
1025
        ], null);
1026
    }
1027
1028
    /**
1029
     * @return Request
1030
     */
1031
    public function getRequest()
1032
    {
1033
        return $this->container->get('request_stack')->getCurrentRequest();
1034
    }
1035
1036
    /**
1037
     * Gets a container configuration parameter by its name.
1038
     *
1039
     * @param string $name The parameter name
1040
     *
1041
     * @return mixed
1042
     */
1043
    protected function getParameter($name)
1044
    {
1045
        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...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...
1046
    }
1047
1048
    /**
1049
     * Render JSON.
1050
     *
1051
     * @param mixed $data
1052
     * @param int   $status
1053
     * @param array $headers
1054
     *
1055
     * @return Response with json encoded data
1056
     */
1057
    protected function renderJson($data, $status = 200, $headers = [])
1058
    {
1059
        return new JsonResponse($data, $status, $headers);
1060
    }
1061
1062
    /**
1063
     * Returns true if the request is a XMLHttpRequest.
1064
     *
1065
     * @return bool True if the request is an XMLHttpRequest, false otherwise
1066
     */
1067
    protected function isXmlHttpRequest()
1068
    {
1069
        $request = $this->getRequest();
1070
1071
        return $request->isXmlHttpRequest() || $request->get('_xml_http_request');
1072
    }
1073
1074
    /**
1075
     * Returns the correct RESTful verb, given either by the request itself or
1076
     * via the "_method" parameter.
1077
     *
1078
     * @return string HTTP method, either
1079
     */
1080
    protected function getRestMethod()
1081
    {
1082
        $request = $this->getRequest();
1083
1084
        if (Request::getHttpMethodParameterOverride() || !$request->request->has('_method')) {
1085
            return $request->getMethod();
1086
        }
1087
1088
        return $request->request->get('_method');
1089
    }
1090
1091
    /**
1092
     * Contextualize the admin class depends on the current request.
1093
     *
1094
     * @throws \RuntimeException
1095
     */
1096
    protected function configure(): void
1097
    {
1098
        $request = $this->getRequest();
1099
1100
        $adminCode = $request->get('_sonata_admin');
1101
1102
        if (!$adminCode) {
1103
            throw new \RuntimeException(sprintf(
1104
                'There is no `_sonata_admin` defined for the controller `%s` and the current route `%s`',
1105
                get_class($this),
1106
                $request->get('_route')
1107
            ));
1108
        }
1109
1110
        $this->admin = $this->container->get('sonata.admin.pool')->getAdminByAdminCode($adminCode);
1111
1112
        if (!$this->admin) {
1113
            throw new \RuntimeException(sprintf(
1114
                'Unable to find the admin class related to the current controller (%s)',
1115
                get_class($this)
1116
            ));
1117
        }
1118
1119
        $this->templateRegistry = $this->container->get($this->admin->getCode().'.template_registry');
1120
        if (!$this->templateRegistry instanceof TemplateRegistryInterface) {
1121
            throw new \RuntimeException(sprintf(
1122
                'Unable to find the template registry related to the current admin (%s)',
1123
                $this->admin->getCode()
1124
            ));
1125
        }
1126
1127
        $rootAdmin = $this->admin;
1128
1129
        while ($rootAdmin->isChild()) {
1130
            $rootAdmin->setCurrentChild(true);
1131
            $rootAdmin = $rootAdmin->getParent();
1132
        }
1133
1134
        $rootAdmin->setRequest($request);
1135
1136
        if ($request->get('uniqid')) {
1137
            $this->admin->setUniqid($request->get('uniqid'));
1138
        }
1139
    }
1140
1141
    /**
1142
     * Proxy for the logger service of the container.
1143
     * If no such service is found, a NullLogger is returned.
1144
     *
1145
     * @return LoggerInterface
1146
     */
1147
    protected function getLogger()
1148
    {
1149
        if ($this->container->has('logger')) {
1150
            return $this->container->get('logger');
1151
        }
1152
1153
        return new NullLogger();
1154
    }
1155
1156
    /**
1157
     * Returns the base template name.
1158
     *
1159
     * @return string The template name
1160
     */
1161
    protected function getBaseTemplate()
1162
    {
1163
        if ($this->isXmlHttpRequest()) {
1164
            // NEXT_MAJOR: Remove this line and use commented line below it instead
1165
            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.x. 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...
1166
            // return $this->templateRegistry->getTemplate('ajax');
1167
        }
1168
1169
        // NEXT_MAJOR: Remove this line and use commented line below it instead
1170
        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.x. 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...
1171
        // return $this->templateRegistry->getTemplate('layout');
1172
    }
1173
1174
    /**
1175
     * @throws \Exception
1176
     */
1177
    protected function handleModelManagerException(\Exception $e): void
1178
    {
1179
        if ($this->get('kernel')->isDebug()) {
1180
            throw $e;
1181
        }
1182
1183
        $context = ['exception' => $e];
1184
        if ($e->getPrevious()) {
1185
            $context['previous_exception_message'] = $e->getPrevious()->getMessage();
1186
        }
1187
        $this->getLogger()->error($e->getMessage(), $context);
1188
    }
1189
1190
    /**
1191
     * Redirect the user depend on this choice.
1192
     *
1193
     * @param object $object
1194
     *
1195
     * @return RedirectResponse
1196
     */
1197
    protected function redirectTo($object)
1198
    {
1199
        $request = $this->getRequest();
1200
1201
        $url = false;
1202
1203
        if (null !== $request->get('btn_update_and_list')) {
1204
            return $this->redirectToList();
1205
        }
1206
        if (null !== $request->get('btn_create_and_list')) {
1207
            return $this->redirectToList();
1208
        }
1209
1210
        if (null !== $request->get('btn_create_and_create')) {
1211
            $params = [];
1212
            if ($this->admin->hasActiveSubClass()) {
1213
                $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...
1214
            }
1215
            $url = $this->admin->generateUrl('create', $params);
1216
        }
1217
1218
        if ('DELETE' === $this->getRestMethod()) {
1219
            return $this->redirectToList();
1220
        }
1221
1222
        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...
1223
            foreach (['edit', 'show'] as $route) {
1224
                if ($this->admin->hasRoute($route) && $this->admin->hasAccess($route, $object)) {
1225
                    $url = $this->admin->generateObjectUrl($route, $object);
1226
1227
                    break;
1228
                }
1229
            }
1230
        }
1231
1232
        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...
1233
            return $this->redirectToList();
1234
        }
1235
1236
        return new RedirectResponse($url);
1237
    }
1238
1239
    /**
1240
     * Redirects the user to the list view.
1241
     *
1242
     * @return RedirectResponse
1243
     */
1244
    final protected function redirectToList()
1245
    {
1246
        $parameters = [];
1247
1248
        if ($filter = $this->admin->getFilterParameters()) {
1249
            $parameters['filter'] = $filter;
1250
        }
1251
1252
        return $this->redirect($this->admin->generateUrl('list', $parameters));
1253
    }
1254
1255
    /**
1256
     * Returns true if the preview is requested to be shown.
1257
     *
1258
     * @return bool
1259
     */
1260
    protected function isPreviewRequested()
1261
    {
1262
        $request = $this->getRequest();
1263
1264
        return null !== $request->get('btn_preview');
1265
    }
1266
1267
    /**
1268
     * Returns true if the preview has been approved.
1269
     *
1270
     * @return bool
1271
     */
1272
    protected function isPreviewApproved()
1273
    {
1274
        $request = $this->getRequest();
1275
1276
        return null !== $request->get('btn_preview_approve');
1277
    }
1278
1279
    /**
1280
     * Returns true if the request is in the preview workflow.
1281
     *
1282
     * That means either a preview is requested or the preview has already been shown
1283
     * and it got approved/declined.
1284
     *
1285
     * @return bool
1286
     */
1287
    protected function isInPreviewMode()
1288
    {
1289
        return $this->admin->supportsPreviewMode()
1290
        && ($this->isPreviewRequested()
1291
            || $this->isPreviewApproved()
1292
            || $this->isPreviewDeclined());
1293
    }
1294
1295
    /**
1296
     * Returns true if the preview has been declined.
1297
     *
1298
     * @return bool
1299
     */
1300
    protected function isPreviewDeclined()
1301
    {
1302
        $request = $this->getRequest();
1303
1304
        return null !== $request->get('btn_preview_decline');
1305
    }
1306
1307
    /**
1308
     * Gets ACL users.
1309
     *
1310
     * @return \Traversable
1311
     */
1312
    protected function getAclUsers()
1313
    {
1314
        $aclUsers = [];
1315
1316
        $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...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...
1317
        if (null !== $userManagerServiceName && $this->has($userManagerServiceName)) {
1318
            $userManager = $this->get($userManagerServiceName);
1319
1320
            if (method_exists($userManager, 'findUsers')) {
1321
                $aclUsers = $userManager->findUsers();
1322
            }
1323
        }
1324
1325
        return is_array($aclUsers) ? new \ArrayIterator($aclUsers) : $aclUsers;
1326
    }
1327
1328
    /**
1329
     * Gets ACL roles.
1330
     *
1331
     * @return \Traversable
1332
     */
1333
    protected function getAclRoles()
1334
    {
1335
        $aclRoles = [];
1336
        $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...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...
1337
        $pool = $this->container->get('sonata.admin.pool');
1338
1339
        foreach ($pool->getAdminServiceIds() as $id) {
1340
            try {
1341
                $admin = $pool->getInstance($id);
1342
            } catch (\Exception $e) {
1343
                continue;
1344
            }
1345
1346
            $baseRole = $admin->getSecurityHandler()->getBaseRole($admin);
1347
            foreach ($admin->getSecurityInformation() as $role => $permissions) {
1348
                $role = sprintf($baseRole, $role);
1349
                $aclRoles[] = $role;
1350
            }
1351
        }
1352
1353
        foreach ($roleHierarchy as $name => $roles) {
1354
            $aclRoles[] = $name;
1355
            $aclRoles = array_merge($aclRoles, $roles);
1356
        }
1357
1358
        $aclRoles = array_unique($aclRoles);
1359
1360
        return is_array($aclRoles) ? new \ArrayIterator($aclRoles) : $aclRoles;
1361
    }
1362
1363
    /**
1364
     * Validate CSRF token for action without form.
1365
     *
1366
     * @param string $intention
1367
     *
1368
     * @throws HttpException
1369
     */
1370
    protected function validateCsrfToken($intention): void
1371
    {
1372
        $request = $this->getRequest();
1373
        $token = $request->request->get('_sonata_csrf_token', false);
1374
1375
        if ($this->container->has('security.csrf.token_manager')) {
1376
            $valid = $this->container->get('security.csrf.token_manager')->isTokenValid(new CsrfToken($intention, $token));
1377
        } else {
1378
            return;
1379
        }
1380
1381
        if (!$valid) {
1382
            throw new HttpException(400, 'The csrf token is not valid, CSRF attack?');
1383
        }
1384
    }
1385
1386
    /**
1387
     * Escape string for html output.
1388
     *
1389
     * @param string $s
1390
     *
1391
     * @return string
1392
     */
1393
    protected function escapeHtml($s)
1394
    {
1395
        return htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
1396
    }
1397
1398
    /**
1399
     * Get CSRF token.
1400
     *
1401
     * @param string $intention
1402
     *
1403
     * @return string|false
1404
     */
1405
    protected function getCsrfToken($intention)
1406
    {
1407
        if ($this->container->has('security.csrf.token_manager')) {
1408
            return $this->container->get('security.csrf.token_manager')->getToken($intention)->getValue();
1409
        }
1410
1411
        return false;
1412
    }
1413
1414
    /**
1415
     * This method can be overloaded in your custom CRUD controller.
1416
     * It's called from createAction.
1417
     *
1418
     * @param mixed $object
1419
     *
1420
     * @return Response|null
1421
     */
1422
    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...
1423
    {
1424
    }
1425
1426
    /**
1427
     * This method can be overloaded in your custom CRUD controller.
1428
     * It's called from editAction.
1429
     *
1430
     * @param mixed $object
1431
     *
1432
     * @return Response|null
1433
     */
1434
    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...
1435
    {
1436
    }
1437
1438
    /**
1439
     * This method can be overloaded in your custom CRUD controller.
1440
     * It's called from deleteAction.
1441
     *
1442
     * @param mixed $object
1443
     *
1444
     * @return Response|null
1445
     */
1446
    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...
1447
    {
1448
    }
1449
1450
    /**
1451
     * This method can be overloaded in your custom CRUD controller.
1452
     * It's called from showAction.
1453
     *
1454
     * @param mixed $object
1455
     *
1456
     * @return Response|null
1457
     */
1458
    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...
1459
    {
1460
    }
1461
1462
    /**
1463
     * This method can be overloaded in your custom CRUD controller.
1464
     * It's called from listAction.
1465
     *
1466
     * @return Response|null
1467
     */
1468
    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...
1469
    {
1470
    }
1471
1472
    /**
1473
     * Translate a message id.
1474
     *
1475
     * @param string $id
1476
     * @param string $domain
1477
     * @param string $locale
1478
     *
1479
     * @return string translated string
1480
     */
1481
    final protected function trans($id, array $parameters = [], $domain = null, $locale = null)
1482
    {
1483
        $domain = $domain ?: $this->admin->getTranslationDomain();
1484
1485
        return $this->get('translator')->trans($id, $parameters, $domain, $locale);
1486
    }
1487
1488
    private function checkParentChildAssociation(Request $request, $object): void
1489
    {
1490
        if (!($parentAdmin = $this->admin->getParent())) {
1491
            return;
1492
        }
1493
1494
        // NEXT_MAJOR: remove this check
1495
        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...
1496
            return;
1497
        }
1498
1499
        $parentId = $request->get($parentAdmin->getIdParameter());
1500
1501
        $propertyAccessor = PropertyAccess::createPropertyAccessor();
1502
        $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...
1503
1504
        if ($parentAdmin->getObject($parentId) !== $propertyAccessor->getValue($object, $propertyPath)) {
1505
            // NEXT_MAJOR: make this exception
1506
            @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...
1507
                ." and won't be allowed in 4.0.",
1508
                E_USER_DEPRECATED
1509
            );
1510
        }
1511
    }
1512
1513
    /**
1514
     * Sets the admin form theme to form view. Used for compatibility between Symfony versions.
1515
     *
1516
     * @param string $theme
1517
     */
1518
    private function setFormTheme(FormView $formView, $theme): void
1519
    {
1520
        $twig = $this->get('twig');
1521
1522
        // BC for Symfony < 3.2 where this runtime does not exists
1523
        if (!method_exists(AppVariable::class, 'getToken')) {
1524
            $twig->getExtension(FormExtension::class)->renderer->setTheme($formView, $theme);
1525
1526
            return;
1527
        }
1528
1529
        // BC for Symfony < 3.4 where runtime should be TwigRenderer
1530
        if (!method_exists(DebugCommand::class, 'getLoaderPaths')) {
1531
            $twig->getRuntime(TwigRenderer::class)->setTheme($formView, $theme);
1532
1533
            return;
1534
        }
1535
1536
        $twig->getRuntime(FormRenderer::class)->setTheme($formView, $theme);
1537
    }
1538
}
1539