Completed
Push — master ( 3a65e8...9f4bbe )
by Grégoire
16s
created

CRUDController::batchActionDelete()   A

Complexity

Conditions 2
Paths 3

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 22
rs 9.2
c 0
b 0
f 0
cc 2
eloc 14
nc 3
nop 1
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 51 and the first side effect is on line 45.

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\Util\AdminObjectAclData;
22
use Sonata\AdminBundle\Util\AdminObjectAclManipulator;
23
use Symfony\Bridge\Twig\AppVariable;
24
use Symfony\Bridge\Twig\Command\DebugCommand;
25
use Symfony\Bridge\Twig\Extension\FormExtension;
26
use Symfony\Bridge\Twig\Form\TwigRenderer;
27
use Symfony\Bundle\FrameworkBundle\Controller\ControllerTrait;
28
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
29
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
30
use Symfony\Component\DependencyInjection\ContainerInterface;
31
use Symfony\Component\Form\FormRenderer;
32
use Symfony\Component\Form\FormView;
33
use Symfony\Component\HttpFoundation\JsonResponse;
34
use Symfony\Component\HttpFoundation\RedirectResponse;
35
use Symfony\Component\HttpFoundation\Request;
36
use Symfony\Component\HttpFoundation\Response;
37
use Symfony\Component\HttpKernel\Exception\HttpException;
38
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
39
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
40
use Symfony\Component\Security\Csrf\CsrfToken;
41
42
// BC for Symfony < 3.3 where this trait does not exist
43
// NEXT_MAJOR: Remove the polyfill and inherit from \Symfony\Bundle\FrameworkBundle\Controller\Controller again
44
if (!trait_exists(ControllerTrait::class)) {
45
    require_once __DIR__.'/PolyfillControllerTrait.php';
46
}
47
48
/**
49
 * @author Thomas Rabaix <[email protected]>
50
 */
