Completed
Push — 3.x ( a302a7...034439 )
by Grégoire
04:21
created

CRUDController::renderWithExtraParams()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 18
rs 9.2
c 0
b 0
f 0
cc 4
eloc 11
nc 8
nop 3
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 54 and the first side effect is on line 48.

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
/*
4
 * This file is part of the Sonata Project package.
5
 *
6
 * (c) Thomas Rabaix <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Sonata\AdminBundle\Controller;
13
14
use Doctrine\Common\Inflector\Inflector;
15
use Psr\Log\LoggerInterface;
16
use Psr\Log\NullLogger;
17
use Sonata\AdminBundle\Admin\AdminInterface;
18
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
19
use Sonata\AdminBundle\Exception\LockException;
20
use Sonata\AdminBundle\Exception\ModelManagerException;
21
use Sonata\AdminBundle\Templating\TemplateRegistryInterface;
22
use Sonata\AdminBundle\Util\AdminObjectAclData;
23
use Sonata\AdminBundle\Util\AdminObjectAclManipulator;
24
use Symfony\Bridge\Twig\AppVariable;
25
use Symfony\Bridge\Twig\Command\DebugCommand;
26
use Symfony\Bridge\Twig\Extension\FormExtension;
27
use Symfony\Bridge\Twig\Form\TwigRenderer;
28
use Symfony\Bundle\FrameworkBundle\Controller\ControllerTrait;
29
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
30
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
31
use Symfony\Component\DependencyInjection\ContainerInterface;
32
use Symfony\Component\Form\FormRenderer;
33
use Symfony\Component\Form\FormView;
34
use Symfony\Component\HttpFoundation\JsonResponse;
35
use Symfony\Component\HttpFoundation\RedirectResponse;
36
use Symfony\Component\HttpFoundation\Request;
37
use Symfony\Component\HttpFoundation\Response;
38
use Symfony\Component\HttpKernel\Exception\HttpException;
39
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
40
use Symfony\Component\PropertyAccess\PropertyAccess;
41
use Symfony\Component\PropertyAccess\PropertyPath;
42
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
43
use Symfony\Component\Security\Csrf\CsrfToken;
44
45
// BC for Symfony < 3.3 where this trait does not exist
46
// NEXT_MAJOR: Remove the polyfill and inherit from \Symfony\Bundle\FrameworkBundle\Controller\Controller again
47
if (!trait_exists(ControllerTrait::class)) {
48
    require_once __DIR__.'/PolyfillControllerTrait.php';
49
}
50
51
/**
52
 * @author Thomas Rabaix <[email protected]>
53
 */
54
class CRUDController implements ContainerAwareInterface
55
{
56
    // NEXT_MAJOR: Don't use these traits anymore (inherit from Controller instead)
57
    use ControllerTrait, ContainerAwareTrait {
58
        ControllerTrait::render as originalRender;
59
    }
60
61
    /**
62
     * The related Admin class.
63
     *
64
     * @var AdminInterface
65
     */
66
    protected $admin;
67
68
    /**
69
     * The template registry of the related Admin class.
70
     *
71
     * @var TemplateRegistryInterface
72
     */
73
    private $templateRegistry;
74
75
    // BC for Symfony 3.3 where ControllerTrait exists but does not contain get() and has() methods.
76
    public function __call($method, $arguments)
77
    {
78
        if (in_array($method, ['get', 'has'])) {
79
            return call_user_func_array([$this->container, $method], $arguments);
80
        }
81
82
        if (method_exists($this, 'proxyToControllerClass')) {
83
            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...
84
        }
85
86
        throw new \LogicException('Call to undefined method '.__CLASS__.'::'.$method);
87
    }
88
89
    public function setContainer(ContainerInterface $container = null)
90
    {
91
        $this->container = $container;
92
93
        $this->configure();
94
    }
95
96
    /**
97
     * NEXT_MAJOR: Remove this method.
98
     *
99
     * @see renderWithExtraParams()
100
     *
101
     * @param string $view       The view name
102
     * @param array  $parameters An array of parameters to pass to the view
103
     *
104
     * @return Response A Response instance
105
     *
106
     * @deprecated since version 3.27, to be removed in 4.0. Use Sonata\AdminBundle\Controller\CRUDController::renderWithExtraParams() instead.
107
     */
108
    public function render($view, array $parameters = [], Response $response = null)
109
    {
110
        @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...
111
            'Method '.__CLASS__.'::render has been renamed to '.__CLASS__.'::renderWithExtraParams.',
112
            E_USER_DEPRECATED
113
        );
114
115
        return $this->renderWithExtraParams($view, $parameters, $response);
116
    }
117
118
    /**
119
     * Renders a view while passing mandatory parameters on to the template.
120
     *
121
     * @param string $view The view name
122
     *
123
     * @return Response A Response instance
124
     */
125
    public function renderWithExtraParams($view, array $parameters = [], Response $response = null)
