Completed
Pull Request — 3.x (#5164)
by Marko
04:15
created

AbstractAdmin::buildDatagrid()   C

Complexity

Conditions 11
Paths 31

Size

Total Lines 63

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 63
rs 6.6606
c 0
b 0
f 0
cc 11
nc 31
nop 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
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\Admin;
13
14
use Doctrine\Common\Util\ClassUtils;
15
use Knp\Menu\FactoryInterface as MenuFactoryInterface;
16
use Knp\Menu\ItemInterface;
17
use Knp\Menu\ItemInterface as MenuItemInterface;
18
use Sonata\AdminBundle\Admin\AdminExtensionInterface as OldAdminExtensionInterface;
19
use Sonata\AdminBundle\Admin\Extension\AdminExtensionInterface as NewAdminExtensionInterface;
20
use Sonata\AdminBundle\Admin\Extension\AlterNewInstanceInterface;
21
use Sonata\AdminBundle\Admin\Extension\AlterObjectInterface;
22
use Sonata\AdminBundle\Admin\Extension\ConfigureActionButtonsInterface;
23
use Sonata\AdminBundle\Admin\Extension\ConfigureBatchActionsInterface;
24
use Sonata\AdminBundle\Admin\Extension\ConfigureDatagridFieldsInterface;
25
use Sonata\AdminBundle\Admin\Extension\ConfigureDefaultFilterValuesInterface;
26
use Sonata\AdminBundle\Admin\Extension\ConfigureExportFieldsInterface;
27
use Sonata\AdminBundle\Admin\Extension\ConfigureFormFieldsInterface;
28
use Sonata\AdminBundle\Admin\Extension\ConfigureListFieldsInterface;
29
use Sonata\AdminBundle\Admin\Extension\ConfigureQueryInterface;
30
use Sonata\AdminBundle\Admin\Extension\ConfigureRoutesInterface;
31
use Sonata\AdminBundle\Admin\Extension\ConfigureShowFieldsInterface;
32
use Sonata\AdminBundle\Admin\Extension\ConfigureTabMenuInterface;
33
use Sonata\AdminBundle\Admin\Extension\GetAccessMappingInterface;
34
use Sonata\AdminBundle\Admin\Extension\GetPersistentParametersInterface;
35
use Sonata\AdminBundle\Admin\Extension\PostPersistInterface;
36
use Sonata\AdminBundle\Admin\Extension\PostRemoveInterface;
37
use Sonata\AdminBundle\Admin\Extension\PostUpdateInterface;
38
use Sonata\AdminBundle\Admin\Extension\PrePersistInterface;
39
use Sonata\AdminBundle\Admin\Extension\PreRemoveInterface;
40
use Sonata\AdminBundle\Admin\Extension\PreUpdateInterface;
41
use Sonata\AdminBundle\Admin\Extension\ValidateInterface;
42
use Sonata\AdminBundle\Builder\DatagridBuilderInterface;
43
use Sonata\AdminBundle\Builder\FormContractorInterface;
44
use Sonata\AdminBundle\Builder\ListBuilderInterface;
45
use Sonata\AdminBundle\Builder\RouteBuilderInterface;
46
use Sonata\AdminBundle\Builder\ShowBuilderInterface;
47
use Sonata\AdminBundle\Datagrid\DatagridInterface;
48
use Sonata\AdminBundle\Datagrid\DatagridMapper;
49
use Sonata\AdminBundle\Datagrid\ListMapper;
50
use Sonata\AdminBundle\Datagrid\Pager;
51
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
52
use Sonata\AdminBundle\Filter\Persister\FilterPersisterInterface;
53
use Sonata\AdminBundle\Form\FormMapper;
54
use Sonata\AdminBundle\Form\Type\ModelHiddenType;
55
use Sonata\AdminBundle\Model\ModelManagerInterface;
56
use Sonata\AdminBundle\Route\RouteCollection;
57
use Sonata\AdminBundle\Route\RouteGeneratorInterface;
58
use Sonata\AdminBundle\Security\Handler\AclSecurityHandlerInterface;
59
use Sonata\AdminBundle\Security\Handler\SecurityHandlerInterface;
60
use Sonata\AdminBundle\Show\ShowMapper;
61
use Sonata\AdminBundle\Templating\MutableTemplateRegistryInterface;
62
use Sonata\AdminBundle\Translator\LabelTranslatorStrategyInterface;
63
use Sonata\CoreBundle\Model\Metadata;
64
use Sonata\CoreBundle\Validator\Constraints\InlineConstraint;
65
use Sonata\CoreBundle\Validator\ErrorElement;
66
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
67
use Symfony\Component\Form\Form;
68
use Symfony\Component\Form\FormBuilderInterface;
69
use Symfony\Component\Form\FormEvent;
70
use Symfony\Component\Form\FormEvents;
71
use Symfony\Component\HttpFoundation\Request;
72
use Symfony\Component\PropertyAccess\PropertyPath;
73
use Symfony\Component\Routing\Generator\UrlGeneratorInterface as RoutingUrlGeneratorInterface;
74
use Symfony\Component\Security\Acl\Model\DomainObjectInterface;
75
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
76
use Symfony\Component\Translation\TranslatorInterface;
77
use Symfony\Component\Validator\Validator\ValidatorInterface;
78
79
/**
80
 * @author Thomas Rabaix <[email protected]>
81
 */
82
abstract class AbstractAdmin implements AdminInterface, DomainObjectInterface, AdminTreeInterface
83
{
84
    const CONTEXT_MENU = 'menu';
85
    const CONTEXT_DASHBOARD = 'dashboard';
86
87
    const CLASS_REGEX =
88
        '@
89
        (?:([A-Za-z0-9]*)\\\)?        # vendor name / app name
90
        (Bundle\\\)?                  # optional bundle directory
91
        ([A-Za-z0-9]+?)(?:Bundle)?\\\ # bundle name, with optional suffix
92
        (
93
            Entity|Document|Model|PHPCR|CouchDocument|Phpcr|
94
            Doctrine\\\Orm|Doctrine\\\Phpcr|Doctrine\\\MongoDB|Doctrine\\\CouchDB
95
        )\\\(.*)@x';
96
97
    const MOSAIC_ICON_CLASS = 'fa fa-th-large fa-fw';
98
99
    /**
100
     * The list FieldDescription constructed from the configureListField method.
101
     *
102
     * @var array
103
     */
104
    protected $listFieldDescriptions = [];
105
106
    /**
107
     * The show FieldDescription constructed from the configureShowFields method.
108
     *
109
     * @var array
110
     */
111
    protected $showFieldDescriptions = [];
112
113
    /**
114
     * The list FieldDescription constructed from the configureFormField method.
115
     *
116
     * @var array
117
     */
118
    protected $formFieldDescriptions = [];
119
120
    /**
121
     * The filter FieldDescription constructed from the configureFilterField method.
122
     *
123
     * @var array
124
     */
125
    protected $filterFieldDescriptions = [];
126
127
    /**
128
     * The number of result to display in the list.
129
     *
130
     * @var int
131
     */
132
    protected $maxPerPage = 32;
133
134
    /**
135
     * The maximum number of page numbers to display in the list.
136
     *
137
     * @var int
138
     */
139
    protected $maxPageLinks = 25;
140
141
    /**
142
     * The base route name used to generate the routing information.
143
     *
144
     * @var string
145
     */
146
    protected $baseRouteName;
147
148
    /**
149
     * The base route pattern used to generate the routing information.
150
     *
151
     * @var string
152
     */
153
    protected $baseRoutePattern;
154
155
    /**
156
     * The base name controller used to generate the routing information.
157
     *
158
     * @var string
159
     */
160
    protected $baseControllerName;
161
162
    /**
163
     * The label class name  (used in the title/breadcrumb ...).
164
     *
165
     * @var string
166
     */
167
    protected $classnameLabel;
168
169
    /**
170
     * The translation domain to be used to translate messages.
171
     *
172
     * @var string
173
     */
174
    protected $translationDomain = 'messages';
175
176
    /**
177
     * Options to set to the form (ie, validation_groups).
178
     *
179
     * @var array
180
     */
181
    protected $formOptions = [];
182
183
    /**
184
     * Default values to the datagrid.
185
     *
186
     * @var array
187
     */
188
    protected $datagridValues = [
189
        '_page' => 1,
190
        '_per_page' => 32,
191
    ];
192
193
    /**
194
     * Predefined per page options.
195
     *
196
     * @var array
197
     */
198
    protected $perPageOptions = [16, 32, 64, 128, 256];
199
200
    /**
201
     * Pager type.
202
     *
203
     * @var string
204
     */
205
    protected $pagerType = Pager::TYPE_DEFAULT;
206
207
    /**
208
     * The code related to the admin.
209
     *
210
     * @var string
211
     */
212
    protected $code;
213
214
    /**
215
     * The label.
216
     *
217
     * @var string
218
     */
219
    protected $label;
220
221
    /**
222
     * Whether or not to persist the filters in the session.
223
     *
224
     * NEXT_MAJOR: remove this property
225
     *
226
     * @var bool
227
     *
228
     * @deprecated since 3.34, to be removed in 4.0.
229
     */
230
    protected $persistFilters = false;
231
232
    /**
233
     * Array of routes related to this admin.
234
     *
235
     * @var RouteCollection
236
     */
237
    protected $routes;
238
239
    /**
240
     * The subject only set in edit/update/create mode.
241
     *
242
     * @var object
243
     */
244
    protected $subject;
245
246
    /**
247
     * Define a Collection of child admin, ie /admin/order/{id}/order-element/{childId}.
248
     *
249
     * @var array
250
     */
251
    protected $children = [];
252
253
    /**
254
     * Reference the parent collection.
255
     *
256
     * @var AdminInterface|null
257
     */
258
    protected $parent = null;
259
260
    /**
261
     * The base code route refer to the prefix used to generate the route name.
262
     *
263
     * NEXT_MAJOR: remove this attribute.
264
     *
265
     * @deprecated This attribute is deprecated since 3.24 and will be removed in 4.0
266
     *
267
     * @var string
268
     */
269
    protected $baseCodeRoute = '';
270
271
    /**
272
     * NEXT_MAJOR: should be default array and private.
273
     *
274
     * @var string|array
275
     */
276
    protected $parentAssociationMapping = null;
277
278
    /**
279
     * Reference the parent FieldDescription related to this admin
280
     * only set for FieldDescription which is associated to an Sub Admin instance.
281
     *
282
     * @var FieldDescriptionInterface
283
     */
284
    protected $parentFieldDescription;
285
286
    /**
287
     * If true then the current admin is part of the nested admin set (from the url).
288
     *
289
     * @var bool
290
     */
291
    protected $currentChild = false;
292
293
    /**
294
     * The uniqid is used to avoid clashing with 2 admin related to the code
295
     * ie: a Block linked to a Block.
296
     *
297
     * @var string
298
     */
299
    protected $uniqid;
300
301
    /**
302
     * The Entity or Document manager.
303
     *
304
     * @var ModelManagerInterface
305
     */
306
    protected $modelManager;
307
308
    /**
309
     * The current request object.
310
     *
311
     * @var \Symfony\Component\HttpFoundation\Request
312
     */
313
    protected $request;
314
315
    /**
316
     * The translator component.
317
     *
318
     * NEXT_MAJOR: remove this property
319
     *
320
     * @var \Symfony\Component\Translation\TranslatorInterface
321
     *
322
     * @deprecated since 3.9, to be removed with 4.0
323
     */
324
    protected $translator;
325
326
    /**
327
     * The related form contractor.
328
     *
329
     * @var FormContractorInterface
330
     */
331
    protected $formContractor;
332
333
    /**
334
     * The related list builder.
335
     *
336
     * @var ListBuilderInterface
337
     */
338
    protected $listBuilder;
339
340
    /**
341
     * The related view builder.
342
     *
343
     * @var ShowBuilderInterface
344
     */
345
    protected $showBuilder;
346
347
    /**
348
     * The related datagrid builder.
349
     *
350
     * @var DatagridBuilderInterface
351
     */
352
    protected $datagridBuilder;
353
354
    /**
355
     * @var RouteBuilderInterface
356
     */
357
    protected $routeBuilder;
358
359
    /**
360
     * The datagrid instance.
361
     *
362
     * @var \Sonata\AdminBundle\Datagrid\DatagridInterface
363
     */
364
    protected $datagrid;
365
366
    /**
367
     * The router instance.
368
     *
369
     * @var RouteGeneratorInterface
370
     */
371
    protected $routeGenerator;
372
373
    /**
374
     * The generated breadcrumbs.
375
     *
376
     * NEXT_MAJOR : remove this property
377
     *
378
     * @var array
379
     */
380
    protected $breadcrumbs = [];
381
382
    /**
383
     * @var SecurityHandlerInterface
384
     */
385
    protected $securityHandler = null;
386
387
    /**
388
     * @var ValidatorInterface
389
     */
390
    protected $validator = null;
391
392
    /**
393
     * The configuration pool.
394
     *
395
     * @var Pool
396
     */
397
    protected $configurationPool;
398
399
    /**
400
     * @var MenuItemInterface
401
     */
402
    protected $menu;
403
404
    /**
405
     * @var MenuFactoryInterface
406
     */
407
    protected $menuFactory;
408
409
    /**
410
     * @var array
411
     */
412
    protected $loaded = [
413
        'view_fields' => false,
414
        'view_groups' => false,
415
        'routes' => false,
416
        'tab_menu' => false,
417
    ];
418
419
    /**
420
     * @var array
421
     */
422
    protected $formTheme = [];
423
424
    /**
425
     * @var array
426
     */
427
    protected $filterTheme = [];
428
429
    /**
430
     * @var array
431
     *
432
     * @deprecated since 3.34, will be dropped in 4.0. Use TemplateRegistry services instead
433
     */
434
    protected $templates = [];
435
436
    /**
437
     * @var OldAdminExtensionInterface|NewAdminExtensionInterface[]
438
     */
439
    protected $extensions = [];
440
441
    /**
442
     * @var LabelTranslatorStrategyInterface
443
     */
444
    protected $labelTranslatorStrategy;
445
446
    /**
447
     * Setting to true will enable preview mode for
448
     * the entity and show a preview button in the
449
     * edit/create forms.
450
     *
451
     * @var bool
452
     */
453
    protected $supportsPreviewMode = false;
454
455
    /**
456
     * Roles and permissions per role.
457
     *
458
     * @var array 'role' => ['permission', 'permission']
459
     */
460
    protected $securityInformation = [];
461
462
    protected $cacheIsGranted = [];
463
464
    /**
465
     * Action list for the search result.
466
     *
467
     * @var string[]
468
     */
469
    protected $searchResultActions = ['edit', 'show'];
470
471
    protected $listModes = [
472
        'list' => [
473
            'class' => 'fa fa-list fa-fw',
474
        ],
475
        'mosaic' => [
476
            'class' => self::MOSAIC_ICON_CLASS,
477
        ],
478
    ];
479
480
    /**
481
     * The Access mapping.
482
     *
483
     * @var array [action1 => requiredRole1, action2 => [requiredRole2, requiredRole3]]
484
     */
485
    protected $accessMapping = [];
486
487
    /**
488
     * @var MutableTemplateRegistryInterface
489
     */
490
    private $templateRegistry;
491
492
    /**
493
     * The class name managed by the admin class.
494
     *
495
     * @var string
496
     */
497
    private $class;
498
499
    /**
500
     * The subclasses supported by the admin class.
501
     *
502
     * @var array
503
     */
504
    private $subClasses = [];
505
506
    /**
507
     * The list collection.
508
     *
509
     * @var array
510
     */
511
    private $list;
512
513
    /**
514
     * @var FieldDescriptionCollection
515
     */
516
    private $show;
517
518
    /**
519
     * @var Form
520
     */
521
    private $form;
522
523
    /**
524
     * @var DatagridInterface
525
     */
526
    private $filter;
0 ignored issues
show
Unused Code introduced by
The property $filter is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
527
528
    /**
529
     * The cached base route name.
530
     *
531
     * @var string
532
     */
533
    private $cachedBaseRouteName;
534
535
    /**
536
     * The cached base route pattern.
537
     *
538
     * @var string
539
     */
540
    private $cachedBaseRoutePattern;
541
542
    /**
543
     * The form group disposition.
544
     *
545
     * @var array|bool
546
     */
547
    private $formGroups = false;
548
549
    /**
550
     * The form tabs disposition.
551
     *
552
     * @var array|bool
553
     */
554
    private $formTabs = false;
555
556
    /**
557
     * The view group disposition.
558
     *
559
     * @var array|bool
560
     */
561
    private $showGroups = false;
562
563
    /**
564
     * The view tab disposition.
565
     *
566
     * @var array|bool
567
     */
568
    private $showTabs = false;
569
570
    /**
571
     * The manager type to use for the admin.
572
     *
573
     * @var string
574
     */
575
    private $managerType;
576
577
    /**
578
     * The breadcrumbsBuilder component.
579
     *
580
     * @var BreadcrumbsBuilderInterface
581
     */
582
    private $breadcrumbsBuilder;
583
584
    /**
585
     * Component responsible for persisting filters.
586
     *
587
     * @var FilterPersisterInterface|null
588
     */
589
    private $filterPersister;
590
591
    /**
592
     * @param string $code
593
     * @param string $class
594
     * @param string $baseControllerName
595
     */
596
    public function __construct($code, $class, $baseControllerName)
597
    {
598
        $this->code = $code;
599
        $this->class = $class;
600
        $this->baseControllerName = $baseControllerName;
601
602
        $this->predefinePerPageOptions();
603
        $this->datagridValues['_per_page'] = $this->maxPerPage;
604
    }
605
606
    /**
607
     * {@inheritdoc}
608
     *
609
     * NEXT_MAJOR: return null to indicate no override
610
     */
611
    public function getExportFormats()
612
    {
613
        return [
614
            'json', 'xml', 'csv', 'xls',
615
        ];
616
    }
617
618
    /**
619
     * @return array
620
     */
621
    public function getExportFields()
622
    {
623
        $fields = $this->getModelManager()->getExportFields($this->getClass());
624
625
        foreach ($this->getExtensions() as $extension) {
0 ignored issues
show
Bug introduced by
The expression $this->getExtensions() of type object<Sonata\AdminBundl...minExtensionInterface>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
626
            // NEXT_MAJOR: check if extension implements ConfigureExportFieldsInterface before calling the method
627
            if (method_exists($extension, 'configureExportFields')) {
628
                $fields = $extension->configureExportFields($this, $fields);
629
            }
630
631
            // NEXT_MAJOR: remove this conditional
632
            if (!$extension instanceof ConfigureExportFieldsInterface) {
633
                $this->triggerExtensionDeprecationMessage(
634
                    'configureExportFields',
635
                    ConfigureExportFieldsInterface::class
636
                );
637
            }
638
        }
639
640
        return $fields;
641
    }
642
643
    public function getDataSourceIterator()
644
    {
645
        $datagrid = $this->getDatagrid();
646
        $datagrid->buildPager();
647
648
        $fields = [];
649
650
        foreach ($this->getExportFields() as $key => $field) {
651
            $label = $this->getTranslationLabel($field, 'export', 'label');
652
            $transLabel = $this->trans($label);
653
654
            // NEXT_MAJOR: Remove this hack, because all field labels will be translated with the major release
655
            // No translation key exists
656
            if ($transLabel == $label) {
657
                $fields[$key] = $field;
658
            } else {
659
                $fields[$transLabel] = $field;
660
            }
661
        }
662
663
        return $this->getModelManager()->getDataSourceIterator($datagrid, $fields);
664
    }
665
666
    public function validate(ErrorElement $errorElement, $object)
667
    {
668
    }
669
670
    /**
671
     * define custom variable.
672
     */
673
    public function initialize()
674
    {
675
        if (!$this->classnameLabel) {
676
            $this->classnameLabel = substr($this->getClass(), strrpos($this->getClass(), '\\') + 1);
677
        }
678
679
        // NEXT_MAJOR: Remove this line.
680
        $this->baseCodeRoute = $this->getCode();
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...ctAdmin::$baseCodeRoute has been deprecated with message: This attribute is deprecated since 3.24 and will be removed in 4.0

This property 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 property will be removed from the class and what other property to use instead.

Loading history...
681
682
        $this->configure();
683
    }
684
685
    public function configure()
686
    {
687
    }
688
689
    public function update($object)
690
    {
691
        $this->preUpdate($object);
692
        foreach ($this->extensions as $extension) {
0 ignored issues
show
Bug introduced by
The expression $this->extensions of type object<Sonata\AdminBundl...minExtensionInterface>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
693
            // NEXT_MAJOR: check if extension implements PreUpdateInterface before calling the method
694
            if (method_exists($extension, 'preUpdate')) {
695
                $extension->preUpdate($this, $object);
696
            }
697
698
            // NEXT_MAJOR: remove this conditional
699
            if (!$extension instanceof PreUpdateInterface) {
700
                $this->triggerExtensionDeprecationMessage('preUpdate', PreUpdateInterface::class);
701
            }
702
        }
703
704
        $result = $this->getModelManager()->update($object);
705
        // BC compatibility
706
        if (null !== $result) {
707
            $object = $result;
708
        }
709
710
        $this->postUpdate($object);
711
        foreach ($this->extensions as $extension) {
0 ignored issues
show
Bug introduced by
The expression $this->extensions of type object<Sonata\AdminBundl...minExtensionInterface>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
712
            // NEXT_MAJOR: check if extension implements PostUpdateInterface before calling the method
713
            if (method_exists($extension, 'postUpdate')) {
714
                $extension->postUpdate($this, $object);
715
            }
716
717
            // NEXT_MAJOR: remove this conditional
718
            if (!$extension instanceof PostUpdateInterface) {
719
                $this->triggerExtensionDeprecationMessage('postUpdate', PostUpdateInterface::class);
720
            }
721
        }
722
723
        return $object;
724
    }
725
726
    public function create($object)
727
    {
728
        $this->prePersist($object);
729
        foreach ($this->extensions as $extension) {
0 ignored issues
show
Bug introduced by
The expression $this->extensions of type object<Sonata\AdminBundl...minExtensionInterface>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
730
            // NEXT_MAJOR: check if extension implements PrePersistInterface before calling the method
731
            if (method_exists($extension, 'prePersist')) {
732
                $extension->prePersist($this, $object);
733
            }
734
735
            // NEXT_MAJOR: remove this conditional
736
            if (!$extension instanceof PrePersistInterface) {
737
                $this->triggerExtensionDeprecationMessage('prePersist', PrePersistInterface::class);
738
            }
739
        }
740
741
        $result = $this->getModelManager()->create($object);
742
        // BC compatibility
743
        if (null !== $result) {
744
            $object = $result;
745
        }
746
747
        $this->postPersist($object);
748
        foreach ($this->extensions as $extension) {
0 ignored issues
show
Bug introduced by
The expression $this->extensions of type object<Sonata\AdminBundl...minExtensionInterface>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
749
            // NEXT_MAJOR: check if extension implements PostPersistInterface before calling the method
750
            if (method_exists($extension, 'postPersist')) {
751
                $extension->postPersist($this, $object);
752
            }
753
754
            // NEXT_MAJOR: remove this conditional
755
            if (!$extension instanceof PostPersistInterface) {
756
                $this->triggerExtensionDeprecationMessage('postPersist', PostPersistInterface::class);
757
            }
758
        }
759
760
        $this->createObjectSecurity($object);
761
762
        return $object;
763
    }
764
765
    public function delete($object)
766
    {
767
        $this->preRemove($object);
768
        foreach ($this->extensions as $extension) {
0 ignored issues
show
Bug introduced by
The expression $this->extensions of type object<Sonata\AdminBundl...minExtensionInterface>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
769
            // NEXT_MAJOR: check if extension implements PreRemoveInterface before calling the method
770
            if (method_exists($extension, 'preRemove')) {
771
                $extension->preRemove($this, $object);
772
            }
773
774
            // NEXT_MAJOR: remove this conditional
775
            if (!$extension instanceof PreRemoveInterface) {
776
                $this->triggerExtensionDeprecationMessage('preRemove', PreRemoveInterface::class);
777
            }
778
        }
779
780
        $this->getSecurityHandler()->deleteObjectSecurity($this, $object);
781
        $this->getModelManager()->delete($object);
782
783
        $this->postRemove($object);
784
        foreach ($this->extensions as $extension) {
0 ignored issues
show
Bug introduced by
The expression $this->extensions of type object<Sonata\AdminBundl...minExtensionInterface>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
785
            // NEXT_MAJOR: check if extension implements PostRemoveInterface before calling the method
786
            if (method_exists($extension, 'postRemove')) {
787
                $extension->postRemove($this, $object);
788
            }
789
790
            // NEXT_MAJOR: remove this conditional
791
            if (!$extension instanceof PostRemoveInterface) {
792
                $this->triggerExtensionDeprecationMessage('postRemove', PostRemoveInterface::class);
793
            }
794
        }
795
    }
796
797
    public function preValidate($object)
0 ignored issues
show
Unused Code introduced by
The parameter $object 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...
798
    {
799
    }
800
801
    public function preUpdate($object)
802
    {
803
    }
804
805
    public function postUpdate($object)
806
    {
807
    }
808
809
    public function prePersist($object)
810
    {
811
    }
812
813
    public function postPersist($object)
814
    {
815
    }
816
817
    public function preRemove($object)
818
    {
819
    }
820
821
    public function postRemove($object)
822
    {
823
    }
824
825
    public function preBatchAction($actionName, ProxyQueryInterface $query, array &$idx, $allElements)
826
    {
827
    }
828
829
    public function getFilterParameters()
830
    {
831
        $parameters = [];
832
833
        // build the values array
834
        if ($this->hasRequest()) {
835
            $filters = $this->request->query->get('filter', []);
836
837
            // if filter persistence is configured
838
            // NEXT_MAJOR: remove `$this->persistFilters !== false` from the condition
839
            if (false !== $this->persistFilters && null !== $this->filterPersister) {
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tAdmin::$persistFilters has been deprecated with message: since 3.34, to be removed in 4.0.

This property 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 property will be removed from the class and what other property to use instead.

Loading history...
840
                // if reset filters is asked, remove from storage
841
                if ('reset' === $this->request->query->get('filters')) {
842
                    $this->filterPersister->reset($this->getCode());
843
                }
844
845
                // if no filters, fetch from storage
846
                // otherwise save to storage
847
                if (empty($filters)) {
848
                    $filters = $this->filterPersister->get($this->getCode());
849
                } else {
850
                    $this->filterPersister->set($this->getCode(), $filters);
851
                }
852
            }
853
854
            $parameters = array_merge(
855
                $this->getModelManager()->getDefaultSortValues($this->getClass()),
856
                $this->datagridValues,
857
                $this->getDefaultFilterValues(),
858
                $filters
859
            );
860
861
            if (!$this->determinedPerPageValue($parameters['_per_page'])) {
862
                $parameters['_per_page'] = $this->maxPerPage;
863
            }
864
865
            // always force the parent value
866
            if ($this->isChild() && $this->getParentAssociationMapping()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->getParentAssociationMapping() of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null 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...
867
                $name = str_replace('.', '__', $this->getParentAssociationMapping());
868
                $parameters[$name] = ['value' => $this->request->get($this->getParent()->getIdParameter())];
869
            }
870
        }
871
872
        return $parameters;
873
    }
874
875
    public function buildDatagrid()
876
    {
877
        if ($this->datagrid) {
878
            return;
879
        }
880
881
        $filterParameters = $this->getFilterParameters();
882
883
        // transform _sort_by from a string to a FieldDescriptionInterface for the datagrid.
884
        if (isset($filterParameters['_sort_by']) && is_string($filterParameters['_sort_by'])) {
885
            if ($this->hasListFieldDescription($filterParameters['_sort_by'])) {
886
                $filterParameters['_sort_by'] = $this->getListFieldDescription($filterParameters['_sort_by']);
887
            } else {
888
                $filterParameters['_sort_by'] = $this->getModelManager()->getNewFieldDescriptionInstance(
889
                    $this->getClass(),
890
                    $filterParameters['_sort_by'],
891
                    []
892
                );
893
894
                $this->getListBuilder()->buildField(null, $filterParameters['_sort_by'], $this);
895
            }
896
        }
897
898
        // initialize the datagrid
899
        $this->datagrid = $this->getDatagridBuilder()->getBaseDatagrid($this, $filterParameters);
900
901
        $this->datagrid->getPager()->setMaxPageLinks($this->maxPageLinks);
902
903
        $mapper = new DatagridMapper($this->getDatagridBuilder(), $this->datagrid, $this);
904
905
        // build the datagrid filter
906
        $this->configureDatagridFilters($mapper);
907
908
        // ok, try to limit to add parent filter
909
        if ($this->isChild() && $this->getParentAssociationMapping() && !$mapper->has($this->getParentAssociationMapping())) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->getParentAssociationMapping() of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null 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...
910
            $mapper->add($this->getParentAssociationMapping(), null, [
911
                'show_filter' => false,
912
                'label' => false,
913
                'field_type' => ModelHiddenType::class,
914
                'field_options' => [
915
                    'model_manager' => $this->getModelManager(),
916
                ],
917
                'operator_type' => HiddenType::class,
918
            ], null, null, [
919
                'admin_code' => $this->getParent()->getCode(),
920
            ]);
921
        }
922
923
        foreach ($this->getExtensions() as $extension) {
0 ignored issues
show
Bug introduced by
The expression $this->getExtensions() of type object<Sonata\AdminBundl...minExtensionInterface>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
924
            // NEXT_MAJOR: check if extension implements ConfigureDatagridFieldsInterface before calling the method
925
            if (method_exists($extension, 'configureDatagridFilters')) {
926
                $extension->configureDatagridFilters($mapper);
927
            }
928
929
            // NEXT_MAJOR: remove this conditional
930
            if (!$extension instanceof ConfigureDatagridFieldsInterface) {
931
                $this->triggerExtensionDeprecationMessage(
932
                    'configureDatagridFilters',
933
                    ConfigureDatagridFieldsInterface::class
934
                );
935
            }
936
        }
937
    }
938
939
    /**
940
     * Returns the name of the parent related field, so the field can be use to set the default
941
     * value (ie the parent object) or to filter the object.
942
     *
943
     * @throws \InvalidArgumentException
944
     *
945
     * @return null|string
946
     */
947
    public function getParentAssociationMapping()
948
    {
949
        // NEXT_MAJOR: remove array check
950
        if (\is_array($this->parentAssociationMapping) && $this->getParent()) {
951
            $parent = $this->getParent()->getCode();
952
953
            if (array_key_exists($parent, $this->parentAssociationMapping)) {
954
                return $this->parentAssociationMapping[$parent];
955
            }
956
957
            throw new \InvalidArgumentException(sprintf(
958
                "There's no association between %s and %s.",
959
                $this->getCode(),
960
                $this->getParent()->getCode()
961
            ));
962
        }
963
964
        // NEXT_MAJOR: remove this line
965
        return $this->parentAssociationMapping;
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->parentAssociationMapping; of type string|array adds the type array to the return on line 965 which is incompatible with the return type documented by Sonata\AdminBundle\Admin...arentAssociationMapping of type null|string.
Loading history...
966
    }
967
968
    /**
969
     * @param string $code
970
     * @param string $value
971
     */
972
    final public function addParentAssociationMapping($code, $value)
973
    {
974
        $this->parentAssociationMapping[$code] = $value;
975
    }
976
977
    /**
978
     * Returns the baseRoutePattern used to generate the routing information.
979
     *
980
     * @throws \RuntimeException
981
     *
982
     * @return string the baseRoutePattern used to generate the routing information
983
     */
984
    public function getBaseRoutePattern()
985
    {
986
        if (null !== $this->cachedBaseRoutePattern) {
987
            return $this->cachedBaseRoutePattern;
988
        }
989
990
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route pattern
991
            if (!$this->baseRoutePattern) {
992
                preg_match(self::CLASS_REGEX, $this->class, $matches);
993
994
                if (!$matches) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $matches of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
995
                    throw new \RuntimeException(sprintf('Please define a default `baseRoutePattern` value for the admin class `%s`', get_class($this)));
996
                }
997
            }
998
999
            $this->cachedBaseRoutePattern = sprintf('%s/%s/%s',
1000
                $this->getParent()->getBaseRoutePattern(),
1001
                $this->getParent()->getRouterIdParameter(),
1002
                $this->baseRoutePattern ?: $this->urlize($matches[5], '-')
0 ignored issues
show
Bug introduced by
The variable $matches does not seem to be defined for all execution paths leading up to this point.

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1003
            );
1004
        } elseif ($this->baseRoutePattern) {
1005
            $this->cachedBaseRoutePattern = $this->baseRoutePattern;
1006
        } else {
1007
            preg_match(self::CLASS_REGEX, $this->class, $matches);
1008
1009
            if (!$matches) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $matches of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1010
                throw new \RuntimeException(sprintf('Please define a default `baseRoutePattern` value for the admin class `%s`', get_class($this)));
1011
            }