51
class CRUDController implements ContainerAwareInterface
52
{
53
    // NEXT_MAJOR: Don't use these traits anymore (inherit from Controller instead)
54
    use ControllerTrait, ContainerAwareTrait {
55
        ControllerTrait::render as originalRender;
56
    }
57
58
    /**
59
     * The related Admin class.
60
     *
61
     * @var AdminInterface
62
     */
63
    protected $admin;
64
65
    // BC for Symfony 3.3 where ControllerTrait exists but does not contain get() and has() methods.
66
    public function __call($method, $arguments)
67
    {
68
        if (in_array($method, ['get', 'has'])) {
69
            return call_user_func_array([$this->container, $method], $arguments);
70
        }
71
72
        if (method_exists($this, 'proxyToControllerClass')) {
73
            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...
74
        }
75
76
        throw new \LogicException('Call to undefined method '.__CLASS__.'::'.$method);
77
    }
78
79
    /**
80
     * Sets the Container associated with this Controller.
81
     *
82
     * @param ContainerInterface $container A ContainerInterface instance
83
     */
84
    public function setContainer(ContainerInterface $container = null)
85
    {
86
        $this->container = $container;
87
88
        $this->configure();
89
    }
90
91
    /**
92
     * NEXT_MAJOR: Remove this method.
93
     *
94
     * @see renderWithExtraParams()
95
     *
96
     * @param string   $view       The view name
97
     * @param array    $parameters An array of parameters to pass to the view
98
     * @param Response $response   A response instance
99
     *
100
     * @return Response A Response instance
101
     *
102
     * @deprecated since version 3.27, to be removed in 4.0. Use Sonata\AdminBundle\Controller\CRUDController instead.
103
     */
104
    public function render($view, array $parameters = [], Response $response = null)
105
    {
106
        return $this->renderWithExtraParams($view, $parameters, $response);
107
    }
108
109
    /**
110
     * Renders a view while passing mandatory parameters on to the template.
111
     *
112
     * @param string   $view       The view name
113
     * @param array    $parameters An array of parameters to pass to the view
114
     * @param Response $response   A response instance
115
     *
116
     * @return Response A Response instance
117
     */
118
    public function renderWithExtraParams($view, array $parameters = [], Response $response = null)
119
    {
120
        if (!$this->isXmlHttpRequest()) {
121
            $parameters['breadcrumbs_builder'] = $this->get('sonata.admin.breadcrumbs_builder');
0 ignored issues
show
Documentation Bug introduced by
The method get 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...
122
        }
123
        $parameters['admin'] = isset($parameters['admin']) ?
124
            $parameters['admin'] :
125
            $this->admin;
126
127
        $parameters['base_template'] = isset($parameters['base_template']) ?
128
            $parameters['base_template'] :
129
            $this->getBaseTemplate();
130
131
        $parameters['admin_pool'] = $this->get('sonata.admin.pool');
0 ignored issues
show
Documentation Bug introduced by
The method get 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...
132
133
        //NEXT_MAJOR: Remove method alias and use $this->render() directly.
134
        return $this->originalRender($view, $parameters, $response);
135
    }
136
137
    /**
138
     * List action.
139
     *
140
     * @return Response
141
     *
142
     * @throws AccessDeniedException If access is not granted
143
     */
144
    public function listAction()
145
    {
146
        $request = $this->getRequest();
147
148
        $this->admin->checkAccess('list');
149
150
        $preResponse = $this->preList($request);
151
        if (null !== $preResponse) {
152
            return $preResponse;
153
        }
154
155
        if ($listMode = $request->get('_list_mode')) {
156
            $this->admin->setListMode($listMode);
157
        }
158
159
        $datagrid = $this->admin->getDatagrid();
160
        $formView = $datagrid->getForm()->createView();
161
162
        // set the theme for the current Admin Form
163
        $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...
164
165
        return $this->renderWithExtraParams($this->admin->getTemplate('list'), [
166
            'action' => 'list',
167
            'form' => $formView,
168
            'datagrid' => $datagrid,
169
            'csrf_token' => $this->getCsrfToken('sonata.batch'),
170
            'export_formats' => $this->has('sonata.admin.admin_exporter') ?
0 ignored issues
show
Documentation Bug introduced by
The method has 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...
171
                $this->get('sonata.admin.admin_exporter')->getAvailableFormats($this->admin) :
0 ignored issues
show
Documentation Bug introduced by
The method get 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...
172
                $this->admin->getExportFormats(),
173
        ], null);
174
    }
175
176
    /**
177
     * Execute a batch delete.
178
     *
179
     * @param ProxyQueryInterface $query
180
     *
181
     * @return RedirectResponse
182
     *
183
     * @throws AccessDeniedException If access is not granted
184
     */
185
    public function batchActionDelete(ProxyQueryInterface $query)
186
    {
187
        $this->admin->checkAccess('batchDelete');
188
189
        $modelManager = $this->admin->getModelManager();
190
191
        try {
192
            $modelManager->batchDelete($this->admin->getClass(), $query);
193
            $this->addFlash(
194
                'sonata_flash_success',
195
                $this->trans('flash_batch_delete_success', [], 'SonataAdminBundle')
196
            );
197
        } catch (ModelManagerException $e) {
198
            $this->handleModelManagerException($e);
199
            $this->addFlash(
200
                'sonata_flash_error',
201
                $this->trans('flash_batch_delete_error', [], 'SonataAdminBundle')
202
            );
203
        }
204
205
        return $this->redirectToList();
206
    }
207
208
    /**
209
     * Delete action.
210
     *
211
     * @param int|string|null $id
212
     *
213
     * @return Response|RedirectResponse
214
     *
215
     * @throws NotFoundHttpException If the object does not exist
216
     * @throws AccessDeniedException If access is not granted
217
     */
218
    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...
219
    {
220
        $request = $this->getRequest();
221
        $id = $request->get($this->admin->getIdParameter());
222
        $object = $this->admin->getObject($id);
223
224
        if (!$object) {
225
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
226
        }
227
228
        $this->admin->checkAccess('delete', $object);
229
230
        $preResponse = $this->preDelete($request, $object);
231
        if (null !== $preResponse) {
232
            return $preResponse;
233
        }
234
235
        if ('DELETE' == $this->getRestMethod()) {
236
            // check the csrf token
237
            $this->validateCsrfToken('sonata.delete');
238
239
            $objectName = $this->admin->toString($object);
240
241
            try {
242
                $this->admin->delete($object);
243
244
                if ($this->isXmlHttpRequest()) {
245
                    return $this->renderJson(['result' => 'ok'], 200, []);
246
                }
247
248
                $this->addFlash(
249
                    'sonata_flash_success',
250
                    $this->trans(
251
                        'flash_delete_success',
252
                        ['%name%' => $this->escapeHtml($objectName)],
253
                        'SonataAdminBundle'
254
                    )
255
                );
256
            } catch (ModelManagerException $e) {
257
                $this->handleModelManagerException($e);
258
259
                if ($this->isXmlHttpRequest()) {
260
                    return $this->renderJson(['result' => 'error'], 200, []);
261
                }
262
263
                $this->addFlash(
264
                    'sonata_flash_error',
265
                    $this->trans(
266
                        'flash_delete_error',
267
                        ['%name%' => $this->escapeHtml($objectName)],
268
                        'SonataAdminBundle'
269
                    )
270
                );
271
            }
272
273
            return $this->redirectTo($object);
274
        }
275
276
        return $this->renderWithExtraParams($this->admin->getTemplate('delete'), [
277
            'object' => $object,
278
            'action' => 'delete',
279
            'csrf_token' => $this->getCsrfToken('sonata.delete'),
280
        ], null);
281
    }
282
283
    /**
284
     * Edit action.
285
     *
286
     * @param int|string|null $id
287
     *
288
     * @return Response|RedirectResponse
289
     *
290
     * @throws NotFoundHttpException If the object does not exist
291
     * @throws AccessDeniedException If access is not granted
292
     */
293
    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...
294
    {
295
        $request = $this->getRequest();
296
        // the key used to lookup the template
297
        $templateKey = 'edit';
298
299
        $id = $request->get($this->admin->getIdParameter());
300
        $existingObject = $this->admin->getObject($id);
301
302
        if (!$existingObject) {
303
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
304
        }
305
306
        $this->admin->checkAccess('edit', $existingObject);
307
308
        $preResponse = $this->preEdit($request, $existingObject);
309
        if (null !== $preResponse) {
310
            return $preResponse;
311
        }
312
313
        $this->admin->setSubject($existingObject);
314
        $objectId = $this->admin->getNormalizedIdentifier($existingObject);
315
316
        /** @var $form Form */
317
        $form = $this->admin->getForm();
318
        $form->setData($existingObject);
319
        $form->handleRequest($request);
320
321
        if ($form->isSubmitted()) {
322
            $this->admin->preValidate($existingObject);
323
            $isFormValid = $form->isValid();
324
325
            // persist if the form was valid and if in preview mode the preview was approved
326
            if ($isFormValid && (!$this->isInPreviewMode() || $this->isPreviewApproved())) {
327
                $submittedObject = $form->getData();
328
                $this->admin->setSubject($submittedObject);
329
330
                try {
331
                    $existingObject = $this->admin->update($submittedObject);
332
333
                    if ($this->isXmlHttpRequest()) {
334
                        return $this->renderJson([
335
                            'result' => 'ok',
336
                            'objectId' => $objectId,
337
                            'objectName' => $this->escapeHtml($this->admin->toString($existingObject)),
338
                        ], 200, []);
339
                    }
340
341
                    $this->addFlash(
342
                        'sonata_flash_success',
343
                        $this->trans(
344
                            'flash_edit_success',
345
                            ['%name%' => $this->escapeHtml($this->admin->toString($existingObject))],
346
                            'SonataAdminBundle'
347
                        )
348
                    );
349
350
                    // redirect to edit mode
351
                    return $this->redirectTo($existingObject);
352
                } catch (ModelManagerException $e) {
353
                    $this->handleModelManagerException($e);
354
355
                    $isFormValid = false;
356
                } catch (LockException $e) {
357
                    $this->addFlash('sonata_flash_error', $this->trans('flash_lock_error', [
358
                        '%name%' => $this->escapeHtml($this->admin->toString($existingObject)),
359
                        '%link_start%' => '<a href="'.$this->admin->generateObjectUrl('edit', $existingObject).'">',
360
                        '%link_end%' => '</a>',
361
                    ], 'SonataAdminBundle'));
362
                }
363
            }
364
365
            // show an error message if the form failed validation
366
            if (!$isFormValid) {
367
                if (!$this->isXmlHttpRequest()) {
368
                    $this->addFlash(
369
                        'sonata_flash_error',
370
                        $this->trans(
371
                            'flash_edit_error',
372
                            ['%name%' => $this->escapeHtml($this->admin->toString($existingObject))],
373
                            'SonataAdminBundle'
374
                        )
375
                    );
376
                }
377
            } elseif ($this->isPreviewRequested()) {
378
                // enable the preview template if the form was valid and preview was requested
379
                $templateKey = 'preview';
380
                $this->admin->getShow();
381
            }
382
        }
383
384
        $formView = $form->createView();
385
        // set the theme for the current Admin Form
386
        $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...
387
388
        return $this->renderWithExtraParams($this->admin->getTemplate($templateKey), [
389
            'action' => 'edit',
390
            'form' => $formView,
391
            'object' => $existingObject,
392
            'objectId' => $objectId,
393
        ], null);
394
    }
395
396
    /**
397
     * Batch action.
398
     *
399
     * @return Response|RedirectResponse
400
     *
401
     * @throws NotFoundHttpException If the HTTP method is not POST
402
     * @throws \RuntimeException     If the batch action is not defined
403
     */
404
    public function batchAction()
405
    {
406
        $request = $this->getRequest();
407
        $restMethod = $this->getRestMethod();
408
409
        if ('POST' !== $restMethod) {
410
            throw $this->createNotFoundException(sprintf('Invalid request type "%s", POST expected', $restMethod));
411
        }
412
413
        // check the csrf token
414
        $this->validateCsrfToken('sonata.batch');
415
416
        $confirmation = $request->get('confirmation', false);
417
418
        if ($data = json_decode($request->get('data'), true)) {
419
            $action = $data['action'];
420
            $idx = $data['idx'];
421
            $allElements = $data['all_elements'];
422
            $request->request->replace(array_merge($request->request->all(), $data));
423
        } else {
424
            $request->request->set('idx', $request->get('idx', []));
425
            $request->request->set('all_elements', $request->get('all_elements', false));
426
427
            $action = $request->get('action');
428
            $idx = $request->get('idx');
429
            $allElements = $request->get('all_elements');
430
            $data = $request->request->all();
431
432
            unset($data['_sonata_csrf_token']);
433
        }
434
435
        $batchActions = $this->admin->getBatchActions();
436
        if (!array_key_exists($action, $batchActions)) {
437
            throw new \RuntimeException(sprintf('The `%s` batch action is not defined', $action));
438
        }
439
440
        $camelizedAction = Inflector::classify($action);
441
        $isRelevantAction = sprintf('batchAction%sIsRelevant', ucfirst($camelizedAction));
442
443
        if (method_exists($this, $isRelevantAction)) {
444
            $nonRelevantMessage = call_user_func([$this, $isRelevantAction], $idx, $allElements, $request);
445
        } else {
446
            $nonRelevantMessage = 0 != count($idx) || $allElements; // at least one item is selected
447
        }
448
449
        if (!$nonRelevantMessage) { // default non relevant message (if false of null)
450
            $nonRelevantMessage = 'flash_batch_empty';
451
        }
452
453
        $datagrid = $this->admin->getDatagrid();
454
        $datagrid->buildPager();
455
456
        if (true !== $nonRelevantMessage) {
457
            $this->addFlash(
458
                'sonata_flash_info',
459
                $this->trans($nonRelevantMessage, [], 'SonataAdminBundle')
460
            );
461
462
            return $this->redirectToList();
463
        }
464
465
        $askConfirmation = isset($batchActions[$action]['ask_confirmation']) ?
466
            $batchActions[$action]['ask_confirmation'] :
467
            true;
468
469
        if ($askConfirmation && 'ok' != $confirmation) {
470
            $actionLabel = $batchActions[$action]['label'];
471
            $batchTranslationDomain = isset($batchActions[$action]['translation_domain']) ?
472
                $batchActions[$action]['translation_domain'] :
473
                $this->admin->getTranslationDomain();
474
475
            $formView = $datagrid->getForm()->createView();
476
            $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...
477
478
            return $this->renderWithExtraParams($this->admin->getTemplate('batch_confirmation'), [
479
                'action' => 'list',
480
                'action_label' => $actionLabel,
481
                'batch_translation_domain' => $batchTranslationDomain,
482
                'datagrid' => $datagrid,
483
                'form' => $formView,
484
                'data' => $data,
485
                'csrf_token' => $this->getCsrfToken('sonata.batch'),
486
            ], null);
487
        }
488
489
        // execute the action, batchActionXxxxx
490
        $finalAction = sprintf('batchAction%s', $camelizedAction);
491
        if (!method_exists($this, $finalAction)) {
492
            throw new \RuntimeException(sprintf('A `%s::%s` method must be callable', get_class($this), $finalAction));
493
        }
494
495
        $query = $datagrid->getQuery();
496
497
        $query->setFirstResult(null);
498
        $query->setMaxResults(null);
499
500
        $this->admin->preBatchAction($action, $query, $idx, $allElements);
501
502
        if (count($idx) > 0) {
503
            $this->admin->getModelManager()->addIdentifiersToQuery($this->admin->getClass(), $query, $idx);
504
        } elseif (!$allElements) {
505
            $this->addFlash(
506
                'sonata_flash_info',
507
                $this->trans('flash_batch_no_elements_processed', [], 'SonataAdminBundle')
508
            );
509
510
            return $this->redirectToList();
511
        }
512
513
        return call_user_func([$this, $finalAction], $query, $request);
514
    }
515
516
    /**
517
     * Create action.
518
     *
519
     * @return Response
520
     *
521
     * @throws AccessDeniedException If access is not granted
522
     */
523
    public function createAction()
524
    {
525
        $request = $this->getRequest();
526
        // the key used to lookup the template
527
        $templateKey = 'edit';
528
529
        $this->admin->checkAccess('create');
530
531
        $class = new \ReflectionClass($this->admin->hasActiveSubClass() ? $this->admin->getActiveSubClass() : $this->admin->getClass());
532
533
        if ($class->isAbstract()) {
534
            return $this->renderWithExtraParams(
535
                'SonataAdminBundle:CRUD:select_subclass.html.twig',
536
                [
537
                    'base_template' => $this->getBaseTemplate(),
538
                    'admin' => $this->admin,
539
                    'action' => 'create',
540
                ],
541
                null
542
            );
543
        }
544
545
        $newObject = $this->admin->getNewInstance();
546
547
        $preResponse = $this->preCreate($request, $newObject);
548
        if (null !== $preResponse) {
549
            return $preResponse;
550
        }
551
552
        $this->admin->setSubject($newObject);
553
554
        /** @var $form \Symfony\Component\Form\Form */
555
        $form = $this->admin->getForm();
556
        $form->setData($newObject);
557
        $form->handleRequest($request);
558
559
        if ($form->isSubmitted()) {
560
            $this->admin->preValidate($newObject);
561
            $isFormValid = $form->isValid();
562
563
            // persist if the form was valid and if in preview mode the preview was approved
564
            if ($isFormValid && (!$this->isInPreviewMode() || $this->isPreviewApproved())) {
565
                $submittedObject = $form->getData();
566
                $this->admin->setSubject($submittedObject);
567
                $this->admin->checkAccess('create', $submittedObject);
568
569
                try {
570
                    $newObject = $this->admin->create($submittedObject);
571
572
                    if ($this->isXmlHttpRequest()) {
573
                        return $this->renderJson([
574
                            'result' => 'ok',
575
                            'objectId' => $this->admin->getNormalizedIdentifier($newObject),
576
                        ], 200, []);
577
                    }
578
579
                    $this->addFlash(
580
                        'sonata_flash_success',
581
                        $this->trans(
582
                            'flash_create_success',
583
                            ['%name%' => $this->escapeHtml($this->admin->toString($newObject))],
584
                            'SonataAdminBundle'
585
                        )
586
                    );
587
588
                    // redirect to edit mode
589
                    return $this->redirectTo($newObject);
590
                } catch (ModelManagerException $e) {
591
                    $this->handleModelManagerException($e);
592
593
                    $isFormValid = false;
594
                }
595
            }
596
597
            // show an error message if the form failed validation
598
            if (!$isFormValid) {
599
                if (!$this->isXmlHttpRequest()) {
600
                    $this->addFlash(
601
                        'sonata_flash_error',
602
                        $this->trans(
603
                            'flash_create_error',
604
                            ['%name%' => $this->escapeHtml($this->admin->toString($newObject))],
605
                            'SonataAdminBundle'
606
                        )
607
                    );
608
                }
609
            } elseif ($this->isPreviewRequested()) {
610
                // pick the preview template if the form was valid and preview was requested
611
                $templateKey = 'preview';
612
                $this->admin->getShow();
613
            }
614
        }
615
616
        $formView = $form->createView();
617
        // set the theme for the current Admin Form
618
        $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...
619
620
        return $this->renderWithExtraParams($this->admin->getTemplate($templateKey), [
621
            'action' => 'create',
622
            'form' => $formView,
623
            'object' => $newObject,
624
            'objectId' => null,
625
        ], null);
626
    }
627
628
    /**
629
     * Show action.
630
     *
631
     * @param int|string|null $id
632
     *
633
     * @return Response
634
     *
635
     * @throws NotFoundHttpException If the object does not exist
636
     * @throws AccessDeniedException If access is not granted
637
     */
638
    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...
639
    {
640
        $request = $this->getRequest();
641
        $id = $request->get($this->admin->getIdParameter());
642
643
        $object = $this->admin->getObject($id);
644
645
        if (!$object) {
646
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
647
        }
648
649
        $this->admin->checkAccess('show', $object);
650
651
        $preResponse = $this->preShow($request, $object);
652
        if (null !== $preResponse) {
653
            return $preResponse;
654
        }
655
656
        $this->admin->setSubject($object);
657
658
        return $this->renderWithExtraParams($this->admin->getTemplate('show'), [
659
            'action' => 'show',
660
            'object' => $object,
661
            'elements' => $this->admin->getShow(),
662
        ], null);
663
    }
664
665
    /**
666
     * Show history revisions for object.
667
     *
668
     * @param int|string|null $id
669
     *
670
     * @return Response
671
     *
672
     * @throws AccessDeniedException If access is not granted
673
     * @throws NotFoundHttpException If the object does not exist or the audit reader is not available
674
     */
675
    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...
676
    {
677
        $request = $this->getRequest();
678
        $id = $request->get($this->admin->getIdParameter());
679
680
        $object = $this->admin->getObject($id);
681
682
        if (!$object) {
683
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
684
        }
685
686
        $this->admin->checkAccess('history', $object);
687
688
        $manager = $this->get('sonata.admin.audit.manager');
0 ignored issues
show
Documentation Bug introduced by
The method get 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...
689
690
        if (!$manager->hasReader($this->admin->getClass())) {
691
            throw $this->createNotFoundException(
692
                sprintf(
693
                    'unable to find the audit reader for class : %s',
694
                    $this->admin->getClass()
695
                )
696
            );
697
        }
698
699
        $reader = $manager->getReader($this->admin->getClass());
700
701
        $revisions = $reader->findRevisions($this->admin->getClass(), $id);
702
703
        return $this->renderWithExtraParams($this->admin->getTemplate('history'), [
704
            'action' => 'history',
705
            'object' => $object,
706
            'revisions' => $revisions,
707
            'currentRevision' => $revisions ? current($revisions) : false,
708
        ], null);
709
    }
710
711
    /**
712
     * View history revision of object.
713
     *
714
     * @param int|string|null $id
715
     * @param string|null     $revision
716
     *
717
     * @return Response
718
     *
719
     * @throws AccessDeniedException If access is not granted
720
     * @throws NotFoundHttpException If the object or revision does not exist or the audit reader is not available
721
     */
722
    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...
723
    {
724
        $request = $this->getRequest();
725
        $id = $request->get($this->admin->getIdParameter());
726
727
        $object = $this->admin->getObject($id);
728
729
        if (!$object) {
730
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
731
        }
732
733
        $this->admin->checkAccess('historyViewRevision', $object);
734
735
        $manager = $this->get('sonata.admin.audit.manager');
0 ignored issues
show
Documentation Bug introduced by
The method get 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...
736
737
        if (!$manager->hasReader($this->admin->getClass())) {
738
            throw $this->createNotFoundException(
739
                sprintf(
740
                    'unable to find the audit reader for class : %s',
741
                    $this->admin->getClass()
742
                )
743
            );
744
        }
745
746
        $reader = $manager->getReader($this->admin->getClass());
747
748
        // retrieve the revisioned object
749
        $object = $reader->find($this->admin->getClass(), $id, $revision);
750
751
        if (!$object) {
752
            throw $this->createNotFoundException(
753
                sprintf(
754
                    'unable to find the targeted object `%s` from the revision `%s` with classname : `%s`',
755
                    $id,
756
                    $revision,
757
                    $this->admin->getClass()
758
                )
759
            );
760
        }
761
762
        $this->admin->setSubject($object);
763
764
        return $this->renderWithExtraParams($this->admin->getTemplate('show'), [
765
            'action' => 'show',
766
            'object' => $object,
767
            'elements' => $this->admin->getShow(),
768
        ], null);
769
    }
770
771
    /**
772
     * Compare history revisions of object.
773
     *
774
     * @param int|string|null $id
775
     * @param int|string|null $base_revision
776
     * @param int|string|null $compare_revision
777
     *
778
     * @return Response
779
     *
780
     * @throws AccessDeniedException If access is not granted
781
     * @throws NotFoundHttpException If the object or revision does not exist or the audit reader is not available
782
     */
783
    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...
784
    {
785
        $request = $this->getRequest();
786
787
        $this->admin->checkAccess('historyCompareRevisions');
788
789
        $id = $request->get($this->admin->getIdParameter());
790
791
        $object = $this->admin->getObject($id);
792
793
        if (!$object) {
794
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
795
        }
796
797
        $manager = $this->get('sonata.admin.audit.manager');
0 ignored issues
show
Documentation Bug introduced by
The method get 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...
798
799
        if (!$manager->hasReader($this->admin->getClass())) {
800
            throw $this->createNotFoundException(
801
                sprintf(
802
                    'unable to find the audit reader for class : %s',
803
                    $this->admin->getClass()
804
                )
805
            );
806
        }
807
808
        $reader = $manager->getReader($this->admin->getClass());
809
810
        // retrieve the base revision
811
        $base_object = $reader->find($this->admin->getClass(), $id, $base_revision);
812
        if (!$base_object) {
813
            throw $this->createNotFoundException(
814
                sprintf(
815
                    'unable to find the targeted object `%s` from the revision `%s` with classname : `%s`',
816
                    $id,
817
                    $base_revision,
818
                    $this->admin->getClass()
819
                )
820
            );
821
        }
822
823
        // retrieve the compare revision
824
        $compare_object = $reader->find($this->admin->getClass(), $id, $compare_revision);
825
        if (!$compare_object) {
826
            throw $this->createNotFoundException(
827
                sprintf(
828
                    'unable to find the targeted object `%s` from the revision `%s` with classname : `%s`',
829
                    $id,
830
                    $compare_revision,
831
                    $this->admin->getClass()
832
                )
833
            );
834
        }
835
836
        $this->admin->setSubject($base_object);
837
838
        return $this->renderWithExtraParams($this->admin->getTemplate('show_compare'), [
839
            'action' => 'show',
840
            'object' => $base_object,
841
            'object_compare' => $compare_object,
842
            'elements' => $this->admin->getShow(),
843
        ], null);
844
    }
845
846
    /**
847
     * Export data to specified format.
848
     *
849
     * @param Request $request
850
     *
851
     * @return Response
852
     *
853
     * @throws AccessDeniedException If access is not granted
854
     * @throws \RuntimeException     If the export format is invalid
855
     */
856
    public function exportAction(Request $request)
857
    {
858
        $this->admin->checkAccess('export');
859
860
        $format = $request->get('format');
861
862
        // NEXT_MAJOR: remove the check
863
        if (!$this->has('sonata.admin.admin_exporter')) {
0 ignored issues
show
Documentation Bug introduced by
The method has 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...
864
            @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...
865
                'Not registering the exporter bundle is deprecated since version 3.14.'
866
                .' You must register it to be able to use the export action in 4.0.',
867
                E_USER_DEPRECATED
868
            );
869
            $allowedExportFormats = (array) $this->admin->getExportFormats();
870
871
            $class = $this->admin->getClass();
872
            $filename = sprintf(
873
                'export_%s_%s.%s',
874
                strtolower(substr($class, strripos($class, '\\') + 1)),
875
                date('Y_m_d_H_i_s', strtotime('now')),
876
                $format
877
            );
878
            $exporter = $this->get('sonata.admin.exporter');
0 ignored issues
show
Documentation Bug introduced by
The method get 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...
879
        } else {
880
            $adminExporter = $this->get('sonata.admin.admin_exporter');
0 ignored issues
show
Documentation Bug introduced by
The method get 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...
881
            $allowedExportFormats = $adminExporter->getAvailableFormats($this->admin);
882
            $filename = $adminExporter->getExportFilename($this->admin, $format);
883
            $exporter = $this->get('sonata.exporter.exporter');
0 ignored issues
show
Documentation Bug introduced by
The method get 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...
884
        }
885
886
        if (!in_array($format, $allowedExportFormats)) {
887
            throw new \RuntimeException(
888
                sprintf(
889
                    'Export in format `%s` is not allowed for class: `%s`. Allowed formats are: `%s`',
890
                    $format,
891
                    $this->admin->getClass(),
892
                    implode(', ', $allowedExportFormats)
893
                )
894
            );
895
        }
896
897
        return $exporter->getResponse(
898
            $format,
899
            $filename,
900
            $this->admin->getDataSourceIterator()
901
        );
902
    }