126
    {
127
        if (!$this->isXmlHttpRequest()) {
128
            $parameters['breadcrumbs_builder'] = $this->get('sonata.admin.breadcrumbs_builder');
129
        }
130
        $parameters['admin'] = isset($parameters['admin']) ?
131
            $parameters['admin'] :
132
            $this->admin;
133
134
        $parameters['base_template'] = isset($parameters['base_template']) ?
135
            $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
        // NEXT_MAJOR: Remove reflection check.
456
        $reflector = new \ReflectionMethod($this->admin, 'getBatchActions');
457
        if ($reflector->getDeclaringClass()->getName() === get_class($this->admin)) {
0 ignored issues
show
introduced by
Consider using $reflector->class. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
458
            @trigger_error('Override Sonata\AdminBundle\Admin\AbstractAdmin::getBatchActions method'
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...
459
                .' is deprecated since version 3.2.'
460
                .' Use Sonata\AdminBundle\Admin\AbstractAdmin::configureBatchActions instead.'
461
                .' The method will be final in 4.0.', E_USER_DEPRECATED
462
            );
463
        }
464
        $batchActions = $this->admin->getBatchActions();
465
        if (!array_key_exists($action, $batchActions)) {
466
            throw new \RuntimeException(sprintf('The `%s` batch action is not defined', $action));
467
        }
468
469
        $camelizedAction = Inflector::classify($action);
470
        $isRelevantAction = sprintf('batchAction%sIsRelevant', $camelizedAction);
471
472
        if (method_exists($this, $isRelevantAction)) {
473
            $nonRelevantMessage = call_user_func([$this, $isRelevantAction], $idx, $allElements, $request);
474
        } else {
475
            $nonRelevantMessage = 0 != count($idx) || $allElements; // at least one item is selected
476
        }
477
478
        if (!$nonRelevantMessage) { // default non relevant message (if false of null)
479
            $nonRelevantMessage = 'flash_batch_empty';
480
        }
481
482
        $datagrid = $this->admin->getDatagrid();
483
        $datagrid->buildPager();
484
485
        if (true !== $nonRelevantMessage) {
486
            $this->addFlash(
487
                'sonata_flash_info',
488
                $this->trans($nonRelevantMessage, [], 'SonataAdminBundle')
489
            );
490
491
            return $this->redirectToList();
492
        }
493
494
        $askConfirmation = isset($batchActions[$action]['ask_confirmation']) ?
495
            $batchActions[$action]['ask_confirmation'] :
496
            true;
497
498
        if ($askConfirmation && 'ok' != $confirmation) {
499
            $actionLabel = $batchActions[$action]['label'];
500
            $batchTranslationDomain = isset($batchActions[$action]['translation_domain']) ?
501
                $batchActions[$action]['translation_domain'] :
502
                $this->admin->getTranslationDomain();
503
504
            $formView = $datagrid->getForm()->createView();
505
            $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...
506
507
            // NEXT_MAJOR: Remove this line and use commented line below it instead
508
            $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...
509
            // $template = $this->templateRegistry->getTemplate('batch_confirmation');
510
511
            return $this->renderWithExtraParams($template, [
512
                'action' => 'list',
513
                'action_label' => $actionLabel,
514
                'batch_translation_domain' => $batchTranslationDomain,
515
                'datagrid' => $datagrid,
516
                'form' => $formView,
517
                'data' => $data,
518
                'csrf_token' => $this->getCsrfToken('sonata.batch'),
519
            ], null);
520
        }
521
522
        // execute the action, batchActionXxxxx
523
        $finalAction = sprintf('batchAction%s', $camelizedAction);
524
        if (!method_exists($this, $finalAction)) {
525
            throw new \RuntimeException(sprintf('A `%s::%s` method must be callable', get_class($this), $finalAction));
526
        }
527
528
        $query = $datagrid->getQuery();
529
530
        $query->setFirstResult(null);
531
        $query->setMaxResults(null);
532
533
        $this->admin->preBatchAction($action, $query, $idx, $allElements);
534
535
        if (count($idx) > 0) {
536
            $this->admin->getModelManager()->addIdentifiersToQuery($this->admin->getClass(), $query, $idx);
537
        } elseif (!$allElements) {
538
            $this->addFlash(
539
                'sonata_flash_info',
540
                $this->trans('flash_batch_no_elements_processed', [], 'SonataAdminBundle')
541
            );
542
543
            return $this->redirectToList();
544
        }
545
546
        return call_user_func([$this, $finalAction], $query, $request);
547
    }
548
549
    /**
550
     * Create action.
551
     *
552
     * @throws AccessDeniedException If access is not granted
553
     *
554
     * @return Response
555
     */
556
    public function createAction()