1012
1013
            $this->cachedBaseRoutePattern = sprintf('/%s%s/%s',
1014
                empty($matches[1]) ? '' : $this->urlize($matches[1], '-').'/',
1015
                $this->urlize($matches[3], '-'),
1016
                $this->urlize($matches[5], '-')
1017
            );
1018
        }
1019
1020
        return $this->cachedBaseRoutePattern;
1021
    }
1022
1023
    /**
1024
     * Returns the baseRouteName used to generate the routing information.
1025
     *
1026
     * @throws \RuntimeException
1027
     *
1028
     * @return string the baseRouteName used to generate the routing information
1029
     */
1030
    public function getBaseRouteName()
1031
    {
1032
        if (null !== $this->cachedBaseRouteName) {
1033
            return $this->cachedBaseRouteName;
1034
        }
1035
1036
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route name
1037
            if (!$this->baseRouteName) {
1038
                preg_match(self::CLASS_REGEX, $this->class, $matches);
1039
1040
                if (!$matches) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $matches of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1041
                    throw new \RuntimeException(sprintf('Cannot automatically determine base route name, please define a default `baseRouteName` value for the admin class `%s`', get_class($this)));
1042
                }
1043
            }
1044
1045
            $this->cachedBaseRouteName = sprintf('%s_%s',
1046
                $this->getParent()->getBaseRouteName(),
1047
                $this->baseRouteName ?: $this->urlize($matches[5])
0 ignored issues
show
Bug introduced by
The variable $matches 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...
1048
            );