903
904
    /**
905
     * Returns the Response object associated to the acl action.
906
     *
907
     * @param int|string|null $id
908
     *
909
     * @return Response|RedirectResponse
910
     *
911
     * @throws AccessDeniedException If access is not granted
912
     * @throws NotFoundHttpException If the object does not exist or the ACL is not enabled
913
     */
914
    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...
915
    {
916
        $request = $this->getRequest();
917
918
        if (!$this->admin->isAclEnabled()) {
919
            throw $this->createNotFoundException('ACL are not enabled for this admin');
920
        }
921
922
        $id = $request->get($this->admin->getIdParameter());
923
924
        $object = $this->admin->getObject($id);
925
926
        if (!$object) {
927
            throw $this->createNotFoundException(sprintf('unable to find the object with id: %s', $id));
928
        }
929
930
        $this->admin->checkAccess('acl', $object);
931
932
        $this->admin->setSubject($object);
933
        $aclUsers = $this->getAclUsers();
934
        $aclRoles = $this->getAclRoles();
935
936
        $adminObjectAclManipulator = $this->get('sonata.admin.object.manipulator.acl.admin');
0 ignored issues
show
Documentation Bug introduced by
The method get 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...
937
        $adminObjectAclData = new AdminObjectAclData(
938
            $this->admin,
939
            $object,
940
            $aclUsers,
941
            $adminObjectAclManipulator->getMaskBuilderClass(),
942
            $aclRoles
943
        );
944
945
        $aclUsersForm = $adminObjectAclManipulator->createAclUsersForm($adminObjectAclData);
946
        $aclRolesForm = $adminObjectAclManipulator->createAclRolesForm($adminObjectAclData);
947
948
        if ('POST' === $request->getMethod()) {
949
            if ($request->request->has(AdminObjectAclManipulator::ACL_USERS_FORM_NAME)) {
950
                $form = $aclUsersForm;
951
                $updateMethod = 'updateAclUsers';
952
            } elseif ($request->request->has(AdminObjectAclManipulator::ACL_ROLES_FORM_NAME)) {
953
                $form = $aclRolesForm;
954
                $updateMethod = 'updateAclRoles';
955
            }
956
957
            if (isset($form)) {
958
                $form->handleRequest($request);
959
960
                if ($form->isValid()) {
961
                    $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...
962
                    $this->addFlash(
963
                        'sonata_flash_success',
964
                        $this->trans('flash_acl_edit_success', [], 'SonataAdminBundle')
965
                    );
966
967
                    return new RedirectResponse($this->admin->generateObjectUrl('acl', $object));
968
                }
969
            }
970
        }
971
972
        return $this->renderWithExtraParams($this->admin->getTemplate('acl'), [
973
            'action' => 'acl',
974
            'permissions' => $adminObjectAclData->getUserPermissions(),
975
            'object' => $object,
976
            'users' => $aclUsers,
977
            'roles' => $aclRoles,
978
            'aclUsersForm' => $aclUsersForm->createView(),
979
            'aclRolesForm' => $aclRolesForm->createView(),
980
        ], null);
981
    }