557
    {
558
        $request = $this->getRequest();
559
        // the key used to lookup the template
560
        $templateKey = 'edit';
561
562
        $this->admin->checkAccess('create');
563
564
        $class = new \ReflectionClass($this->admin->hasActiveSubClass() ? $this->admin->getActiveSubClass() : $this->admin->getClass());
565
566
        if ($class->isAbstract()) {
567
            return $this->renderWithExtraParams(
568
                '@SonataAdmin/CRUD/select_subclass.html.twig',
569
                [
570
                    'base_template' => $this->getBaseTemplate(),
571
                    'admin' => $this->admin,
572
                    'action' => 'create',
573
                ],
574
                null
575
            );
576
        }
577
578
        $newObject = $this->admin->getNewInstance();
579
580
        $preResponse = $this->preCreate($request, $newObject);
581
        if (null !== $preResponse) {
582
            return $preResponse;
583
        }
584
585
        $this->admin->setSubject($newObject);
586
587
        /** @var $form \Symfony\Component\Form\Form */
588
        $form = $this->admin->getForm();
589
        $form->setData($newObject);
590
        $form->handleRequest($request);
591
592
        if ($form->isSubmitted()) {
593
            $isFormValid = $form->isValid();
594
595
            // persist if the form was valid and if in preview mode the preview was approved
596
            if ($isFormValid && (!$this->isInPreviewMode() || $this->isPreviewApproved())) {
597
                $submittedObject = $form->getData();
598
                $this->admin->setSubject($submittedObject);
599
                $this->admin->checkAccess('create', $submittedObject);
600
601
                try {
602
                    $newObject = $this->admin->create($submittedObject);
603
604
                    if ($this->isXmlHttpRequest()) {
605
                        return $this->renderJson([
606
                            'result' => 'ok',
607
                            'objectId' => $this->admin->getNormalizedIdentifier($newObject),
608
                        ], 200, []);
609
                    }
610
611
                    $this->addFlash(
612
                        'sonata_flash_success',
613
                        $this->trans(
614
                            'flash_create_success',
615
                            ['%name%' => $this->escapeHtml($this->admin->toString($newObject))],
616
                            'SonataAdminBundle'
617
                        )
618
                    );
619
620
                    // redirect to edit mode
621
                    return $this->redirectTo($newObject);
622
                } catch (ModelManagerException $e) {
623
                    $this->handleModelManagerException($e);
624
625
                    $isFormValid = false;
626
                }
627
            }
628
629
            // show an error message if the form failed validation
630
            if (!$isFormValid) {
631
                if (!$this->isXmlHttpRequest()) {
632
                    $this->addFlash(
633
                        'sonata_flash_error',
634
                        $this->trans(
635
                            'flash_create_error',
636
                            ['%name%' => $this->escapeHtml($this->admin->toString($newObject))],
637
                            'SonataAdminBundle'
638
                        )
639
                    );
640
                }
641
            } elseif ($this->isPreviewRequested()) {
642
                // pick the preview template if the form was valid and preview was requested
643
                $templateKey = 'preview';
644
                $this->admin->getShow();
645
            }
646
        }
647
648
        $formView = $form->createView();
649
        // set the theme for the current Admin Form
650
        $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...
651
652
        // NEXT_MAJOR: Remove this line and use commented line below it instead
653
        $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...
654
        // $template = $this->templateRegistry->getTemplate($templateKey);
655
656
        return $this->renderWithExtraParams($template, [
657
            'action' => 'create',
658
            'form' => $formView,
659
            'object' => $newObject,
660
            'objectId' => null,
661
        ], null);
662
    }
663
664
    /**
665
     * Show action.
666
     *
667
     * @param int|string|null $id
668
     *
669
     * @throws NotFoundHttpException If the object does not exist
670
     * @throws AccessDeniedException If access is not granted
671
     *
672
     * @return Response
673
     */
674
    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...
675
    {
676
        $request = $this->getRequest();
677
        $id = $request->get($this->admin->getIdParameter());
678
679
        $object = $this->admin->getObject($id);
680
681
        if (!$object) {
682
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
683
        }
684
685
        $this->checkParentChildAssociation($request, $object);
686
687
        $this->admin->checkAccess('show', $object);
688
689
        $preResponse = $this->preShow($request, $object);
690
        if (null !== $preResponse) {
691
            return $preResponse;
692
        }
693
694
        $this->admin->setSubject($object);
695
696
        // NEXT_MAJOR: Remove this line and use commented line below it instead
697
        $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...
698
        //$template = $this->templateRegistry->getTemplate('show');
699
700
        return $this->renderWithExtraParams($template, [
701
            'action' => 'show',
702
            'object' => $object,
703
            'elements' => $this->admin->getShow(),
704
        ], null);
705
    }
706
707
    /**
708
     * Show history revisions for object.
709
     *
710
     * @param int|string|null $id
711
     *
712
     * @throws AccessDeniedException If access is not granted
713
     * @throws NotFoundHttpException If the object does not exist or the audit reader is not available
714
     *
715
     * @return Response
716
     */
717
    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...
718
    {
719
        $request = $this->getRequest();
720
        $id = $request->get($this->admin->getIdParameter());
721
722
        $object = $this->admin->getObject($id);
723
724
        if (!$object) {
725
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
726
        }
727
728
        $this->admin->checkAccess('history', $object);
729
730
        $manager = $this->get('sonata.admin.audit.manager');
731
732
        if (!$manager->hasReader($this->admin->getClass())) {
733
            throw $this->createNotFoundException(
734
                sprintf(
735
                    'unable to find the audit reader for class : %s',
736
                    $this->admin->getClass()
737
                )
738
            );
739
        }
740
741
        $reader = $manager->getReader($this->admin->getClass());
742
743
        $revisions = $reader->findRevisions($this->admin->getClass(), $id);
744
745
        // NEXT_MAJOR: Remove this line and use commented line below it instead
746
        $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...
747
        // $template = $this->templateRegistry->getTemplate('history');
748
749
        return $this->renderWithExtraParams($template, [
750
            'action' => 'history',
751
            'object' => $object,
752
            'revisions' => $revisions,
753
            'currentRevision' => $revisions ? current($revisions) : false,
754
        ], null);
755
    }