1049
        } elseif ($this->baseRouteName) {
1050
            $this->cachedBaseRouteName = $this->baseRouteName;
1051
        } else {
1052
            preg_match(self::CLASS_REGEX, $this->class, $matches);
1053
1054
            if (!$matches) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $matches of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1055
                throw new \RuntimeException(sprintf('Cannot automatically determine base route name, please define a default `baseRouteName` value for the admin class `%s`', get_class($this)));
1056
            }
1057
1058
            $this->cachedBaseRouteName = sprintf('admin_%s%s_%s',
1059
                empty($matches[1]) ? '' : $this->urlize($matches[1]).'_',
1060
                $this->urlize($matches[3]),
1061
                $this->urlize($matches[5])
1062
            );
1063
        }
1064
1065
        return $this->cachedBaseRouteName;
1066
    }
1067
1068
    /**
1069
     * urlize the given word.
1070
     *
1071
     * @param string $word
1072
     * @param string $sep  the separator
1073
     *
1074
     * @return string
1075
     */
1076
    public function urlize($word, $sep = '_')
1077
    {
1078
        return strtolower(preg_replace('/[^a-z0-9_]/i', $sep.'$1', $word));
1079
    }
1080
1081
    public function getClass()
1082
    {
1083
        if ($this->hasActiveSubClass()) {
1084
            if ($this->getParentFieldDescription()) {
1085
                throw new \RuntimeException('Feature not implemented: an embedded admin cannot have subclass');
1086
            }
1087
1088
            $subClass = $this->getRequest()->query->get('subclass');
1089
1090
            if (!$this->hasSubClass($subClass)) {
1091
                throw new \RuntimeException(sprintf('Subclass "%" is not defined.', $subClass));
1092
            }
1093
1094
            return $this->getSubClass($subClass);
1095
        }
1096
1097
        // see https://github.com/sonata-project/SonataCoreBundle/commit/247eeb0a7ca7211142e101754769d70bc402a5b4
1098
        if ($this->subject && is_object($this->subject)) {
1099
            return ClassUtils::getClass($this->subject);
1100
        }
1101
1102
        return $this->class;
1103
    }
1104
1105
    public function getSubClasses()
1106
    {
1107
        return $this->subClasses;
1108
    }
1109
1110
    /**
1111
     * NEXT_MAJOR: remove this method.
1112
     */
1113
    public function addSubClass($subClass)
1114
    {
1115
        @trigger_error(sprintf(
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...
1116
            'Method "%s" is deprecated since 3.30 and will be removed in 4.0.',
1117
            __METHOD__
1118
        ), E_USER_DEPRECATED);
1119
1120
        if (!in_array($subClass, $this->subClasses)) {
1121
            $this->subClasses[] = $subClass;
1122
        }
1123
    }
1124
1125
    public function setSubClasses(array $subClasses)
1126
    {
1127
        $this->subClasses = $subClasses;
1128
    }
1129
1130
    public function hasSubClass($name)
1131
    {
1132
        return isset($this->subClasses[$name]);
1133
    }
1134
1135
    public function hasActiveSubClass()
1136
    {
1137
        if (count($this->subClasses) > 0 && $this->request) {
1138
            return null !== $this->getRequest()->query->get('subclass');
1139
        }
1140
1141
        return false;
1142
    }
1143
1144
    public function getActiveSubClass()
1145
    {
1146
        if (!$this->hasActiveSubClass()) {
1147
            return;
1148
        }
1149
1150
        return $this->getSubClass($this->getActiveSubclassCode());
1151
    }
1152
1153
    public function getActiveSubclassCode()
1154
    {
1155
        if (!$this->hasActiveSubClass()) {
1156
            return;
1157
        }
1158
1159
        $subClass = $this->getRequest()->query->get('subclass');
1160
1161
        if (!$this->hasSubClass($subClass)) {
1162
            return;
1163
        }
1164
1165
        return $subClass;
1166
    }
1167
1168
    public function getBatchActions()
1169
    {
1170
        $actions = [];
1171
1172
        if ($this->hasRoute('delete') && $this->hasAccess('delete')) {
1173
            $actions['delete'] = [
1174
                'label' => 'action_delete',
1175
                'translation_domain' => 'SonataAdminBundle',
1176
                'ask_confirmation' => true, // by default always true
1177
            ];
1178
        }
1179
1180
        $actions = $this->configureBatchActions($actions);
1181
1182
        foreach ($this->getExtensions() as $extension) {
0 ignored issues
show
Bug introduced by
The expression $this->getExtensions() of type object<Sonata\AdminBundl...minExtensionInterface>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1183
            // NEXT_MAJOR: check if extension implements ConfigureBatchActionsInterface before calling the method
1184
            if (method_exists($extension, 'configureBatchActions')) {
1185
                $actions = $extension->configureBatchActions($this, $actions);
1186
            }
1187
1188
            // NEXT_MAJOR: remove this conditional
1189
            if (!$extension instanceof ConfigureBatchActionsInterface) {
1190
                $this->triggerExtensionDeprecationMessage(
1191
                    'configureBatchActions',
1192
                    ConfigureBatchActionsInterface::class
1193
                );
1194
            }
1195
        }
1196
1197
        foreach ($actions  as $name => &$action) {
1198
            if (!array_key_exists('label', $action)) {
1199
                $action['label'] = $this->getTranslationLabel($name, 'batch', 'label');
1200
            }
1201
1202
            if (!array_key_exists('translation_domain', $action)) {
1203
                $action['translation_domain'] = $this->getTranslationDomain();
1204
            }
1205
        }
1206
1207
        return $actions;
1208
    }
1209
1210
    public function getRoutes()
1211
    {
1212
        $this->buildRoutes();
1213
1214
        return $this->routes;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->routes; (Sonata\AdminBundle\Route\RouteCollection) is incompatible with the return type declared by the interface Sonata\AdminBundle\Admin...torInterface::getRoutes of type Sonata\AdminBundle\Admin\RouteCollection.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1215
    }
1216
1217
    public function getRouterIdParameter()
1218
    {
1219
        return '{'.$this->getIdParameter().'}';
1220
    }
1221
1222
    public function getIdParameter()
1223
    {
1224
        $parameter = 'id';
1225
1226
        for ($i = 0; $i < $this->getChildDepth(); ++$i) {
1227
            $parameter = 'child'.ucfirst($parameter);
1228
        }
1229
1230
        return $parameter;
1231
    }
1232
1233
    public function hasRoute($name)
1234
    {
1235
        if (!$this->routeGenerator) {
1236
            throw new \RuntimeException('RouteGenerator cannot be null');
1237
        }
1238
1239
        return $this->routeGenerator->hasAdminRoute($this, $name);
1240
    }
1241
1242
    public function isCurrentRoute($name, $adminCode = null)
1243
    {
1244
        if (!$this->hasRequest()) {
1245
            return false;
1246
        }
1247
1248
        $request = $this->getRequest();
1249
        $route = $request->get('_route');
1250
1251
        if ($adminCode) {
1252
            $admin = $this->getConfigurationPool()->getAdminByAdminCode($adminCode);
1253
        } else {
1254
            $admin = $this;
1255
        }
1256
1257
        if (!$admin) {
1258
            return false;
1259
        }
1260
1261
        return ($admin->getBaseRouteName().'_'.$name) == $route;
1262
    }