982
983
    /**
984
     * @return Request
985
     */
986
    public function getRequest()
987
    {
988
        return $this->container->get('request_stack')->getCurrentRequest();
989
    }
990
991
    /**
992
     * Gets a container configuration parameter by its name.
993
     *
994
     * @param string $name The parameter name
995
     *
996
     * @return mixed
997
     */
998
    protected function getParameter($name)
999
    {
1000
        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\DependencyInjection\Container, Symfony\Component\Depend...ection\ContainerBuilder, Symfony\Component\Depend...tainers\CustomContainer, Symfony\Component\Depend...ProjectServiceContainer, Symfony\Component\Depend...ProjectServiceContainer.

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...
1001
    }
1002
1003
    /**
1004
     * Render JSON.
1005
     *
1006
     * @param mixed $data
1007
     * @param int   $status
1008
     * @param array $headers
1009
     *
1010
     * @return Response with json encoded data
1011
     */
1012
    protected function renderJson($data, $status = 200, $headers = [])
1013
    {
1014
        return new JsonResponse($data, $status, $headers);
1015
    }
1016
1017
    /**
1018
     * Returns true if the request is a XMLHttpRequest.
1019
     *
1020
     * @return bool True if the request is an XMLHttpRequest, false otherwise
1021
     */
1022
    protected function isXmlHttpRequest()