756
757
    /**
758
     * View history revision of object.
759
     *
760
     * @param int|string|null $id
761
     * @param string|null     $revision
762
     *
763
     * @throws AccessDeniedException If access is not granted
764
     * @throws NotFoundHttpException If the object or revision does not exist or the audit reader is not available
765
     *
766
     * @return Response
767
     */
768
    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...
769
    {
770
        $request = $this->getRequest();
771
        $id = $request->get($this->admin->getIdParameter());
772
773
        $object = $this->admin->getObject($id);
774
775
        if (!$object) {
776
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
777
        }
778
779
        $this->admin->checkAccess('historyViewRevision', $object);
780
781
        $manager = $this->get('sonata.admin.audit.manager');
782
783
        if (!$manager->hasReader($this->admin->getClass())) {
784
            throw $this->createNotFoundException(
785
                sprintf(
786
                    'unable to find the audit reader for class : %s',
787
                    $this->admin->getClass()
788
                )
789
            );
790
        }
791
792
        $reader = $manager->getReader($this->admin->getClass());
793
794
        // retrieve the revisioned object
795
        $object = $reader->find($this->admin->getClass(), $id, $revision);
796
797
        if (!$object) {
798
            throw $this->createNotFoundException(
799
                sprintf(
800
                    'unable to find the targeted object `%s` from the revision `%s` with classname : `%s`',
801
                    $id,
802
                    $revision,
803
                    $this->admin->getClass()
804
                )
805
            );
806
        }
807
808
        $this->admin->setSubject($object);
809
810
        // NEXT_MAJOR: Remove this line and use commented line below it instead
811
        $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...
812
        // $template = $this->templateRegistry->getTemplate('show');
813
814
        return $this->renderWithExtraParams($template, [
815
            'action' => 'show',
816
            'object' => $object,
817
            'elements' => $this->admin->getShow(),
818
        ], null);
819
    }
820
821
    /**
822
     * Compare history revisions of object.
823
     *
824
     * @param int|string|null $id
825
     * @param int|string|null $base_revision
826
     * @param int|string|null $compare_revision
827
     *
828
     * @throws AccessDeniedException If access is not granted
829
     * @throws NotFoundHttpException If the object or revision does not exist or the audit reader is not available
830
     *
831
     * @return Response
832
     */
833
    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...
834
    {
835
        $request = $this->getRequest();
836
837
        $this->admin->checkAccess('historyCompareRevisions');
838
839
        $id = $request->get($this->admin->getIdParameter());
840
841
        $object = $this->admin->getObject($id);
842
843
        if (!$object) {
844
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
845
        }
846
847
        $manager = $this->get('sonata.admin.audit.manager');
848
849
        if (!$manager->hasReader($this->admin->getClass())) {
850
            throw $this->createNotFoundException(
851
                sprintf(
852
                    'unable to find the audit reader for class : %s',
853
                    $this->admin->getClass()
854
                )
855
            );
856
        }
857
858
        $reader = $manager->getReader($this->admin->getClass());
859
860
        // retrieve the base revision
861
        $base_object = $reader->find($this->admin->getClass(), $id, $base_revision);
862
        if (!$base_object) {
863
            throw $this->createNotFoundException(
864
                sprintf(
865
                    'unable to find the targeted object `%s` from the revision `%s` with classname : `%s`',
866
                    $id,
867
                    $base_revision,
868
                    $this->admin->getClass()
869
                )
870
            );
871
        }
872
873
        // retrieve the compare revision
874
        $compare_object = $reader->find($this->admin->getClass(), $id, $compare_revision);
875
        if (!$compare_object) {
876
            throw $this->createNotFoundException(
877
                sprintf(
878
                    'unable to find the targeted object `%s` from the revision `%s` with classname : `%s`',
879
                    $id,
880
                    $compare_revision,
881
                    $this->admin->getClass()
882
                )
883
            );
884
        }
885
886
        $this->admin->setSubject($base_object);
887
888
        // NEXT_MAJOR: Remove this line and use commented line below it instead
889
        $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...
890
        // $template = $this->templateRegistry->getTemplate('show_compare');
891
892
        return $this->renderWithExtraParams($template, [
893
            'action' => 'show',
894
            'object' => $base_object,
895
            'object_compare' => $compare_object,
896
            'elements' => $this->admin->getShow(),
897
        ], null);
898
    }
899
900
    /**
901
     * Export data to specified format.
902
     *
903
     * @throws AccessDeniedException If access is not granted
904
     * @throws \RuntimeException     If the export format is invalid
905
     *
906
     * @return Response
907
     */