1263
1264
    public function generateObjectUrl($name, $object, array $parameters = [], $absolute = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1265
    {
1266
        $parameters['id'] = $this->getUrlsafeIdentifier($object);
1267
1268
        return $this->generateUrl($name, $parameters, $absolute);
1269
    }
1270
1271
    public function generateUrl($name, array $parameters = [], $absolute = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1272
    {
1273
        return $this->routeGenerator->generateUrl($this, $name, $parameters, $absolute);
0 ignored issues
show
Documentation introduced by
$absolute is of type integer, but the function expects a boolean.

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...
1274
    }
1275
1276
    public function generateMenuUrl($name, array $parameters = [], $absolute = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1277
    {
1278
        return $this->routeGenerator->generateMenuUrl($this, $name, $parameters, $absolute);
0 ignored issues
show
Documentation introduced by
$absolute is of type integer, but the function expects a boolean.

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...
1279
    }
1280
1281
    final public function setTemplateRegistry(MutableTemplateRegistryInterface $templateRegistry)
1282
    {
1283
        $this->templateRegistry = $templateRegistry;
1284
    }
1285
1286
    public function setTemplates(array $templates)
1287
    {
1288
        // NEXT_MAJOR: Remove this line
1289
        $this->templates = $templates;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin\AbstractAdmin::$templates has been deprecated with message: since 3.34, will be dropped in 4.0. Use TemplateRegistry services instead

This property 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 property will be removed from the class and what other property to use instead.

Loading history...
1290
1291
        $this->getTemplateRegistry()->setTemplates($templates);
1292
    }
1293
1294
    /**
1295
     * @param string $name
1296
     * @param string $template
1297
     */
1298
    public function setTemplate($name, $template)
1299
    {
1300
        // NEXT_MAJOR: Remove this line
1301
        $this->templates[$name] = $template;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin\AbstractAdmin::$templates has been deprecated with message: since 3.34, will be dropped in 4.0. Use TemplateRegistry services instead

This property 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 property will be removed from the class and what other property to use instead.

Loading history...
1302
1303
        $this->getTemplateRegistry()->setTemplate($name, $template);
1304
    }
1305
1306
    /**
1307
     * @deprecated since 3.34, will be dropped in 4.0. Use TemplateRegistry services instead
1308
     *
1309
     * @return array
1310
     */
1311
    public function getTemplates()
1312
    {
1313
        return $this->getTemplateRegistry()->getTemplates();
1314
    }
1315
1316
    /**
1317
     * @deprecated since 3.34, will be dropped in 4.0. Use TemplateRegistry services instead
1318
     *
1319
     * @param string $name
1320
     *
1321
     * @return null|string
1322
     */
1323
    public function getTemplate($name)
1324
    {
1325
        return $this->getTemplateRegistry()->getTemplate($name);
1326
    }
1327
1328
    public function getNewInstance()
1329
    {
1330
        $object = $this->getModelManager()->getModelInstance($this->getClass());
1331
        foreach ($this->getExtensions() as $extension) {
0 ignored issues
show
Bug introduced by
The expression $this->getExtensions() of type object<Sonata\AdminBundl...minExtensionInterface>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1332
            // NEXT_MAJOR: check if extension implements AlterNewInstanceInterface before calling the method
1333
            if (method_exists($extension, 'alterNewInstance')) {
1334
                $extension->alterNewInstance($this, $object);
1335
            }
1336
1337
            // NEXT_MAJOR: remove this conditional
1338
            if (!$extension instanceof AlterNewInstanceInterface) {
1339
                $this->triggerExtensionDeprecationMessage(
1340
                    'alterNewInstance',
1341
                    AlterNewInstanceInterface::class
1342
                );
1343
            }
1344
        }
1345
1346
        return $object;
1347
    }
1348
1349
    public function getFormBuilder()
1350
    {
1351
        $this->formOptions['data_class'] = $this->getClass();
1352
1353
        $formBuilder = $this->getFormContractor()->getFormBuilder(
1354
            $this->getUniqid(),
1355
            $this->formOptions
1356
        );
1357
1358
        $this->defineFormBuilder($formBuilder);
1359
1360
        return $formBuilder;
1361
    }
1362
1363
    /**
1364
     * This method is being called by the main admin class and the child class,
1365
     * the getFormBuilder is only call by the main admin class.
1366
     */
1367
    public function defineFormBuilder(FormBuilderInterface $formBuilder)
1368
    {
1369
        $mapper = new FormMapper($this->getFormContractor(), $formBuilder, $this);
1370
1371
        $this->configureFormFields($mapper);
1372
1373
        foreach ($this->getExtensions() as $extension) {
0 ignored issues
show
Bug introduced by
The expression $this->getExtensions() of type object<Sonata\AdminBundl...minExtensionInterface>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1374
            // NEXT_MAJOR: check if extension implements ConfigureFormFieldsInterface before calling the method
1375
            if (method_exists($extension, 'configureFormFields')) {
1376
                $extension->configureFormFields($mapper);
1377
            }
1378
1379
            // NEXT_MAJOR: remove this conditional
1380
            if (!$extension instanceof ConfigureFormFieldsInterface) {
1381
                $this->triggerExtensionDeprecationMessage(
1382
                    'configureFormFields',
1383
                    ConfigureFormFieldsInterface::class
1384
                );
1385
            }
1386
        }
1387
1388
        $this->attachInlineValidator();
1389
    }
1390
1391
    public function attachAdminClass(FieldDescriptionInterface $fieldDescription)
1392
    {
1393
        $pool = $this->getConfigurationPool();
1394
1395
        $adminCode = $fieldDescription->getOption('admin_code');
1396
1397
        if (null !== $adminCode) {
1398
            $admin = $pool->getAdminByAdminCode($adminCode);
0 ignored issues
show
Documentation introduced by
$adminCode 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...
1399
        } else {
1400
            $admin = $pool->getAdminByClass($fieldDescription->getTargetEntity());
1401
        }
1402
1403
        if (!$admin) {
1404
            return;
1405
        }
1406
1407
        if ($this->hasRequest()) {
1408
            $admin->setRequest($this->getRequest());
1409
        }
1410
1411
        $fieldDescription->setAssociationAdmin($admin);
1412
    }
1413
1414
    public function getObject($id)
1415
    {
1416
        $object = $this->getModelManager()->find($this->getClass(), $id);
1417
        foreach ($this->getExtensions() as $extension) {
0 ignored issues
show
Bug introduced by
The expression $this->getExtensions() of type object<Sonata\AdminBundl...minExtensionInterface>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1418
            // NEXT_MAJOR: check if extension implements AlterObjectInterface before calling the method
1419
            if (method_exists($extension, 'alterObject')) {
1420
                $extension->alterObject($this, $object);
1421
            }
1422
1423
            // NEXT_MAJOR: remove this conditional
1424
            if (!$extension instanceof AlterObjectInterface) {
1425
                $this->triggerExtensionDeprecationMessage(
1426
                    'alterObject',
1427
                    AlterObjectInterface::class
1428
                );
1429
            }
1430
        }
1431
1432
        return $object;
1433
    }
1434
1435
    public function getForm()
1436
    {
1437
        $this->buildForm();
1438
1439
        return $this->form;
1440
    }
1441
1442
    public function getList()
1443
    {
1444
        $this->buildList();
1445
1446
        return $this->list;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->list; (array) is incompatible with the return type declared by the interface Sonata\AdminBundle\Admin...istryInterface::getList of type Sonata\AdminBundle\Admin...ldDescriptionCollection.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1447
    }
1448
1449
    public function createQuery($context = 'list')
1450
    {
1451
        if (func_num_args() > 0) {
1452
            @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...
1453
                'The $context argument of '.__METHOD__.' is deprecated since 3.3, to be removed in 4.0.',
1454
                E_USER_DEPRECATED
1455
            );
1456
        }
1457
        $query = $this->getModelManager()->createQuery($this->getClass());
1458
1459
        foreach ($this->extensions as $extension) {
0 ignored issues
show
Bug introduced by
The expression $this->extensions of type object<Sonata\AdminBundl...minExtensionInterface>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1460
            // NEXT_MAJOR: check if extension implements ConfigureQueryInterface before calling the method
1461
            if (method_exists($extension, 'configureQuery')) {
1462
                $extension->configureQuery($this, $query, $context);
1463
            }
1464
1465
            // NEXT_MAJOR: remove this conditional
1466
            if (!$extension instanceof ConfigureQueryInterface) {
1467
                $this->triggerExtensionDeprecationMessage('configureQuery', ConfigureQueryInterface::class);
1468
            }
1469
        }
1470
1471
        return $query;
1472
    }
1473
1474
    public function getDatagrid()
1475
    {
1476
        $this->buildDatagrid();
1477
1478
        return $this->datagrid;
1479
    }
1480
1481
    public function buildTabMenu($action, AdminInterface $childAdmin = null)
1482
    {
1483
        if ($this->loaded['tab_menu']) {
1484
            return;
1485
        }
1486
1487
        $this->loaded['tab_menu'] = true;
1488
1489
        $menu = $this->menuFactory->createItem('root');
1490
        $menu->setChildrenAttribute('class', 'nav navbar-nav');
1491
        $menu->setExtra('translation_domain', $this->translationDomain);
1492
1493
        // Prevents BC break with KnpMenuBundle v1.x
1494
        if (method_exists($menu, 'setCurrentUri')) {
1495
            $menu->setCurrentUri($this->getRequest()->getBaseUrl().$this->getRequest()->getPathInfo());
0 ignored issues
show
Bug introduced by
The method setCurrentUri() does not exist on Knp\Menu\ItemInterface. Did you maybe mean setCurrent()?

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

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

Loading history...
1496
        }
1497
1498
        $this->configureTabMenu($menu, $action, $childAdmin);
1499
1500
        foreach ($this->getExtensions() as $extension) {
0 ignored issues
show
Bug introduced by
The expression $this->getExtensions() of type object<Sonata\AdminBundl...minExtensionInterface>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1501
            // NEXT_MAJOR: check if extension implements ConfigureTabMenuInterface before calling the method
1502
            if (method_exists($extension, 'configureTabMenu')) {
1503
                $extension->configureTabMenu($this, $menu, $action, $childAdmin);
1504
            }
1505
1506
            // NEXT_MAJOR: remove this conditional
1507
            if (!$extension instanceof ConfigureTabMenuInterface) {
1508
                $this->triggerExtensionDeprecationMessage(
1509
                    'configureTabMenu',
1510
                    ConfigureTabMenuInterface::class
1511
                );
1512
            }
1513
        }
1514
1515
        $this->menu = $menu;
1516
    }
1517
1518
    public function buildSideMenu($action, AdminInterface $childAdmin = null)
1519
    {
1520
        return $this->buildTabMenu($action, $childAdmin);
1521
    }
1522
1523
    /**
1524
     * @param string $action
1525
     *
1526
     * @return ItemInterface
1527
     */
1528
    public function getSideMenu($action, AdminInterface $childAdmin = null)
1529
    {
1530
        if ($this->isChild()) {
1531
            return $this->getParent()->getSideMenu($action, $this);
1532
        }
1533
1534
        $this->buildSideMenu($action, $childAdmin);
1535
1536
        return $this->menu;
1537
    }
1538
1539
    /**
1540
     * Returns the root code.
1541
     *
1542
     * @return string the root code
1543
     */
1544
    public function getRootCode()
1545
    {
1546
        return $this->getRoot()->getCode();
1547
    }
1548
1549
    /**
1550
     * Returns the master admin.
1551
     *
1552
     * @return AbstractAdmin the root admin class
1553
     */
1554
    public function getRoot()
1555
    {
1556
        $parentFieldDescription = $this->getParentFieldDescription();
1557
1558
        if (!$parentFieldDescription) {
1559
            return $this;
1560
        }
1561
1562
        return $parentFieldDescription->getAdmin()->getRoot();
1563
    }
1564
1565
    public function setBaseControllerName($baseControllerName)
1566
    {
1567
        $this->baseControllerName = $baseControllerName;
1568
    }
1569
1570
    public function getBaseControllerName()
1571
    {
1572
        return $this->baseControllerName;
1573
    }
1574
1575
    /**
1576
     * @param string $label
1577
     */
1578
    public function setLabel($label)
1579
    {
1580
        $this->label = $label;
1581
    }
1582
1583
    public function getLabel()
1584
    {
1585
        return $this->label;
1586
    }
1587
1588
    /**
1589
     * @param bool $persist
1590
     *
1591
     * NEXT_MAJOR: remove this method
1592
     *
1593
     * @deprecated since 3.34, to be removed in 4.0.
1594
     */
1595
    public function setPersistFilters($persist)
1596
    {
1597
        @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...
1598
            'The '.__METHOD__.' method is deprecated since version 3.34 and will be removed in 4.0.',
1599
            E_USER_DEPRECATED
1600
        );
1601
1602
        $this->persistFilters = $persist;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tAdmin::$persistFilters has been deprecated with message: since 3.34, to be removed in 4.0.

This property 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 property will be removed from the class and what other property to use instead.

Loading history...
1603
    }
1604
1605
    /**
1606
     * @param FilterPersisterInterface|null $filterPersister
1607
     */
1608
    public function setFilterPersister(FilterPersisterInterface $filterPersister = null)
1609
    {
1610
        $this->filterPersister = $filterPersister;
1611
        // NEXT_MAJOR remove the deprecated property will be removed. Needed for persisted filter condition.
1612
        $this->persistFilters = true;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tAdmin::$persistFilters has been deprecated with message: since 3.34, to be removed in 4.0.

This property 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 property will be removed from the class and what other property to use instead.

Loading history...
1613
    }
1614
1615
    /**
1616
     * @param int $maxPerPage
1617
     */
1618
    public function setMaxPerPage($maxPerPage)
1619
    {
1620
        $this->maxPerPage = $maxPerPage;
1621
    }
1622
1623
    /**
1624
     * @return int
1625
     */
1626
    public function getMaxPerPage()
1627
    {
1628
        return $this->maxPerPage;
1629
    }
1630
1631
    /**
1632
     * @param int $maxPageLinks
1633
     */
1634
    public function setMaxPageLinks($maxPageLinks)
1635
    {
1636
        $this->maxPageLinks = $maxPageLinks;
1637
    }
1638
1639
    /**
1640
     * @return int
1641
     */
1642
    public function getMaxPageLinks()
1643
    {
1644
        return $this->maxPageLinks;
1645
    }
1646
1647
    public function getFormGroups()
1648
    {
1649
        return $this->formGroups;
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->formGroups; of type array|boolean adds the type boolean to the return on line 1649 which is incompatible with the return type declared by the interface Sonata\AdminBundle\Admin...nterface::getFormGroups of type array.
Loading history...
1650
    }
1651
1652
    public function setFormGroups(array $formGroups)
1653
    {
1654
        $this->formGroups = $formGroups;
1655
    }
1656
1657
    public function removeFieldFromFormGroup($key)
1658
    {
1659
        foreach ($this->formGroups as $name => $formGroup) {
0 ignored issues
show
Bug introduced by
The expression $this->formGroups of type array|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1660
            unset($this->formGroups[$name]['fields'][$key]);
1661
1662
            if (empty($this->formGroups[$name]['fields'])) {
1663
                unset($this->formGroups[$name]);
1664
            }
1665
        }
1666
    }
1667
1668
    /**
1669
     * @param array $group
1670
     */
1671
    public function reorderFormGroup($group, array $keys)
1672
    {
1673
        $formGroups = $this->getFormGroups();
1674
        $formGroups[$group]['fields'] = array_merge(array_flip($keys), $formGroups[$group]['fields']);
1675
        $this->setFormGroups($formGroups);
0 ignored issues
show
Bug introduced by
It seems like $formGroups defined by $this->getFormGroups() on line 1673 can also be of type boolean; however, Sonata\AdminBundle\Admin...tAdmin::setFormGroups() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1676
    }
1677
1678
    public function getFormTabs()
1679
    {
1680
        return $this->formTabs;
1681
    }
1682
1683
    public function setFormTabs(array $formTabs)
1684
    {
1685
        $this->formTabs = $formTabs;
1686
    }
1687
1688
    public function getShowTabs()
1689
    {
1690
        return $this->showTabs;
1691
    }
1692
1693
    public function setShowTabs(array $showTabs)
1694
    {
1695
        $this->showTabs = $showTabs;
1696
    }
1697
1698
    public function getShowGroups()
1699
    {
1700
        return $this->showGroups;
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->showGroups; of type array|boolean adds the type boolean to the return on line 1700 which is incompatible with the return type declared by the interface Sonata\AdminBundle\Admin...nterface::getShowGroups of type array.
Loading history...
1701
    }
1702
1703
    public function setShowGroups(array $showGroups)
1704
    {
1705
        $this->showGroups = $showGroups;
1706
    }
1707
1708
    public function reorderShowGroup($group, array $keys)
1709
    {
1710
        $showGroups = $this->getShowGroups();
1711
        $showGroups[$group]['fields'] = array_merge(array_flip($keys), $showGroups[$group]['fields']);
1712
        $this->setShowGroups($showGroups);
0 ignored issues
show
Bug introduced by
It seems like $showGroups defined by $this->getShowGroups() on line 1710 can also be of type boolean; however, Sonata\AdminBundle\Admin...tAdmin::setShowGroups() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1713
    }
1714
1715
    public function setParentFieldDescription(FieldDescriptionInterface $parentFieldDescription)
1716
    {
1717
        $this->parentFieldDescription = $parentFieldDescription;
1718
    }
1719
1720
    public function getParentFieldDescription()
1721
    {
1722
        return $this->parentFieldDescription;
1723
    }
1724
1725
    public function hasParentFieldDescription()
1726
    {
1727
        return $this->parentFieldDescription instanceof FieldDescriptionInterface;
1728
    }
1729
1730
    public function setSubject($subject)
1731
    {
1732
        if (is_object($subject) && !is_a($subject, $this->getClass(), true)) {
1733
            $message = <<<'EOT'
1734
You are trying to set entity an instance of "%s",
1735
which is not the one registered with this admin class ("%s").
1736
This is deprecated since 3.5 and will no longer be supported in 4.0.
1737
EOT;
1738
1739
            @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...
1740
                sprintf($message, get_class($subject), $this->getClass()),
1741
                E_USER_DEPRECATED
1742
            ); // NEXT_MAJOR : throw an exception instead
1743
        }
1744
1745
        $this->subject = $subject;
1746
    }