1023
    {
1024
        $request = $this->getRequest();
1025
1026
        return $request->isXmlHttpRequest() || $request->get('_xml_http_request');
1027
    }
1028
1029
    /**
1030
     * Returns the correct RESTful verb, given either by the request itself or
1031
     * via the "_method" parameter.
1032
     *
1033
     * @return string HTTP method, either
1034
     */
1035
    protected function getRestMethod()
1036
    {
1037
        $request = $this->getRequest();
1038
1039
        if (Request::getHttpMethodParameterOverride() || !$request->request->has('_method')) {
1040
            return $request->getMethod();
1041
        }
1042
1043
        return $request->request->get('_method');
1044
    }
1045
1046
    /**
1047
     * Contextualize the admin class depends on the current request.
1048
     *
1049
     * @throws \RuntimeException
1050
     */
1051
    protected function configure()
1052
    {
1053
        $request = $this->getRequest();
1054
1055
        $adminCode = $request->get('_sonata_admin');
1056
1057
        if (!$adminCode) {
1058
            throw new \RuntimeException(sprintf(
1059
                'There is no `_sonata_admin` defined for the controller `%s` and the current route `%s`',
1060
                get_class($this),
1061
                $request->get('_route')
1062
            ));
1063
        }
1064
1065
        $this->admin = $this->container->get('sonata.admin.pool')->getAdminByAdminCode($adminCode);
1066
1067
        if (!$this->admin) {
1068
            throw new \RuntimeException(sprintf(
1069
                'Unable to find the admin class related to the current controller (%s)',
1070
                get_class($this)
1071
            ));
1072
        }
1073
1074
        $rootAdmin = $this->admin;
1075
1076
        while ($rootAdmin->isChild()) {
1077
            $rootAdmin->setCurrentChild(true);
1078
            $rootAdmin = $rootAdmin->getParent();
1079
        }
1080
1081
        $rootAdmin->setRequest($request);
1082
1083
        if ($request->get('uniqid')) {
1084
            $this->admin->setUniqid($request->get('uniqid'));
1085
        }
1086
    }