908
    public function exportAction(Request $request)
909
    {
910
        $this->admin->checkAccess('export');
911
912
        $format = $request->get('format');
913
914
        // NEXT_MAJOR: remove the check
915
        if (!$this->has('sonata.admin.admin_exporter')) {
916
            @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...
917
                'Not registering the exporter bundle is deprecated since version 3.14.'
918
                .' You must register it to be able to use the export action in 4.0.',
919
                E_USER_DEPRECATED
920
            );
921
            $allowedExportFormats = (array) $this->admin->getExportFormats();
922
923
            $class = $this->admin->getClass();
924
            $filename = sprintf(
925
                'export_%s_%s.%s',
926
                strtolower(substr($class, strripos($class, '\\') + 1)),
927
                date('Y_m_d_H_i_s', strtotime('now')),
928
                $format
929
            );
930
            $exporter = $this->get('sonata.admin.exporter');
931
        } else {
932
            $adminExporter = $this->get('sonata.admin.admin_exporter');
933
            $allowedExportFormats = $adminExporter->getAvailableFormats($this->admin);
934
            $filename = $adminExporter->getExportFilename($this->admin, $format);
935
            $exporter = $this->get('sonata.exporter.exporter');
936
        }
937
938
        if (!in_array($format, $allowedExportFormats)) {
939
            throw new \RuntimeException(
940
                sprintf(
941
                    'Export in format `%s` is not allowed for class: `%s`. Allowed formats are: `%s`',
942
                    $format,
943
                    $this->admin->getClass(),
944
                    implode(', ', $allowedExportFormats)
945
                )
946
            );
947
        }
948
949
        return $exporter->getResponse(
950
            $format,
951
            $filename,
952
            $this->admin->getDataSourceIterator()
953
        );
954
    }
955
956
    /**
957
     * Returns the Response object associated to the acl action.
958
     *
959
     * @param int|string|null $id
960
     *
961
     * @throws AccessDeniedException If access is not granted
962
     * @throws NotFoundHttpException If the object does not exist or the ACL is not enabled
963
     *
964
     * @return Response|RedirectResponse
965
     */
966
    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...
967
    {
968
        $request = $this->getRequest();
969
970
        if (!$this->admin->isAclEnabled()) {
971
            throw $this->createNotFoundException('ACL are not enabled for this admin');
972
        }
973
974
        $id = $request->get($this->admin->getIdParameter());
975
976
        $object = $this->admin->getObject($id);
977
978
        if (!$object) {
979
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
980
        }
981
982
        $this->admin->checkAccess('acl', $object);
983
984
        $this->admin->setSubject($object);
985
        $aclUsers = $this->getAclUsers();
986
        $aclRoles = $this->getAclRoles();
987
988
        $adminObjectAclManipulator = $this->get('sonata.admin.object.manipulator.acl.admin');
989
        $adminObjectAclData = new AdminObjectAclData(
990
            $this->admin,
991
            $object,
992
            $aclUsers,
993
            $adminObjectAclManipulator->getMaskBuilderClass(),
994
            $aclRoles
995
        );
996
997
        $aclUsersForm = $adminObjectAclManipulator->createAclUsersForm($adminObjectAclData);
998
        $aclRolesForm = $adminObjectAclManipulator->createAclRolesForm($adminObjectAclData);
999
1000
        if ('POST' === $request->getMethod()) {
1001
            if ($request->request->has(AdminObjectAclManipulator::ACL_USERS_FORM_NAME)) {
1002
                $form = $aclUsersForm;
1003
                $updateMethod = 'updateAclUsers';
1004
            } elseif ($request->request->has(AdminObjectAclManipulator::ACL_ROLES_FORM_NAME)) {
1005
                $form = $aclRolesForm;
1006
                $updateMethod = 'updateAclRoles';
1007
            }
1008
1009
            if (isset($form)) {
1010
                $form->handleRequest($request);
1011
1012
                if ($form->isValid()) {
1013
                    $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...
1014
                    $this->addFlash(
1015
                        'sonata_flash_success',
1016
                        $this->trans('flash_acl_edit_success', [], 'SonataAdminBundle')
1017
                    );
1018
1019
                    return new RedirectResponse($this->admin->generateObjectUrl('acl', $object));
1020
                }
1021
            }
1022
        }
1023
1024
        // NEXT_MAJOR: Remove this line and use commented line below it instead
1025
        $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...
1026
        // $template = $this->templateRegistry->getTemplate('acl');
1027
1028
        return $this->renderWithExtraParams($template, [
1029
            'action' => 'acl',
1030
            'permissions' => $adminObjectAclData->getUserPermissions(),
1031
            'object' => $object,
1032
            'users' => $aclUsers,
1033
            'roles' => $aclRoles,
1034
            'aclUsersForm' => $aclUsersForm->createView(),
1035
            'aclRolesForm' => $aclRolesForm->createView(),
1036
        ], null);
1037
    }
1038
1039
    /**
1040
     * @return Request
1041
     */
1042
    public function getRequest()
1043
    {
1044
        return $this->container->get('request_stack')->getCurrentRequest();
1045
    }