1747
1748
    public function getSubject()
1749
    {
1750
        if (null === $this->subject && $this->request && !$this->hasParentFieldDescription()) {
1751
            $id = $this->request->get($this->getIdParameter());
1752
1753
            if (null !== $id) {
1754
                $this->subject = $this->getObject($id);
1755
            }
1756
        }
1757
1758
        return $this->subject;
1759
    }
1760
1761
    public function hasSubject()
1762
    {
1763
        return (bool) $this->getSubject();
1764
    }
1765
1766
    public function getFormFieldDescriptions()
1767
    {
1768
        $this->buildForm();
1769
1770
        return $this->formFieldDescriptions;
1771
    }
1772
1773
    public function getFormFieldDescription($name)
1774
    {
1775
        return $this->hasFormFieldDescription($name) ? $this->formFieldDescriptions[$name] : null;
1776
    }
1777
1778
    /**
1779
     * Returns true if the admin has a FieldDescription with the given $name.
1780
     *
1781
     * @param string $name
1782
     *
1783
     * @return bool
1784
     */
1785
    public function hasFormFieldDescription($name)
1786
    {
1787
        return array_key_exists($name, $this->formFieldDescriptions) ? true : false;
1788
    }
1789
1790
    public function addFormFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1791
    {
1792
        $this->formFieldDescriptions[$name] = $fieldDescription;
1793
    }
1794
1795
    /**
1796
     * remove a FieldDescription.
1797
     *
1798
     * @param string $name
1799
     */
1800
    public function removeFormFieldDescription($name)
1801
    {
1802
        unset($this->formFieldDescriptions[$name]);
1803
    }
1804
1805
    /**
1806
     * build and return the collection of form FieldDescription.
1807
     *
1808
     * @return array collection of form FieldDescription
1809
     */
1810
    public function getShowFieldDescriptions()
1811
    {
1812
        $this->buildShow();
1813
1814
        return $this->showFieldDescriptions;
1815
    }
1816
1817
    /**
1818
     * Returns the form FieldDescription with the given $name.
1819
     *
1820
     * @param string $name
1821
     *
1822
     * @return FieldDescriptionInterface
1823
     */
1824
    public function getShowFieldDescription($name)
1825
    {
1826
        $this->buildShow();
1827
1828
        return $this->hasShowFieldDescription($name) ? $this->showFieldDescriptions[$name] : null;
1829
    }
1830
1831
    public function hasShowFieldDescription($name)
1832
    {
1833
        return array_key_exists($name, $this->showFieldDescriptions);
1834
    }
1835
1836
    public function addShowFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1837
    {
1838
        $this->showFieldDescriptions[$name] = $fieldDescription;
1839
    }
1840
1841
    public function removeShowFieldDescription($name)
1842
    {
1843
        unset($this->showFieldDescriptions[$name]);
1844
    }
1845
1846
    public function getListFieldDescriptions()
1847
    {
1848
        $this->buildList();
1849
1850
        return $this->listFieldDescriptions;
1851
    }
1852
1853
    public function getListFieldDescription($name)
1854
    {
1855
        return $this->hasListFieldDescription($name) ? $this->listFieldDescriptions[$name] : null;
1856
    }
1857
1858
    public function hasListFieldDescription($name)
1859
    {
1860
        $this->buildList();
1861
1862
        return array_key_exists($name, $this->listFieldDescriptions) ? true : false;
1863
    }
1864
1865
    public function addListFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1866
    {
1867
        $this->listFieldDescriptions[$name] = $fieldDescription;
1868
    }
1869
1870
    public function removeListFieldDescription($name)
1871
    {
1872
        unset($this->listFieldDescriptions[$name]);
1873
    }
1874
1875
    public function getFilterFieldDescription($name)
1876
    {
1877
        return $this->hasFilterFieldDescription($name) ? $this->filterFieldDescriptions[$name] : null;
1878
    }
1879
1880
    public function hasFilterFieldDescription($name)
1881
    {
1882
        return array_key_exists($name, $this->filterFieldDescriptions) ? true : false;
1883
    }
1884
1885
    public function addFilterFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1886
    {
1887
        $this->filterFieldDescriptions[$name] = $fieldDescription;
1888
    }
1889
1890
    public function removeFilterFieldDescription($name)
1891
    {
1892
        unset($this->filterFieldDescriptions[$name]);
1893
    }
1894
1895
    public function getFilterFieldDescriptions()
1896
    {
1897
        $this->buildDatagrid();
1898
1899
        return $this->filterFieldDescriptions;
1900
    }
1901
1902
    public function addChild(AdminInterface $child)
1903
    {
1904
        for ($parentAdmin = $this; null !== $parentAdmin; $parentAdmin = $parentAdmin->getParent()) {
1905
            if ($parentAdmin->getCode() !== $child->getCode()) {
1906
                continue;
1907
            }
1908
1909
            throw new \RuntimeException(sprintf(
1910
                'Circular reference detected! The child admin `%s` is already in the parent tree of the `%s` admin.',
1911
                $child->getCode(), $this->getCode()
1912
            ));
1913
        }
1914
1915
        $this->children[$child->getCode()] = $child;
1916
1917
        $child->setParent($this);
0 ignored issues
show
Documentation introduced by
$this is of type this<Sonata\AdminBundle\Admin\AbstractAdmin>, but the function expects a object<self>.

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...
1918
1919
        // NEXT_MAJOR: remove $args and add $field parameter to this function on next Major
1920
1921
        $args = \func_get_args();
1922
1923
        if (isset($args[1])) {
1924
            $child->addParentAssociationMapping($this->getCode(), $args[1]);
1925
        } else {
1926
            @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...
1927
                'Calling "addChild" without second argument is deprecated since 3.35'
1928
                .' and will not be allowed in 4.0.',
1929
                E_USER_DEPRECATED
1930
            );
1931
        }
1932
    }
1933
1934
    public function hasChild($code)
1935
    {
1936
        return isset($this->children[$code]);
1937
    }
1938
1939
    public function getChildren()
1940
    {
1941
        return $this->children;
1942
    }
1943
1944
    public function getChild($code)
1945
    {
1946
        return $this->hasChild($code) ? $this->children[$code] : null;
1947
    }
1948
1949
    public function setParent(AdminInterface $parent)
1950
    {
1951
        $this->parent = $parent;
1952
    }
1953
1954
    public function getParent()
1955
    {
1956
        return $this->parent;
1957
    }
1958
1959
    final public function getRootAncestor()
1960
    {
1961
        $parent = $this;
1962
1963
        while ($parent->isChild()) {
1964
            $parent = $parent->getParent();
1965
        }
1966
1967
        return $parent;
1968
    }
1969
1970
    final public function getChildDepth()
1971
    {
1972
        $parent = $this;
1973
        $depth = 0;
1974
1975
        while ($parent->isChild()) {
1976
            $parent = $parent->getParent();
1977
            ++$depth;
1978
        }
1979
1980
        return $depth;
1981
    }
1982
1983
    final public function getCurrentLeafChildAdmin()
1984
    {
1985
        $child = $this->getCurrentChildAdmin();
1986
1987
        if (null === $child) {
1988
            return;
1989
        }
1990
1991
        for ($c = $child; null !== $c; $c = $child->getCurrentChildAdmin()) {
1992
            $child = $c;
1993
        }
1994
1995
        return $child;
1996
    }
1997
1998
    public function isChild()
1999
    {
2000
        return $this->parent instanceof AdminInterface;
2001
    }
2002
2003
    /**
2004
     * Returns true if the admin has children, false otherwise.
2005
     *
2006
     * @return bool if the admin has children
2007
     */
2008
    public function hasChildren()
2009
    {
2010
        return count($this->children) > 0;
2011
    }
2012
2013
    public function setUniqid($uniqid)
2014
    {
2015
        $this->uniqid = $uniqid;
2016
    }
2017
2018
    public function getUniqid()
2019
    {
2020
        if (!$this->uniqid) {
2021
            $this->uniqid = 's'.crc32($this->getBaseCodeRoute());
2022
        }
2023
2024
        return $this->uniqid;
2025
    }
2026
2027
    /**
2028
     * Returns the classname label.
2029
     *
2030
     * @return string the classname label
2031
     */
2032
    public function getClassnameLabel()
2033
    {
2034
        return $this->classnameLabel;
2035
    }
2036
2037
    public function getPersistentParameters()
2038
    {
2039
        $parameters = [];
2040
2041
        foreach ($this->getExtensions() as $extension) {
0 ignored issues
show
Bug introduced by
The expression $this->getExtensions() of type object<Sonata\AdminBundl...minExtensionInterface>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
2042
            $params = [];
2043
2044
            // NEXT_MAJOR: check if extension implements GetPersistentParametersInterface before calling the method
2045
            if (method_exists($extension, 'getPersistentParameters')) {
2046
                $params = $extension->getPersistentParameters($this);
2047
            }
2048
2049
            // NEXT_MAJOR: remove this conditional
2050
            if (!$extension instanceof GetPersistentParametersInterface) {
2051
                $this->triggerExtensionDeprecationMessage(
2052
                    'getPersistentParameters',
2053
                    GetPersistentParametersInterface::class
2054
                );
2055
            }
2056
2057
            if (!is_array($params)) {
2058
                throw new \RuntimeException(sprintf('The %s::getPersistentParameters must return an array', get_class($extension)));
2059
            }
2060
2061
            $parameters = array_merge($parameters, $params);
2062
        }
2063
2064
        return $parameters;
2065
    }
2066
2067
    /**
2068
     * @param string $name
2069
     *
2070
     * @return null|mixed
2071
     */
2072
    public function getPersistentParameter($name)
2073
    {
2074
        $parameters = $this->getPersistentParameters();
2075
2076
        return isset($parameters[$name]) ? $parameters[$name] : null;
2077
    }
2078
2079
    public function getBreadcrumbs($action)
2080
    {
2081
        @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...
2082
            'The '.__METHOD__.' method is deprecated since version 3.2 and will be removed in 4.0.'.
2083
            ' Use Sonata\AdminBundle\Admin\BreadcrumbsBuilder::getBreadcrumbs instead.',
2084
            E_USER_DEPRECATED
2085
        );
2086
2087
        return $this->getBreadcrumbsBuilder()->getBreadcrumbs($this, $action);
2088
    }
2089
2090
    /**
2091
     * Generates the breadcrumbs array.
2092
     *
2093
     * Note: the method will be called by the top admin instance (parent => child)
2094
     *
2095
     * @param string $action
2096
     *
2097
     * @return array
2098
     */
2099
    public function buildBreadcrumbs($action, MenuItemInterface $menu = null)
2100
    {
2101
        @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...
2102
            'The '.__METHOD__.' method is deprecated since version 3.2 and will be removed in 4.0.',
2103
            E_USER_DEPRECATED
2104
        );
2105
2106
        if (isset($this->breadcrumbs[$action])) {
2107
            return $this->breadcrumbs[$action];
2108
        }
2109
2110
        return $this->breadcrumbs[$action] = $this->getBreadcrumbsBuilder()
2111
            ->buildBreadcrumbs($this, $action, $menu);
2112
    }
2113
2114
    /**
2115
     * NEXT_MAJOR : remove this method.
2116
     *
2117
     * @return BreadcrumbsBuilderInterface
2118
     */
2119
    final public function getBreadcrumbsBuilder()
2120
    {
2121
        @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...
2122
            'The '.__METHOD__.' method is deprecated since version 3.2 and will be removed in 4.0.'.
2123
            ' Use the sonata.admin.breadcrumbs_builder service instead.',
2124
            E_USER_DEPRECATED
2125
        );
2126
        if (null === $this->breadcrumbsBuilder) {
2127
            $this->breadcrumbsBuilder = new BreadcrumbsBuilder(
2128
                $this->getConfigurationPool()->getContainer()->getParameter('sonata.admin.configuration.breadcrumbs')
2129
            );
2130
        }
2131
2132
        return $this->breadcrumbsBuilder;
2133
    }
2134
2135
    /**
2136
     * NEXT_MAJOR : remove this method.
2137
     *
2138
     * @return AbstractAdmin
2139
     */
2140
    final public function setBreadcrumbsBuilder(BreadcrumbsBuilderInterface $value)