1087
1088
    /**
1089
     * Proxy for the logger service of the container.
1090
     * If no such service is found, a NullLogger is returned.
1091
     *
1092
     * @return LoggerInterface
1093
     */
1094
    protected function getLogger()
1095
    {
1096
        if ($this->container->has('logger')) {
1097
            return $this->container->get('logger');
1098
        }
1099
1100
        return new NullLogger();
1101
    }
1102
1103
    /**
1104
     * Returns the base template name.
1105
     *
1106
     * @return string The template name
1107
     */
1108
    protected function getBaseTemplate()
1109
    {
1110
        if ($this->isXmlHttpRequest()) {
1111
            return $this->admin->getTemplate('ajax');
1112
        }
1113
1114
        return $this->admin->getTemplate('layout');
1115
    }
1116
1117
    /**
1118
     * @param \Exception $e
1119
     *
1120
     * @throws \Exception
1121
     */
1122
    protected function handleModelManagerException(\Exception $e)
1123
    {
1124
        if ($this->get('kernel')->isDebug()) {
0 ignored issues
show
Documentation Bug introduced by
The method get 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...
1125
            throw $e;
1126
        }
1127
1128
        $context = ['exception' => $e];
1129
        if ($e->getPrevious()) {
1130
            $context['previous_exception_message'] = $e->getPrevious()->getMessage();
1131
        }
1132
        $this->getLogger()->error($e->getMessage(), $context);
1133
    }