1046
1047
    /**
1048
     * Gets a container configuration parameter by its name.
1049
     *
1050
     * @param string $name The parameter name
1051
     *
1052
     * @return mixed
1053
     */
1054
    protected function getParameter($name)
1055
    {
1056
        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...
1057
    }
1058
1059
    /**
1060
     * Render JSON.
1061
     *
1062
     * @param mixed $data
1063
     * @param int   $status
1064
     * @param array $headers
1065
     *
1066
     * @return Response with json encoded data
1067
     */
1068
    protected function renderJson($data, $status = 200, $headers = [])
1069
    {
1070
        return new JsonResponse($data, $status, $headers);
1071
    }
1072
1073
    /**
1074
     * Returns true if the request is a XMLHttpRequest.
1075
     *
1076
     * @return bool True if the request is an XMLHttpRequest, false otherwise
1077
     */
1078
    protected function isXmlHttpRequest()
1079
    {
1080
        $request = $this->getRequest();
1081
1082
        return $request->isXmlHttpRequest() || $request->get('_xml_http_request');
1083
    }
1084
1085
    /**
1086
     * Returns the correct RESTful verb, given either by the request itself or
1087
     * via the "_method" parameter.
1088
     *
1089
     * @return string HTTP method, either
1090
     */
1091
    protected function getRestMethod()
1092
    {
1093
        $request = $this->getRequest();
1094
1095
        if (Request::getHttpMethodParameterOverride() || !$request->request->has('_method')) {
1096
            return $request->getMethod();
1097
        }
1098
1099
        return $request->request->get('_method');
1100
    }
1101
1102
    /**
1103
     * Contextualize the admin class depends on the current request.
1104
     *
1105
     * @throws \RuntimeException
1106
     */
1107
    protected function configure()
1108
    {
1109
        $request = $this->getRequest();
1110
1111
        $adminCode = $request->get('_sonata_admin');
1112
1113
        if (!$adminCode) {
1114
            throw new \RuntimeException(sprintf(
1115
                'There is no `_sonata_admin` defined for the controller `%s` and the current route `%s`',
1116
                get_class($this),
1117
                $request->get('_route')
1118
            ));
1119
        }
1120
1121
        $this->admin = $this->container->get('sonata.admin.pool')->getAdminByAdminCode($adminCode);
1122
1123
        if (!$this->admin) {
1124
            throw new \RuntimeException(sprintf(
1125
                'Unable to find the admin class related to the current controller (%s)',
1126
                get_class($this)
1127
            ));
1128
        }
1129
1130
        $this->templateRegistry = $this->container->get($this->admin->getCode().'.template_registry');
1131
        if (!$this->templateRegistry instanceof TemplateRegistryInterface) {
1132
            throw new \RuntimeException(sprintf(
1133
                'Unable to find the template registry related to the current admin (%s)',
1134
                $this->admin->getCode()
1135
            ));
1136
        }
1137
1138
        $rootAdmin = $this->admin;
1139
1140
        while ($rootAdmin->isChild()) {
1141
            $rootAdmin->setCurrentChild(true);
1142
            $rootAdmin = $rootAdmin->getParent();
1143
        }
1144
1145
        $rootAdmin->setRequest($request);
1146
1147
        if ($request->get('uniqid')) {
1148
            $this->admin->setUniqid($request->get('uniqid'));
1149
        }
1150
    }
1151
1152
    /**
1153
     * Proxy for the logger service of the container.
1154
     * If no such service is found, a NullLogger is returned.
1155
     *
1156
     * @return LoggerInterface
1157
     */
1158
    protected function getLogger()
1159
    {
1160
        if ($this->container->has('logger')) {
1161
            return $this->container->get('logger');
1162
        }
1163
1164
        return new NullLogger();
1165
    }
1166
1167
    /**
1168
     * Returns the base template name.
1169
     *
1170
     * @return string The template name
1171
     */
1172
    protected function getBaseTemplate()
1173
    {
1174
        if ($this->isXmlHttpRequest()) {
1175
            // NEXT_MAJOR: Remove this line and use commented line below it instead
1176
            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...
1177
            // return $this->templateRegistry->getTemplate('ajax');
1178
        }
1179
1180
        // NEXT_MAJOR: Remove this line and use commented line below it instead
1181
        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...
1182
        // return $this->templateRegistry->getTemplate('layout');
1183
    }
1184
1185
    /**
1186
     * @throws \Exception
1187
     */
1188
    protected function handleModelManagerException(\Exception $e)
1189
    {
1190
        if ($this->get('kernel')->isDebug()) {
1191
            throw $e;
1192
        }
1193
1194
        $context = ['exception' => $e];
1195
        if ($e->getPrevious()) {
1196
            $context['previous_exception_message'] = $e->getPrevious()->getMessage();
1197
        }
1198
        $this->getLogger()->error($e->getMessage(), $context);
1199
    }
1200
1201
    /**
1202
     * Redirect the user depend on this choice.
1203
     *
1204
     * @param object $object
1205
     *
1206
     * @return RedirectResponse
1207
     */
1208
    protected function redirectTo($object)