2141
    {
2142
        @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...
2143
            'The '.__METHOD__.' method is deprecated since version 3.2 and will be removed in 4.0.'.
2144
            ' Use the sonata.admin.breadcrumbs_builder service instead.',
2145
            E_USER_DEPRECATED
2146
        );
2147
        $this->breadcrumbsBuilder = $value;
2148
2149
        return $this;
2150
    }
2151
2152
    public function setCurrentChild($currentChild)
2153
    {
2154
        $this->currentChild = $currentChild;
2155
    }
2156
2157
    public function getCurrentChild()
2158
    {
2159
        return $this->currentChild;
2160
    }
2161
2162
    /**
2163
     * Returns the current child admin instance.
2164
     *
2165
     * @return AdminInterface|null the current child admin instance
2166
     */
2167
    public function getCurrentChildAdmin()
2168
    {
2169
        foreach ($this->children as $children) {
2170
            if ($children->getCurrentChild()) {
2171
                return $children;
2172
            }
2173
        }
2174
    }
2175
2176
    public function trans($id, array $parameters = [], $domain = null, $locale = null)
2177
    {
2178
        @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...
2179
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2180
            E_USER_DEPRECATED
2181
        );
2182
2183
        $domain = $domain ?: $this->getTranslationDomain();
2184
2185
        return $this->translator->trans($id, $parameters, $domain, $locale);
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tractAdmin::$translator has been deprecated with message: since 3.9, to be removed with 4.0

This property 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 property will be removed from the class and what other property to use instead.

Loading history...
2186
    }
2187
2188
    /**
2189
     * Translate a message id.
2190
     *
2191
     * NEXT_MAJOR: remove this method
2192
     *
2193
     * @param string      $id
2194
     * @param int         $count
2195
     * @param string|null $domain
2196
     * @param string|null $locale
2197
     *
2198
     * @return string the translated string
2199
     *
2200
     * @deprecated since 3.9, to be removed with 4.0
2201
     */
2202
    public function transChoice($id, $count, array $parameters = [], $domain = null, $locale = null)
2203
    {
2204
        @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...
2205
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2206
            E_USER_DEPRECATED
2207
        );
2208
2209
        $domain = $domain ?: $this->getTranslationDomain();
2210
2211
        return $this->translator->transChoice($id, $count, $parameters, $domain, $locale);
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tractAdmin::$translator has been deprecated with message: since 3.9, to be removed with 4.0

This property 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 property will be removed from the class and what other property to use instead.

Loading history...
2212
    }
2213
2214
    public function setTranslationDomain($translationDomain)
2215
    {
2216
        $this->translationDomain = $translationDomain;
2217
    }
2218
2219
    public function getTranslationDomain()
2220
    {
2221
        return $this->translationDomain;
2222
    }
2223
2224
    /**
2225
     * {@inheritdoc}
2226
     *
2227
     * NEXT_MAJOR: remove this method
2228
     *
2229
     * @deprecated since 3.9, to be removed with 4.0
2230
     */
2231
    public function setTranslator(TranslatorInterface $translator)
2232
    {
2233
        $args = func_get_args();
2234
        if (isset($args[1]) && $args[1]) {
2235
            @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...
2236
                'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2237
                E_USER_DEPRECATED
2238
            );
2239
        }
2240
2241
        $this->translator = $translator;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tractAdmin::$translator has been deprecated with message: since 3.9, to be removed with 4.0

This property 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 property will be removed from the class and what other property to use instead.

Loading history...
2242
    }
2243
2244
    /**
2245
     * {@inheritdoc}
2246
     *
2247
     * NEXT_MAJOR: remove this method
2248
     *
2249
     * @deprecated since 3.9, to be removed with 4.0
2250
     */
2251
    public function getTranslator()
2252
    {
2253
        @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...
2254
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2255
            E_USER_DEPRECATED
2256
        );
2257
2258
        return $this->translator;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...tractAdmin::$translator has been deprecated with message: since 3.9, to be removed with 4.0

This property 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 property will be removed from the class and what other property to use instead.

Loading history...
2259
    }
2260
2261
    public function getTranslationLabel($label, $context = '', $type = '')
2262
    {
2263
        return $this->getLabelTranslatorStrategy()->getLabel($label, $context, $type);
2264
    }
2265
2266
    public function setRequest(Request $request)
0 ignored issues
show
Bug introduced by
You have injected the Request via parameter $request. This is generally not recommended as there might be multiple instances during a request cycle (f.e. when using sub-requests). Instead, it is recommended to inject the RequestStack and retrieve the current request each time you need it via getCurrentRequest().
Loading history...
2267
    {
2268
        $this->request = $request;
2269
2270
        foreach ($this->getChildren() as $children) {
2271
            $children->setRequest($request);
2272
        }
2273
    }
2274
2275
    public function getRequest()
2276
    {
2277
        if (!$this->request) {
2278
            throw new \RuntimeException('The Request object has not been set');
2279
        }
2280
2281
        return $this->request;
2282
    }
2283
2284
    public function hasRequest()
2285
    {
2286
        return null !== $this->request;
2287
    }
2288
2289
    public function setFormContractor(FormContractorInterface $formBuilder)
2290
    {
2291
        $this->formContractor = $formBuilder;
2292
    }
2293
2294
    /**
2295
     * @return FormContractorInterface
2296
     */
2297
    public function getFormContractor()
2298
    {
2299
        return $this->formContractor;
2300
    }
2301
2302
    public function setDatagridBuilder(DatagridBuilderInterface $datagridBuilder)
2303
    {
2304
        $this->datagridBuilder = $datagridBuilder;
2305
    }
2306
2307
    public function getDatagridBuilder()
2308
    {
2309
        return $this->datagridBuilder;
2310
    }
2311
2312
    public function setListBuilder(ListBuilderInterface $listBuilder)
2313
    {
2314
        $this->listBuilder = $listBuilder;
2315
    }
2316
2317
    public function getListBuilder()
2318
    {
2319
        return $this->listBuilder;
2320
    }
2321
2322
    public function setShowBuilder(ShowBuilderInterface $showBuilder)
2323
    {
2324
        $this->showBuilder = $showBuilder;
2325
    }
2326
2327
    /**
2328
     * @return ShowBuilderInterface
2329
     */
2330
    public function getShowBuilder()
2331
    {
2332
        return $this->showBuilder;
2333
    }
2334
2335
    public function setConfigurationPool(Pool $configurationPool)
2336
    {
2337
        $this->configurationPool = $configurationPool;
2338
    }
2339
2340
    /**
2341
     * @return Pool
2342
     */
2343
    public function getConfigurationPool()
2344
    {
2345
        return $this->configurationPool;
2346
    }
2347
2348
    public function setRouteGenerator(RouteGeneratorInterface $routeGenerator)
2349
    {
2350
        $this->routeGenerator = $routeGenerator;
2351
    }
2352
2353
    /**
2354
     * @return RouteGeneratorInterface
2355
     */
2356
    public function getRouteGenerator()
2357
    {
2358
        return $this->routeGenerator;
2359
    }
2360
2361
    public function getCode()
2362
    {
2363
        return $this->code;
2364
    }
2365
2366
    /**
2367
     * NEXT_MAJOR: Remove this function.
2368
     *
2369
     * @deprecated This method is deprecated since 3.24 and will be removed in 4.0
2370
     *
2371
     * @param string $baseCodeRoute
2372
     */
2373
    public function setBaseCodeRoute($baseCodeRoute)
2374
    {
2375
        @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...
2376
            'The '.__METHOD__.' is deprecated since 3.24 and will be removed in 4.0.',
2377
            E_USER_DEPRECATED
2378
        );
2379
2380
        $this->baseCodeRoute = $baseCodeRoute;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...ctAdmin::$baseCodeRoute has been deprecated with message: This attribute is deprecated since 3.24 and will be removed in 4.0

This property 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 property will be removed from the class and what other property to use instead.

Loading history...
2381
    }
2382
2383
    public function getBaseCodeRoute()
2384
    {
2385
        // NEXT_MAJOR: Uncomment the following lines.
2386
        // if ($this->isChild()) {
2387
        //     return $this->getParent()->getBaseCodeRoute().'|'.$this->getCode();
2388
        // }
2389
        //
2390
        // return $this->getCode();
2391
2392
        // NEXT_MAJOR: Remove all the code below.
2393
        if ($this->isChild()) {
2394
            $parentCode = $this->getParent()->getCode();
2395
2396
            if ($this->getParent()->isChild()) {
2397
                $parentCode = $this->getParent()->getBaseCodeRoute();
2398
            }
2399
2400
            return $parentCode.'|'.$this->getCode();
2401
        }
2402
2403
        return $this->baseCodeRoute;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\AdminBundle\Admin...ctAdmin::$baseCodeRoute has been deprecated with message: This attribute is deprecated since 3.24 and will be removed in 4.0

This property 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 property will be removed from the class and what other property to use instead.

Loading history...
2404
    }
2405
2406
    public function getModelManager()
2407
    {
2408
        return $this->modelManager;
2409
    }
2410
2411
    public function setModelManager(ModelManagerInterface $modelManager)
2412
    {
2413
        $this->modelManager = $modelManager;
2414
    }
2415
2416
    public function getManagerType()
2417
    {
2418
        return $this->managerType;
2419
    }
2420
2421
    /**
2422
     * @param string $type
2423
     */
2424
    public function setManagerType($type)
2425
    {
2426
        $this->managerType = $type;
2427
    }
2428
2429
    public function getObjectIdentifier()
2430
    {
2431
        return $this->getCode();
2432
    }
2433
2434
    /**
2435
     * Set the roles and permissions per role.
2436
     */
2437
    public function setSecurityInformation(array $information)
2438
    {
2439
        $this->securityInformation = $information;
2440
    }
2441
2442
    public function getSecurityInformation()
2443
    {
2444
        return $this->securityInformation;
2445
    }
2446
2447
    /**
2448
     * Return the list of permissions the user should have in order to display the admin.
2449
     *
2450
     * @param string $context
2451
     *
2452
     * @return array
2453
     */
2454
    public function getPermissionsShow($context)
2455
    {
2456
        switch ($context) {
2457
            case self::CONTEXT_DASHBOARD:
2458
            case self::CONTEXT_MENU:
2459
            default:
2460
                return ['LIST'];
2461
        }
2462
    }
2463
2464
    public function showIn($context)
2465
    {
2466
        switch ($context) {
2467
            case self::CONTEXT_DASHBOARD:
2468
            case self::CONTEXT_MENU:
2469
            default:
2470
                return $this->isGranted($this->getPermissionsShow($context));
0 ignored issues
show
Documentation introduced by
$this->getPermissionsShow($context) is of type array<integer,string,{"0":"string"}>, 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...
2471
        }
2472
    }
2473
2474
    public function createObjectSecurity($object)
2475
    {
2476
        $this->getSecurityHandler()->createObjectSecurity($this, $object);
2477
    }
2478
2479
    public function setSecurityHandler(SecurityHandlerInterface $securityHandler)
2480
    {
2481
        $this->securityHandler = $securityHandler;
2482
    }
2483
2484
    public function getSecurityHandler()
2485
    {
2486
        return $this->securityHandler;
2487
    }
2488
2489
    public function isGranted($name, $object = null)
2490
    {
2491
        $key = md5(json_encode($name).($object ? '/'.spl_object_hash($object) : ''));
2492
2493
        if (!array_key_exists($key, $this->cacheIsGranted)) {
2494
            $this->cacheIsGranted[$key] = $this->securityHandler->isGranted($this, $name, $object ?: $this);
2495
        }
2496
2497
        return $this->cacheIsGranted[$key];
2498
    }
2499
2500
    public function getUrlsafeIdentifier($entity)
2501
    {
2502
        return $this->getModelManager()->getUrlsafeIdentifier($entity);
2503
    }
2504
2505
    public function getNormalizedIdentifier($entity)
2506
    {
2507
        return $this->getModelManager()->getNormalizedIdentifier($entity);
2508
    }
2509
2510
    public function id($entity)
2511
    {
2512
        return $this->getNormalizedIdentifier($entity);
2513
    }
2514
2515
    public function setValidator($validator)
2516
    {
2517
        // NEXT_MAJOR: Move ValidatorInterface check to method signature
2518
        if (!$validator instanceof ValidatorInterface) {
2519
            throw new \InvalidArgumentException(
2520
                'Argument 1 must be an instance of Symfony\Component\Validator\Validator\ValidatorInterface'
2521
            );
2522
        }
2523
2524
        $this->validator = $validator;
2525
    }
2526
2527
    public function getValidator()
2528
    {
2529
        return $this->validator;
2530
    }
2531
2532
    public function getShow()
2533
    {
2534
        $this->buildShow();
2535
2536
        return $this->show;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->show; (Sonata\AdminBundle\Admin...ldDescriptionCollection) is incompatible with the return type declared by the interface Sonata\AdminBundle\Admin\AdminInterface::getShow of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
2537
    }
2538
2539
    public function setFormTheme(array $formTheme)
2540
    {
2541
        $this->formTheme = $formTheme;
2542
    }
2543
2544
    public function getFormTheme()
2545
    {
2546
        return $this->formTheme;
2547
    }
2548
2549
    public function setFilterTheme(array $filterTheme)
2550
    {
2551
        $this->filterTheme = $filterTheme;
2552
    }
2553
2554
    public function getFilterTheme()
2555
    {
2556
        return $this->filterTheme;
2557
    }
2558
2559
    public function addExtension(OldAdminExtensionInterface $extension)
2560
    {
2561
        @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...
2562
            'Method "addExtension" is deprecated. Check "addAdminExtension".',
2563
            E_USER_DEPRECATED
2564
        );
2565
2566
        $this->extensions[] = $extension;
2567
    }
2568
2569
    /**
2570
     * NEXT_MAJOR: remove whole if and add only NewAdminExtensionInterface typehint.
2571
     *
2572
     * @param NewAdminExtensionInterface|OldAdminExtensionInterface $extension
2573
     */
2574
    public function addAdminExtension($extension)
2575
    {
2576
        if ($extension instanceof OldAdminExtensionInterface) {
2577
            @trigger_error(sprintf(
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...
2578
                'Extensions implementing "%s" are deprecated since 3.x and will be removed in 4.0.'
2579
                .' Implement single-method interfaces from "Sonata\AdminBundle\Admin\Extension" namespace.',
2580
                OldAdminExtensionInterface::class
2581
            ), E_USER_DEPRECATED);
2582
        } elseif (!$extension instanceof NewAdminExtensionInterface) {
2583
            throw new \InvalidArgumentException(
2584
                'Extension has to implement one of the interfaces in "Sonata\AdminBundle\Admin\Extension" namespace.'
2585
            );
2586
        }
2587
2588
        $this->extensions[] = $extension;
2589
    }