1134
1135
    /**
1136
     * Redirect the user depend on this choice.
1137
     *
1138
     * @param object $object
1139
     *
1140
     * @return RedirectResponse
1141
     */
1142
    protected function redirectTo($object)
1143
    {
1144
        $request = $this->getRequest();
1145
1146
        $url = false;
1147
1148
        if (null !== $request->get('btn_update_and_list')) {
1149
            return $this->redirectToList();
1150
        }
1151
        if (null !== $request->get('btn_create_and_list')) {
1152
            return $this->redirectToList();
1153
        }
1154
1155
        if (null !== $request->get('btn_create_and_create')) {
1156
            $params = [];
1157
            if ($this->admin->hasActiveSubClass()) {
1158
                $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...
1159
            }
1160
            $url = $this->admin->generateUrl('create', $params);
1161
        }
1162
1163
        if ('DELETE' === $this->getRestMethod()) {
1164
            return $this->redirectToList();
1165
        }
1166
1167
        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...
1168
            foreach (['edit', 'show'] as $route) {
1169
                if ($this->admin->hasRoute($route) && $this->admin->hasAccess($route, $object)) {
1170
                    $url = $this->admin->generateObjectUrl($route, $object);
1171
1172
                    break;
1173
                }
1174
            }
1175
        }
1176
1177
        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...
1178
            return $this->redirectToList();
1179
        }
1180
1181
        return new RedirectResponse($url);
1182
    }
1183
1184
    /**
1185
     * Redirects the user to the list view.
1186
     *
1187
     * @return RedirectResponse
1188
     */
1189
    final protected function redirectToList()
1190
    {
1191
        $parameters = [];
1192
1193
        if ($filter = $this->admin->getFilterParameters()) {
1194
            $parameters['filter'] = $filter;
1195
        }
1196
1197
        return $this->redirect($this->admin->generateUrl('list', $parameters));
1198
    }
1199
1200
    /**
1201
     * Returns true if the preview is requested to be shown.
1202
     *
1203
     * @return bool
1204
     */
1205
    protected function isPreviewRequested()
1206
    {
1207
        $request = $this->getRequest();
1208
1209
        return null !== $request->get('btn_preview');
1210
    }
1211
1212
    /**
1213
     * Returns true if the preview has been approved.
1214
     *
1215
     * @return bool
1216
     */
1217
    protected function isPreviewApproved()
1218
    {
1219
        $request = $this->getRequest();
1220
1221
        return null !== $request->get('btn_preview_approve');
1222
    }
1223
1224
    /**
1225
     * Returns true if the request is in the preview workflow.
1226
     *
1227
     * That means either a preview is requested or the preview has already been shown
1228
     * and it got approved/declined.
1229
     *
1230
     * @return bool
1231
     */
1232
    protected function isInPreviewMode()
1233
    {
1234
        return $this->admin->supportsPreviewMode()
1235
        && ($this->isPreviewRequested()
1236
            || $this->isPreviewApproved()
1237
            || $this->isPreviewDeclined());
1238
    }
1239
1240
    /**
1241
     * Returns true if the preview has been declined.
1242
     *
1243
     * @return bool
1244
     */
1245
    protected function isPreviewDeclined()
1246
    {
1247
        $request = $this->getRequest();
1248
1249
        return null !== $request->get('btn_preview_decline');
1250
    }
1251
1252
    /**
1253
     * Gets ACL users.
1254
     *
1255
     * @return \Traversable
1256
     */
1257
    protected function getAclUsers()
1258
    {
1259
        $aclUsers = [];
1260
1261
        $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\DependencyInjection\Container, Symfony\Component\Depend...ection\ContainerBuilder, Symfony\Component\Depend...tainers\CustomContainer, Symfony\Component\Depend...ProjectServiceContainer, Symfony\Component\Depend...ProjectServiceContainer.

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...
1262
        if (null !== $userManagerServiceName && $this->has($userManagerServiceName)) {
0 ignored issues
show
Documentation Bug introduced by
The method has 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...
1263
            $userManager = $this->get($userManagerServiceName);
0 ignored issues
show
Documentation Bug introduced by
The method get 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...
1264
1265
            if (method_exists($userManager, 'findUsers')) {
1266
                $aclUsers = $userManager->findUsers();
1267
            }
1268
        }
1269
1270
        return is_array($aclUsers) ? new \ArrayIterator($aclUsers) : $aclUsers;
1271
    }