1209
    {
1210
        $request = $this->getRequest();
1211
1212
        $url = false;
1213
1214
        if (null !== $request->get('btn_update_and_list')) {
1215
            return $this->redirectToList();
1216
        }
1217
        if (null !== $request->get('btn_create_and_list')) {
1218
            return $this->redirectToList();
1219
        }
1220
1221
        if (null !== $request->get('btn_create_and_create')) {
1222
            $params = [];
1223
            if ($this->admin->hasActiveSubClass()) {
1224
                $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...
1225
            }
1226
            $url = $this->admin->generateUrl('create', $params);
1227
        }
1228
1229
        if ('DELETE' === $this->getRestMethod()) {
1230
            return $this->redirectToList();
1231
        }
1232
1233
        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...
1234
            foreach (['edit', 'show'] as $route) {
1235
                if ($this->admin->hasRoute($route) && $this->admin->hasAccess($route, $object)) {
1236
                    $url = $this->admin->generateObjectUrl($route, $object);
1237
1238
                    break;
1239
                }
1240
            }
1241
        }
1242
1243
        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...
1244
            return $this->redirectToList();
1245
        }
1246
1247
        return new RedirectResponse($url);
1248
    }
1249
1250
    /**
1251
     * Redirects the user to the list view.
1252
     *
1253
     * @return RedirectResponse
1254
     */
1255
    final protected function redirectToList()
1256
    {
1257
        $parameters = [];
1258
1259
        if ($filter = $this->admin->getFilterParameters()) {
1260
            $parameters['filter'] = $filter;
1261
        }
1262
1263
        return $this->redirect($this->admin->generateUrl('list', $parameters));
1264
    }
1265
1266
    /**
1267
     * Returns true if the preview is requested to be shown.
1268
     *
1269
     * @return bool
1270
     */
1271
    protected function isPreviewRequested()
1272
    {
1273
        $request = $this->getRequest();
1274
1275
        return null !== $request->get('btn_preview');
1276
    }
1277
1278
    /**
1279
     * Returns true if the preview has been approved.
1280
     *
1281
     * @return bool
1282
     */
1283
    protected function isPreviewApproved()
1284
    {
1285
        $request = $this->getRequest();
1286
1287
        return null !== $request->get('btn_preview_approve');
1288
    }
1289
1290
    /**
1291
     * Returns true if the request is in the preview workflow.
1292
     *
1293
     * That means either a preview is requested or the preview has already been shown
1294
     * and it got approved/declined.
1295
     *
1296
     * @return bool
1297
     */
1298
    protected function isInPreviewMode()
1299
    {
1300
        return $this->admin->supportsPreviewMode()
1301
        && ($this->isPreviewRequested()
1302
            || $this->isPreviewApproved()
1303
            || $this->isPreviewDeclined());
1304
    }
1305
1306
    /**
1307
     * Returns true if the preview has been declined.
1308
     *
1309
     * @return bool
1310
     */
1311
    protected function isPreviewDeclined()
1312
    {
1313
        $request = $this->getRequest();
1314
1315
        return null !== $request->get('btn_preview_decline');
1316
    }
1317
1318
    /**
1319
     * Gets ACL users.
1320
     *
1321
     * @return \Traversable
1322
     */
1323
    protected function getAclUsers()
1324
    {
1325
        $aclUsers = [];
1326
1327
        $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...
1328
        if (null !== $userManagerServiceName && $this->has($userManagerServiceName)) {
1329
            $userManager = $this->get($userManagerServiceName);
1330
1331
            if (method_exists($userManager, 'findUsers')) {
1332
                $aclUsers = $userManager->findUsers();
1333
            }
1334
        }
1335
1336
        return is_array($aclUsers) ? new \ArrayIterator($aclUsers) : $aclUsers;
1337
    }
1338
1339
    /**
1340
     * Gets ACL roles.
1341
     *
1342
     * @return \Traversable
1343
     */
1344
    protected function getAclRoles()
1345
    {
1346
        $aclRoles = [];
1347
        $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...
1348
        $pool = $this->container->get('sonata.admin.pool');
1349
1350
        foreach ($pool->getAdminServiceIds() as $id) {
1351
            try {
1352
                $admin = $pool->getInstance($id);
1353
            } catch (\Exception $e) {
1354
                continue;
1355
            }
1356
1357
            $baseRole = $admin->getSecurityHandler()->getBaseRole($admin);
1358
            foreach ($admin->getSecurityInformation() as $role => $permissions) {
1359
                $role = sprintf($baseRole, $role);
1360
                $aclRoles[] = $role;
1361
            }
1362
        }
1363
1364
        foreach ($roleHierarchy as $name => $roles) {
1365
            $aclRoles[] = $name;
1366
            $aclRoles = array_merge($aclRoles, $roles);
1367
        }
1368
1369
        $aclRoles = array_unique($aclRoles);
1370
1371
        return is_array($aclRoles) ? new \ArrayIterator($aclRoles) : $aclRoles;
1372
    }
1373
1374
    /**
1375
     * Validate CSRF token for action without form.
1376
     *
1377
     * @param string $intention
1378
     *
1379
     * @throws HttpException
1380
     */