2590
2591
    public function getExtensions()
2592
    {
2593
        return $this->extensions;
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->extensions; of type Sonata\AdminBundle\Admin...minExtensionInterface[] adds the type Sonata\AdminBundle\Admin\AdminExtensionInterface to the return on line 2593 which is incompatible with the return type declared by the interface Sonata\AdminBundle\Admin...nterface::getExtensions of type Sonata\AdminBundle\Admin\AdminExtensionInterface[].
Loading history...
2594
    }
2595
2596
    public function setMenuFactory(MenuFactoryInterface $menuFactory)
2597
    {
2598
        $this->menuFactory = $menuFactory;
2599
    }
2600
2601
    public function getMenuFactory()
2602
    {
2603
        return $this->menuFactory;
2604
    }
2605
2606
    public function setRouteBuilder(RouteBuilderInterface $routeBuilder)
2607
    {
2608
        $this->routeBuilder = $routeBuilder;
2609
    }
2610
2611
    public function getRouteBuilder()
2612
    {
2613
        return $this->routeBuilder;
2614
    }
2615
2616
    public function toString($object)
2617
    {
2618
        if (!is_object($object)) {
2619
            return '';
2620
        }
2621
2622
        if (method_exists($object, '__toString') && null !== $object->__toString()) {
2623
            return (string) $object;
2624
        }
2625
2626
        return sprintf('%s:%s', ClassUtils::getClass($object), spl_object_hash($object));
2627
    }
2628
2629
    public function setLabelTranslatorStrategy(LabelTranslatorStrategyInterface $labelTranslatorStrategy)
2630
    {
2631
        $this->labelTranslatorStrategy = $labelTranslatorStrategy;
2632
    }
2633
2634
    public function getLabelTranslatorStrategy()
2635
    {
2636
        return $this->labelTranslatorStrategy;
2637
    }
2638
2639
    public function supportsPreviewMode()
2640
    {
2641
        return $this->supportsPreviewMode;
2642
    }
2643
2644
    /**
2645
     * Set custom per page options.
2646
     */
2647
    public function setPerPageOptions(array $options)
2648
    {
2649
        $this->perPageOptions = $options;
2650
    }
2651
2652
    /**
2653
     * Returns predefined per page options.
2654
     *
2655
     * @return array
2656
     */
2657
    public function getPerPageOptions()
2658
    {
2659
        return $this->perPageOptions;
2660
    }
2661
2662
    /**
2663
     * Set pager type.
2664
     *
2665
     * @param string $pagerType
2666
     */
2667
    public function setPagerType($pagerType)
2668
    {
2669
        $this->pagerType = $pagerType;
2670
    }
2671
2672
    /**
2673
     * Get pager type.
2674
     *
2675
     * @return string
2676
     */
2677
    public function getPagerType()
2678
    {
2679
        return $this->pagerType;
2680
    }
2681
2682
    /**
2683
     * Returns true if the per page value is allowed, false otherwise.
2684
     *
2685
     * @param int $perPage
2686
     *
2687
     * @return bool
2688
     */
2689
    public function determinedPerPageValue($perPage)
2690
    {
2691
        return in_array($perPage, $this->perPageOptions);
2692
    }
2693
2694
    public function isAclEnabled()
2695
    {
2696
        return $this->getSecurityHandler() instanceof AclSecurityHandlerInterface;
2697
    }
2698
2699
    public function getObjectMetadata($object)
2700
    {
2701
        return new Metadata($this->toString($object));
2702
    }
2703
2704
    public function getListModes()
2705
    {
2706
        return $this->listModes;
2707
    }
2708
2709
    public function setListMode($mode)
2710
    {
2711
        if (!$this->hasRequest()) {
2712
            throw new \RuntimeException(sprintf('No request attached to the current admin: %s', $this->getCode()));
2713
        }
2714
2715
        $this->getRequest()->getSession()->set(sprintf('%s.list_mode', $this->getCode()), $mode);
2716
    }
2717
2718
    public function getListMode()
2719
    {
2720
        if (!$this->hasRequest()) {
2721
            return 'list';
2722
        }
2723
2724
        return $this->getRequest()->getSession()->get(sprintf('%s.list_mode', $this->getCode()), 'list');
2725
    }
2726
2727
    public function getAccessMapping()
2728
    {
2729
        return $this->accessMapping;
2730
    }
2731
2732
    public function checkAccess($action, $object = null)
2733
    {
2734
        $access = $this->getAccess();
2735
2736
        if (!array_key_exists($action, $access)) {
2737
            throw new \InvalidArgumentException(sprintf(
2738
                'Action "%s" could not be found in access mapping.'
2739
                .' Please make sure your action is defined into your admin class accessMapping property.',
2740
                $action
2741
            ));
2742
        }
2743
2744
        if (!is_array($access[$action])) {
2745
            $access[$action] = [$access[$action]];
2746
        }
2747
2748
        foreach ($access[$action] as $role) {
2749
            if (false === $this->isGranted($role, $object)) {
2750
                throw new AccessDeniedException(sprintf('Access Denied to the action %s and role %s', $action, $role));
2751
            }
2752
        }
2753
    }
2754
2755
    /**
2756
     * Hook to handle access authorization, without throw Exception.
2757
     *
2758
     * @param string $action
2759
     * @param object $object
2760
     *
2761
     * @return bool
2762
     */
2763
    public function hasAccess($action, $object = null)
2764
    {
2765
        $access = $this->getAccess();
2766
2767
        if (!array_key_exists($action, $access)) {
2768
            return false;
2769
        }
2770
2771
        if (!is_array($access[$action])) {
2772
            $access[$action] = [$access[$action]];
2773
        }
2774
2775
        foreach ($access[$action] as $role) {
2776
            if (false === $this->isGranted($role, $object)) {
2777
                return false;
2778
            }
2779
        }
2780
2781
        return true;
2782
    }
2783
2784
    public function configureActionButtons($action, $object = null)
2785
    {
2786
        $list = [];
2787
2788
        if (in_array($action, ['tree', 'show', 'edit', 'delete', 'list', 'batch'])
2789
            && $this->hasAccess('create')
2790
            && $this->hasRoute('create')
2791
        ) {
2792
            $list['create'] = [
2793
                // NEXT_MAJOR: Remove this line and use commented line below it instead
2794
                'template' => $this->getTemplate('button_create'),
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...actAdmin::getTemplate() has been deprecated with message: since 3.34, will be dropped 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...
2795
//                'template' => $this->getTemplateRegistry()->getTemplate('button_create'),
2796
            ];
2797
        }
2798
2799
        if (in_array($action, ['show', 'delete', 'acl', 'history'])
2800
            && $this->canAccessObject('edit', $object)
2801
            && $this->hasRoute('edit')
2802
        ) {
2803
            $list['edit'] = [
2804
                // NEXT_MAJOR: Remove this line and use commented line below it instead
2805
                'template' => $this->getTemplate('button_edit'),
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...actAdmin::getTemplate() has been deprecated with message: since 3.34, will be dropped 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...
2806
                //'template' => $this->getTemplateRegistry()->getTemplate('button_edit'),
2807
            ];
2808
        }
2809
2810
        if (in_array($action, ['show', 'edit', 'acl'])
2811
            && $this->canAccessObject('history', $object)
2812
            && $this->hasRoute('history')
2813
        ) {
2814
            $list['history'] = [
2815
                // NEXT_MAJOR: Remove this line and use commented line below it instead
2816
                'template' => $this->getTemplate('button_history'),
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...actAdmin::getTemplate() has been deprecated with message: since 3.34, will be dropped 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...
2817
                // 'template' => $this->getTemplateRegistry()->getTemplate('button_history'),
2818
            ];
2819
        }
2820
2821
        if (in_array($action, ['edit', 'history'])
2822
            && $this->isAclEnabled()
2823
            && $this->canAccessObject('acl', $object)
2824
            && $this->hasRoute('acl')
2825
        ) {
2826
            $list['acl'] = [
2827
                // NEXT_MAJOR: Remove this line and use commented line below it instead
2828
                'template' => $this->getTemplate('button_acl'),
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...actAdmin::getTemplate() has been deprecated with message: since 3.34, will be dropped 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...
2829
                // 'template' => $this->getTemplateRegistry()->getTemplate('button_acl'),
2830
            ];
2831
        }
2832
2833
        if (in_array($action, ['edit', 'history', 'acl'])
2834
            && $this->canAccessObject('show', $object)
2835
            && count($this->getShow()) > 0
2836
            && $this->hasRoute('show')
2837
        ) {
2838
            $list['show'] = [
2839
                // NEXT_MAJOR: Remove this line and use commented line below it instead
2840
                'template' => $this->getTemplate('button_show'),
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...actAdmin::getTemplate() has been deprecated with message: since 3.34, will be dropped 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...
2841
                // 'template' => $this->getTemplateRegistry()->getTemplate('button_show'),
2842
            ];
2843
        }
2844
2845
        if (in_array($action, ['show', 'edit', 'delete', 'acl', 'batch'])
2846
            && $this->hasAccess('list')
2847
            && $this->hasRoute('list')
2848
        ) {
2849
            $list['list'] = [
2850
                // NEXT_MAJOR: Remove this line and use commented line below it instead
2851
                'template' => $this->getTemplate('button_list'),
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...actAdmin::getTemplate() has been deprecated with message: since 3.34, will be dropped 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...
2852
                // 'template' => $this->getTemplateRegistry()->getTemplate('button_list'),
2853
            ];
2854
        }
2855
2856
        return $list;
2857
    }
2858
2859
    /**
2860
     * @param string $action
2861
     * @param mixed  $object
2862
     *
2863
     * @return array
2864
     */
2865
    public function getActionButtons($action, $object = null)
2866
    {
2867
        $list = $this->configureActionButtons($action, $object);
2868
2869
        foreach ($this->getExtensions() as $extension) {
0 ignored issues
show
Bug introduced by
The expression $this->getExtensions() of type object<Sonata\AdminBundl...minExtensionInterface>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
2870
            // NEXT_MAJOR: check if extension implements ConfigureActionButtonsInterface before calling the method
2871
            if (method_exists($extension, 'configureActionButtons')) {
2872
                $list = $extension->configureActionButtons($this, $list, $action, $object);
2873
            }
2874
2875
            // NEXT_MAJOR: remove this conditional
2876
            if (!$extension instanceof ConfigureActionButtonsInterface) {
2877
                $this->triggerExtensionDeprecationMessage(
2878
                    'configureActionButtons',
2879
                    ConfigureActionButtonsInterface::class
2880
                );
2881
            }
2882
        }
2883
2884
        return $list;
2885
    }
2886
2887
    /**
2888
     * Get the list of actions that can be accessed directly from the dashboard.
2889
     *
2890
     * @return array
2891
     */
2892
    public function getDashboardActions()
2893
    {
2894
        $actions = [];
2895
2896
        if ($this->hasRoute('create') && $this->hasAccess('create')) {
2897
            $actions['create'] = [
2898
                'label' => 'link_add',
2899
                'translation_domain' => 'SonataAdminBundle',
2900
                // NEXT_MAJOR: Remove this line and use commented line below it instead
2901
                'template' => $this->getTemplate('action_create'),
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...actAdmin::getTemplate() has been deprecated with message: since 3.34, will be dropped 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...
2902
                // 'template' => $this->getTemplateRegistry()->getTemplate('action_create'),
2903
                'url' => $this->generateUrl('create'),
2904
                'icon' => 'plus-circle',
2905
            ];
2906
        }
2907
2908
        if ($this->hasRoute('list') && $this->hasAccess('list')) {
2909
            $actions['list'] = [
2910
                'label' => 'link_list',
2911
                'translation_domain' => 'SonataAdminBundle',
2912
                'url' => $this->generateUrl('list'),
2913
                'icon' => 'list',
2914
            ];
2915
        }
2916
2917
        return $actions;
2918
    }
2919
2920
    /**
2921
     * Setting to true will enable mosaic button for the admin screen.
2922
     * Setting to false will hide mosaic button for the admin screen.
2923
     *
2924
     * @param bool $isShown
2925
     */
2926
    final public function showMosaicButton($isShown)
2927
    {
2928
        if ($isShown) {
2929
            $this->listModes['mosaic'] = ['class' => static::MOSAIC_ICON_CLASS];
2930
        } else {
2931
            unset($this->listModes['mosaic']);
2932
        }
2933
    }
2934
2935
    /**
2936
     * @param FormMapper $form
0 ignored issues
show
Bug introduced by
There is no parameter named $form. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
2937
     */
2938
    final public function getSearchResultLink($object)
2939
    {
2940
        foreach ($this->searchResultActions as $action) {
2941
            if ($this->hasRoute($action) && $this->hasAccess($action, $object)) {
2942
                return $this->generateObjectUrl($action, $object);
2943
            }
2944
        }
2945
    }
2946
2947
    /**
2948
     * Checks if a filter type is set to a default value.
2949
     *
2950
     * @param string $name
2951
     *
2952
     * @return bool
2953
     */
2954
    final public function isDefaultFilter($name)
2955
    {
2956
        $filter = $this->getFilterParameters();
2957
        $default = $this->getDefaultFilterValues();
2958
2959
        if (!array_key_exists($name, $filter) || !array_key_exists($name, $default)) {
2960
            return false;
2961
        }
2962
2963
        return $filter[$name] == $default[$name];
2964
    }
2965
2966
    /**
2967
     * Check object existence and access, without throw Exception.
2968
     *
2969
     * @param string $action
2970
     * @param object $object
2971
     *
2972
     * @return bool
2973
     */
2974
    public function canAccessObject($action, $object)
2975
    {
2976
        return $object && $this->id($object) && $this->hasAccess($action, $object);
2977
    }
2978
2979
    /**
2980
     * @return MutableTemplateRegistryInterface
2981
     */
2982
    final protected function getTemplateRegistry()
2983
    {
2984
        return $this->templateRegistry;
2985
    }
2986
2987
    /**
2988
     * Returns a list of default filters.
2989
     *
2990
     * @return array
2991
     */
2992
    final protected function getDefaultFilterValues()