1272
1273
    /**
1274
     * Gets ACL roles.
1275
     *
1276
     * @return \Traversable
1277
     */
1278
    protected function getAclRoles()
1279
    {
1280
        $aclRoles = [];
1281
        $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\DependencyInjection\Container, Symfony\Component\Depend...ection\ContainerBuilder, Symfony\Component\Depend...tainers\CustomContainer, Symfony\Component\Depend...ProjectServiceContainer, Symfony\Component\Depend...ProjectServiceContainer.

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...
1282
        $pool = $this->container->get('sonata.admin.pool');
1283
1284
        foreach ($pool->getAdminServiceIds() as $id) {
1285
            try {
1286
                $admin = $pool->getInstance($id);
1287
            } catch (\Exception $e) {
1288
                continue;
1289
            }
1290
1291
            $baseRole = $admin->getSecurityHandler()->getBaseRole($admin);
1292
            foreach ($admin->getSecurityInformation() as $role => $permissions) {
1293
                $role = sprintf($baseRole, $role);
1294
                $aclRoles[] = $role;
1295
            }
1296
        }
1297
1298
        foreach ($roleHierarchy as $name => $roles) {
1299
            $aclRoles[] = $name;
1300
            $aclRoles = array_merge($aclRoles, $roles);
1301
        }
1302
1303
        $aclRoles = array_unique($aclRoles);
1304
1305
        return is_array($aclRoles) ? new \ArrayIterator($aclRoles) : $aclRoles;
1306
    }
1307
1308
    /**
1309
     * Validate CSRF token for action without form.
1310
     *
1311
     * @param string $intention
1312
     *
1313
     * @throws HttpException
1314
     */
1315
    protected function validateCsrfToken($intention)
1316
    {
1317
        $request = $this->getRequest();
1318
        $token = $request->request->get('_sonata_csrf_token', false);
1319
1320
        if ($this->container->has('security.csrf.token_manager')) {
1321
            $valid = $this->container->get('security.csrf.token_manager')->isTokenValid(new CsrfToken($intention, $token));
1322
        } else {
1323
            return;
1324
        }
1325
1326
        if (!$valid) {
1327
            throw new HttpException(400, 'The csrf token is not valid, CSRF attack?');
1328
        }
1329
    }
1330
1331
    /**
1332
     * Escape string for html output.
1333
     *
1334
     * @param string $s
1335
     *
1336
     * @return string
1337
     */
1338
    protected function escapeHtml($s)
1339
    {
1340
        return htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
1341
    }
1342
1343
    /**
1344
     * Get CSRF token.
1345
     *
1346
     * @param string $intention
1347
     *
1348
     * @return string|false
1349
     */
1350
    protected function getCsrfToken($intention)
1351
    {
1352
        if ($this->container->has('security.csrf.token_manager')) {
1353
            return $this->container->get('security.csrf.token_manager')->getToken($intention)->getValue();
1354
        }
1355
1356
        return false;
1357
    }
1358
1359
    /**
1360
     * This method can be overloaded in your custom CRUD controller.
1361
     * It's called from createAction.
1362
     *
1363
     * @param Request $request
1364
     * @param mixed   $object
1365
     *
1366
     * @return Response|null
1367
     */
1368
    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...
1369
    {
1370
    }
1371
1372
    /**
1373
     * This method can be overloaded in your custom CRUD controller.
1374
     * It's called from editAction.
1375
     *
1376
     * @param Request $request
1377
     * @param mixed   $object
1378
     *
1379
     * @return Response|null
1380
     */
1381
    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...
1382
    {
1383
    }
1384
1385
    /**
1386
     * This method can be overloaded in your custom CRUD controller.
1387
     * It's called from deleteAction.
1388
     *
1389
     * @param Request $request
1390
     * @param mixed   $object
1391
     *
1392
     * @return Response|null
1393
     */
1394
    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...
1395
    {
1396
    }
1397
1398
    /**
1399
     * This method can be overloaded in your custom CRUD controller.
1400
     * It's called from showAction.
1401
     *
1402
     * @param Request $request
1403
     * @param mixed   $object
1404
     *
1405
     * @return Response|null
1406
     */
1407
    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...
1408
    {
1409
    }
1410
1411
    /**
1412
     * This method can be overloaded in your custom CRUD controller.
1413
     * It's called from listAction.
1414
     *
1415
     * @param Request $request
1416
     *
1417
     * @return Response|null
1418
     */
1419
    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...
1420
    {
1421
    }
1422
1423
    /**
1424
     * Translate a message id.
1425
     *
1426
     * @param string $id
1427
     * @param array  $parameters
1428
     * @param string $domain
1429
     * @param string $locale
1430
     *
1431
     * @return string translated string
1432
     */
1433
    final protected function trans($id, array $parameters = [], $domain = null, $locale = null)
1434
    {
1435
        $domain = $domain ?: $this->admin->getTranslationDomain();
1436
1437
        return $this->get('translator')->trans($id, $parameters, $domain, $locale);
0 ignored issues
show
Documentation Bug introduced by
The method get 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...
1438
    }
1439
1440
    /**
1441
     * Sets the admin form theme to form view. Used for compatibility between Symfony versions.
1442
     *
1443
     * @param FormView $formView
1444
     * @param string   $theme
1445
     */
1446
    private function setFormTheme(FormView $formView, $theme)
1447
    {
1448
        $twig = $this->get('twig');
0 ignored issues
show
Documentation Bug introduced by
The method get 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...
1449
1450
        // BC for Symfony < 3.2 where this runtime does not exists
1451
        if (!method_exists(AppVariable::class, 'getToken')) {
1452
            $twig->getExtension(FormExtension::class)->renderer->setTheme($formView, $theme);
1453
1454
            return;
1455
        }
1456
1457
        // BC for Symfony < 3.4 where runtime should be TwigRenderer
1458
        if (!method_exists(DebugCommand::class, 'getLoaderPaths')) {
1459
            $twig->getRuntime(TwigRenderer::class)->setTheme($formView, $theme);
1460
1461
            return;
1462
        }
1463
1464
        $twig->getRuntime(FormRenderer::class)->setTheme($formView, $theme);
1465
    }
1466
}
1467