1381
    protected function validateCsrfToken($intention)
1382
    {
1383
        $request = $this->getRequest();
1384
        $token = $request->request->get('_sonata_csrf_token', false);
1385
1386
        if ($this->container->has('security.csrf.token_manager')) {
1387
            $valid = $this->container->get('security.csrf.token_manager')->isTokenValid(new CsrfToken($intention, $token));
1388
        } else {
1389
            return;
1390
        }
1391
1392
        if (!$valid) {
1393
            throw new HttpException(400, 'The csrf token is not valid, CSRF attack?');
1394
        }
1395
    }
1396
1397
    /**
1398
     * Escape string for html output.
1399
     *
1400
     * @param string $s
1401
     *
1402
     * @return string
1403
     */
1404
    protected function escapeHtml($s)
1405
    {
1406
        return htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
1407
    }
1408
1409
    /**
1410
     * Get CSRF token.
1411
     *
1412
     * @param string $intention
1413
     *
1414
     * @return string|false
1415
     */
1416
    protected function getCsrfToken($intention)
1417
    {
1418
        if ($this->container->has('security.csrf.token_manager')) {
1419
            return $this->container->get('security.csrf.token_manager')->getToken($intention)->getValue();
1420
        }
1421
1422
        return false;
1423
    }
1424
1425
    /**
1426
     * This method can be overloaded in your custom CRUD controller.
1427
     * It's called from createAction.
1428
     *
1429
     * @param mixed $object
1430
     *
1431
     * @return Response|null
1432
     */
1433
    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...
1434
    {
1435
    }
1436
1437
    /**
1438
     * This method can be overloaded in your custom CRUD controller.
1439
     * It's called from editAction.
1440
     *
1441
     * @param mixed $object
1442
     *
1443
     * @return Response|null
1444
     */
1445
    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...
1446
    {
1447
    }
1448
1449
    /**
1450
     * This method can be overloaded in your custom CRUD controller.
1451
     * It's called from deleteAction.
1452
     *
1453
     * @param mixed $object
1454
     *
1455
     * @return Response|null
1456
     */
1457
    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...
1458
    {
1459
    }
1460
1461
    /**
1462
     * This method can be overloaded in your custom CRUD controller.
1463
     * It's called from showAction.
1464
     *
1465
     * @param mixed $object
1466
     *
1467
     * @return Response|null
1468
     */
1469
    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...
1470
    {
1471
    }
1472
1473
    /**
1474
     * This method can be overloaded in your custom CRUD controller.
1475
     * It's called from listAction.
1476
     *
1477
     * @return Response|null
1478
     */
1479
    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...
1480
    {
1481
    }
1482
1483
    /**
1484
     * Translate a message id.
1485
     *
1486
     * @param string $id
1487
     * @param string $domain
1488
     * @param string $locale
1489
     *
1490
     * @return string translated string
1491
     */
1492
    final protected function trans($id, array $parameters = [], $domain = null, $locale = null)
1493
    {
1494
        $domain = $domain ?: $this->admin->getTranslationDomain();
1495
1496
        return $this->get('translator')->trans($id, $parameters, $domain, $locale);
1497
    }
1498
1499
    private function checkParentChildAssociation(Request $request, $object)
1500
    {
1501
        if (!($parentAdmin = $this->admin->getParent())) {
1502
            return;
1503
        }
1504
1505
        // NEXT_MAJOR: remove this check
1506
        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...
1507
            return;
1508
        }
1509
1510
        $parentId = $request->get($parentAdmin->getIdParameter());
1511
1512
        $propertyAccessor = PropertyAccess::createPropertyAccessor();
1513
        $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...
1514
1515
        if ($parentAdmin->getObject($parentId) !== $propertyAccessor->getValue($object, $propertyPath)) {
1516
            // NEXT_MAJOR: make this exception
1517
            @trigger_error("Accessing a child that isn't connected to a given parent is deprecated since 3.x"
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...
1518
                ." and won't be allowed in 4.0.",
1519
                E_USER_DEPRECATED
1520
            );
1521
        }
1522
    }
1523
1524
    /**
1525
     * Sets the admin form theme to form view. Used for compatibility between Symfony versions.
1526
     *
1527
     * @param string $theme
1528
     */
1529
    private function setFormTheme(FormView $formView, $theme)
1530
    {
1531
        $twig = $this->get('twig');
1532
1533
        // BC for Symfony < 3.2 where this runtime does not exists
1534
        if (!method_exists(AppVariable::class, 'getToken')) {
1535
            $twig->getExtension(FormExtension::class)->renderer->setTheme($formView, $theme);
1536
1537
            return;
1538
        }
1539
1540
        // BC for Symfony < 3.4 where runtime should be TwigRenderer
1541
        if (!method_exists(DebugCommand::class, 'getLoaderPaths')) {
1542
            $twig->getRuntime(TwigRenderer::class)->setTheme($formView, $theme);
1543
1544
            return;
1545
        }
1546
1547
        $twig->getRuntime(FormRenderer::class)->setTheme($formView, $theme);
1548
    }
1549
}
1550