2993
    {
2994
        $defaultFilterValues = [];
2995
2996
        $this->configureDefaultFilterValues($defaultFilterValues);
2997
2998
        foreach ($this->getExtensions() as $extension) {
0 ignored issues
show
Bug introduced by
The expression $this->getExtensions() of type object<Sonata\AdminBundl...minExtensionInterface>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
2999
            // NEXT_MAJOR: check if extension implements ConfigureDefaultFilterValuesInterface before calling the method
3000
            if (method_exists($extension, 'configureDefaultFilterValues')) {
3001
                $extension->configureDefaultFilterValues($this, $defaultFilterValues);
3002
            }
3003
3004
            // NEXT_MAJOR: remove this conditional
3005
            if (!$extension instanceof ConfigureDefaultFilterValuesInterface) {
3006
                $this->triggerExtensionDeprecationMessage(
3007
                    'configureDefaultFilterValues',
3008
                    ConfigureDefaultFilterValuesInterface::class
3009
                );
3010
            }
3011
        }
3012
3013
        return $defaultFilterValues;
3014
    }
3015
3016
    protected function configureFormFields(FormMapper $form)
3017
    {
3018
    }
3019
3020
    protected function configureListFields(ListMapper $list)
3021
    {
3022
    }
3023
3024
    protected function configureDatagridFilters(DatagridMapper $filter)
3025
    {
3026
    }
3027
3028
    protected function configureShowFields(ShowMapper $show)
3029
    {
3030
    }
3031
3032
    protected function configureRoutes(RouteCollection $collection)
3033
    {
3034
    }
3035
3036
    /**
3037
     * Allows you to customize batch actions.
3038
     *
3039
     * @param array $actions List of actions
3040
     *
3041
     * @return array
3042
     */
3043
    protected function configureBatchActions($actions)
3044
    {
3045
        return $actions;
3046
    }
3047
3048
    /**
3049
     * NEXT_MAJOR: remove this method.
3050
     *
3051
     * @return mixed
3052
     *
3053
     * @deprecated Use configureTabMenu instead
3054
     */
3055
    protected function configureSideMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null)
0 ignored issues
show
Unused Code introduced by
The parameter $menu 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...
Unused Code introduced by
The parameter $action 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...
Unused Code introduced by
The parameter $childAdmin 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...
3056
    {
3057
    }
3058
3059
    /**
3060
     * Configures the tab menu in your admin.
3061
     *
3062
     * @param string $action
3063
     *
3064
     * @return mixed
3065
     */
3066
    protected function configureTabMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null)
3067
    {
3068
        // Use configureSideMenu not to mess with previous overrides
3069
        // TODO remove once deprecation period is over
3070
        $this->configureSideMenu($menu, $action, $childAdmin);
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...in::configureSideMenu() has been deprecated with message: Use configureTabMenu 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...
3071
    }
3072
3073
    /**
3074
     * build the view FieldDescription array.
3075
     */
3076
    protected function buildShow()
3077
    {
3078
        if ($this->show) {
3079
            return;
3080
        }
3081
3082
        $this->show = new FieldDescriptionCollection();
3083
        $mapper = new ShowMapper($this->showBuilder, $this->show, $this);
3084
3085
        $this->configureShowFields($mapper);
3086
3087
        foreach ($this->getExtensions() as $extension) {
0 ignored issues
show
Bug introduced by
The expression $this->getExtensions() of type object<Sonata\AdminBundl...minExtensionInterface>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
3088
            // NEXT_MAJOR: check if extension implements ConfigureShowFieldsInterface before calling the method
3089
            if (method_exists($extension, 'configureShowFields')) {
3090
                $extension->configureShowFields($mapper);
3091
            }
3092
3093
            // NEXT_MAJOR: remove this conditional
3094
            if (!$extension instanceof ConfigureShowFieldsInterface) {
3095
                $this->triggerExtensionDeprecationMessage(
3096
                    'configureShowFields',
3097
                    ConfigureShowFieldsInterface::class
3098
                );
3099
            }
3100
        }
3101
    }
3102
3103
    /**
3104
     * build the list FieldDescription array.
3105
     */
3106
    protected function buildList()
3107
    {
3108
        if ($this->list) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->list of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
3109
            return;
3110
        }
3111
3112
        $this->list = $this->getListBuilder()->getBaseList();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getListBuilder()->getBaseList() of type object<Sonata\AdminBundl...dDescriptionCollection> is incompatible with the declared type array of property $list.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
3113
3114
        $mapper = new ListMapper($this->getListBuilder(), $this->list, $this);
3115
3116
        if (count($this->getBatchActions()) > 0) {
3117
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
3118
                $this->getClass(),
3119
                'batch',
3120
                [
3121
                    'label' => 'batch',
3122
                    'code' => '_batch',
3123
                    'sortable' => false,
3124
                    'virtual_field' => true,
3125
                ]
3126
            );
3127
3128
            $fieldDescription->setAdmin($this);
3129
            // NEXT_MAJOR: Remove this line and use commented line below it instead
3130
            $fieldDescription->setTemplate($this->getTemplate('batch'));
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...actAdmin::getTemplate() has been deprecated with message: since 3.34, will be dropped 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...
3131
            // $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('batch'));
3132
3133
            $mapper->add($fieldDescription, 'batch');
0 ignored issues
show
Documentation introduced by
$fieldDescription is of type object<Sonata\AdminBundl...ldDescriptionInterface>, 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...
3134
        }
3135
3136
        $this->configureListFields($mapper);
3137
3138
        foreach ($this->getExtensions() as $extension) {
0 ignored issues
show
Bug introduced by
The expression $this->getExtensions() of type object<Sonata\AdminBundl...minExtensionInterface>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
3139
            // NEXT_MAJOR: check if extension implements ConfigureListFieldsInterface before calling the method
3140
            if (method_exists($extension, 'configureListFields')) {
3141
                $extension->configureListFields($mapper);
3142
            }
3143
3144
            // NEXT_MAJOR: remove this conditional
3145
            if (!$extension instanceof ConfigureListFieldsInterface) {
3146
                $this->triggerExtensionDeprecationMessage(
3147
                    'configureListFields',
3148
                    ConfigureListFieldsInterface::class
3149
                );
3150
            }
3151
        }
3152
3153
        if ($this->hasRequest() && $this->getRequest()->isXmlHttpRequest()) {
3154
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
3155
                $this->getClass(),
3156
                'select',
3157
                [
3158
                    'label' => false,
3159
                    'code' => '_select',
3160
                    'sortable' => false,
3161
                    'virtual_field' => false,
3162
                ]
3163
            );
3164
3165
            $fieldDescription->setAdmin($this);
3166
            // NEXT_MAJOR: Remove this line and use commented line below it instead
3167
            $fieldDescription->setTemplate($this->getTemplate('select'));
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...actAdmin::getTemplate() has been deprecated with message: since 3.34, will be dropped 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...
3168
            // $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('select'));
3169
3170
            $mapper->add($fieldDescription, 'select');
0 ignored issues
show
Documentation introduced by
$fieldDescription is of type object<Sonata\AdminBundl...ldDescriptionInterface>, 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...
3171
        }
3172
    }
3173
3174
    /**
3175
     * Build the form FieldDescription collection.
3176
     */
3177
    protected function buildForm()
3178
    {
3179
        if ($this->form) {
3180
            return;
3181
        }
3182
3183
        // append parent object if any
3184
        // todo : clean the way the Admin class can retrieve set the object
3185
        if ($this->isChild() && $this->getParentAssociationMapping()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->getParentAssociationMapping() of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null 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...
3186
            $parent = $this->getParent()->getObject($this->request->get($this->getParent()->getIdParameter()));
3187
3188
            $propertyAccessor = $this->getConfigurationPool()->getPropertyAccessor();
3189
            $propertyPath = new PropertyPath($this->getParentAssociationMapping());
3190
3191
            $object = $this->getSubject();
3192
3193
            $value = $propertyAccessor->getValue($object, $propertyPath);
3194
3195
            if (is_array($value) || ($value instanceof \Traversable && $value instanceof \ArrayAccess)) {
3196
                $value[] = $parent;
3197
                $propertyAccessor->setValue($object, $propertyPath, $value);
3198
            } else {
3199
                $propertyAccessor->setValue($object, $propertyPath, $parent);
3200
            }
3201
        }
3202
3203
        $formBuilder = $this->getFormBuilder();
3204
        $formBuilder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
3205
            $this->preValidate($event->getData());
3206
        }, 100);
3207
3208
        $this->form = $formBuilder->getForm();
3209
    }
3210
3211
    /**
3212
     * Gets the subclass corresponding to the given name.
3213
     *
3214
     * @param string $name The name of the sub class
3215
     *
3216
     * @return string the subclass
3217
     */
3218
    protected function getSubClass($name)
3219
    {
3220
        if ($this->hasSubClass($name)) {
3221
            return $this->subClasses[$name];
3222
        }
3223
3224
        throw new \RuntimeException(sprintf(
3225
            'Unable to find the subclass `%s` for admin `%s`',
3226
            $name,
3227
            get_class($this)
3228
        ));
3229
    }
3230
3231
    /**
3232
     * Attach the inline validator to the model metadata, this must be done once per admin.
3233
     */
3234
    protected function attachInlineValidator()
3235
    {
3236
        $admin = $this;
3237
3238
        // add the custom inline validation option
3239
        $metadata = $this->validator->getMetadataFor($this->getClass());
3240
3241
        $metadata->addConstraint(new InlineConstraint([
3242
            'service' => $this,
3243
            'method' => function (ErrorElement $errorElement, $object) use ($admin) {
3244
                /* @var \Sonata\AdminBundle\Admin\AdminInterface $admin */
3245
3246
                // This avoid the main validation to be cascaded to children
3247
                // The problem occurs when a model Page has a collection of Page as property
3248
                if ($admin->hasSubject() && spl_object_hash($object) !== spl_object_hash($admin->getSubject())) {
3249
                    return;
3250
                }
3251
3252
                $admin->validate($errorElement, $object);
3253
3254
                foreach ($admin->getExtensions() as $extension) {
0 ignored issues
show
Bug introduced by
The expression $admin->getExtensions() of type object<Sonata\AdminBundl...minExtensionInterface>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
3255
                    // NEXT_MAJOR: check if extension implements ValidateInterface before calling the method
3256
                    if (method_exists($extension, 'validate')) {
3257
                        $extension->validate($admin, $errorElement, $object);
3258
                    }
3259
3260
                    // NEXT_MAJOR: remove this conditional
3261
                    if (!$extension instanceof ValidateInterface) {
3262
                        $this->triggerExtensionDeprecationMessage(
3263
                            'validate',
3264
                            ValidateInterface::class
3265
                        );
3266
                    }
3267
                }
3268
            },
3269
            'serializingWarning' => true,
3270
        ]));
3271
    }
3272
3273
    /**
3274
     * Predefine per page options.
3275
     */
3276
    protected function predefinePerPageOptions()
3277
    {
3278
        array_unshift($this->perPageOptions, $this->maxPerPage);
3279
        $this->perPageOptions = array_unique($this->perPageOptions);
3280
        sort($this->perPageOptions);
3281
    }
3282
3283
    /**
3284
     * Return list routes with permissions name.
3285
     *
3286
     * @return array
3287
     */
3288
    protected function getAccess()
3289
    {
3290
        $access = array_merge([
3291
            'acl' => 'MASTER',
3292
            'export' => 'EXPORT',
3293
            'historyCompareRevisions' => 'EDIT',
3294
            'historyViewRevision' => 'EDIT',
3295
            'history' => 'EDIT',
3296
            'edit' => 'EDIT',
3297
            'show' => 'VIEW',
3298
            'create' => 'CREATE',
3299
            'delete' => 'DELETE',
3300
            'batchDelete' => 'DELETE',
3301
            'list' => 'LIST',
3302
        ], $this->getAccessMapping());
3303
3304
        foreach ($this->extensions as $extension) {
0 ignored issues
show
Bug introduced by
The expression $this->extensions of type object<Sonata\AdminBundl...minExtensionInterface>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
3305
            // NEXT_MAJOR: check if extension implements GetAccessMappingInterface before calling the method
3306
            if (method_exists($extension, 'getAccessMapping')) {
3307
                $access = array_merge($access, $extension->getAccessMapping($this));
3308
            }
3309
3310
            // NEXT_MAJOR: remove this conditional
3311
            if (!$extension instanceof GetAccessMappingInterface) {
3312
                $this->triggerExtensionDeprecationMessage('getAccessMapping', GetAccessMappingInterface::class);
3313
            }
3314
        }
3315
3316
        return $access;
3317
    }
3318
3319
    /**
3320
     * Returns a list of default filters.
3321
     */
3322
    protected function configureDefaultFilterValues(array &$filterValues)
3323
    {
3324
    }
3325
3326
    /**
3327
     * NEXT_MAJOR: remove this method.
3328
     *
3329
     * @param string $method
3330
     * @param string $class
3331
     */
3332
    private function triggerExtensionDeprecationMessage($method, $class)
3333
    {
3334
        @trigger_error(sprintf(
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...
3335
            'Calling "%s" without implementing "%s" in your extension is deprecated.',
3336
            $method,
3337
            $class
3338
        ), E_USER_DEPRECATED);
3339
    }
3340
3341
    /**
3342
     * Build all the related urls to the current admin.
3343
     */
3344
    private function buildRoutes()
3345
    {
3346
        if ($this->loaded['routes']) {
3347
            return;
3348
        }
3349
3350
        $this->loaded['routes'] = true;
3351
3352
        $this->routes = new RouteCollection(
3353
            $this->getBaseCodeRoute(),
3354
            $this->getBaseRouteName(),
3355
            $this->getBaseRoutePattern(),
3356
            $this->getBaseControllerName()
3357
        );
3358
3359
        $this->routeBuilder->build($this, $this->routes);
3360
3361
        $this->configureRoutes($this->routes);
3362
3363
        foreach ($this->getExtensions() as $extension) {
0 ignored issues
show
Bug introduced by
The expression $this->getExtensions() of type object<Sonata\AdminBundl...minExtensionInterface>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
3364
            // NEXT_MAJOR: check if extension implements ConfigureRoutesInterface before calling the method
3365
            if (method_exists($extension, 'configureRoutes')) {
3366
                $extension->configureRoutes($this, $this->routes);
3367
            }
3368
3369
            // NEXT_MAJOR: remove this conditional
3370
            if (!$extension instanceof ConfigureRoutesInterface) {
3371
                $this->triggerExtensionDeprecationMessage(
3372
                    'configureRoutes',
3373
                    ConfigureRoutesInterface::class
3374
                );
3375
            }
3376
        }
3377
    }
3378
}
3379