Completed
Push — master ( f169c2...fc1f10 )
by Grégoire
12s
created

AbstractAdmin::getFilterParameters()   D

Complexity

Conditions 9
Paths 21

Size

Total Lines 45
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 45
rs 4.909
c 0
b 0
f 0
cc 9
eloc 22
nc 21
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Sonata Project package.
7
 *
8
 * (c) Thomas Rabaix <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Sonata\AdminBundle\Admin;
15
16
use Doctrine\Common\Util\ClassUtils;
17
use Knp\Menu\FactoryInterface as MenuFactoryInterface;
18
use Knp\Menu\ItemInterface;
19
use Knp\Menu\ItemInterface as MenuItemInterface;
20
use Sonata\AdminBundle\Builder\DatagridBuilderInterface;
21
use Sonata\AdminBundle\Builder\FormContractorInterface;
22
use Sonata\AdminBundle\Builder\ListBuilderInterface;
23
use Sonata\AdminBundle\Builder\RouteBuilderInterface;
24
use Sonata\AdminBundle\Builder\ShowBuilderInterface;
25
use Sonata\AdminBundle\Datagrid\DatagridInterface;
26
use Sonata\AdminBundle\Datagrid\DatagridMapper;
27
use Sonata\AdminBundle\Datagrid\ListMapper;
28
use Sonata\AdminBundle\Datagrid\Pager;
29
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
30
use Sonata\AdminBundle\Filter\Persister\FilterPersisterInterface;
31
use Sonata\AdminBundle\Form\FormMapper;
32
use Sonata\AdminBundle\Form\Type\ModelHiddenType;
33
use Sonata\AdminBundle\Model\ModelManagerInterface;
34
use Sonata\AdminBundle\Route\RouteCollection;
35
use Sonata\AdminBundle\Route\RouteGeneratorInterface;
36
use Sonata\AdminBundle\Security\Handler\AclSecurityHandlerInterface;
37
use Sonata\AdminBundle\Security\Handler\SecurityHandlerInterface;
38
use Sonata\AdminBundle\Show\ShowMapper;
39
use Sonata\AdminBundle\Templating\MutableTemplateRegistryInterface;
40
use Sonata\AdminBundle\Translator\LabelTranslatorStrategyInterface;
41
use Sonata\CoreBundle\Model\Metadata;
42
use Sonata\CoreBundle\Validator\Constraints\InlineConstraint;
43
use Sonata\CoreBundle\Validator\ErrorElement;
44
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
45
use Symfony\Component\Form\Form;
46
use Symfony\Component\Form\FormBuilderInterface;
47
use Symfony\Component\Form\FormEvent;
48
use Symfony\Component\Form\FormEvents;
49
use Symfony\Component\HttpFoundation\Request;
50
use Symfony\Component\PropertyAccess\PropertyPath;
51
use Symfony\Component\Routing\Generator\UrlGeneratorInterface as RoutingUrlGeneratorInterface;
52
use Symfony\Component\Security\Acl\Model\DomainObjectInterface;
53
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
54
use Symfony\Component\Translation\TranslatorInterface;
55
use Symfony\Component\Validator\Validator\ValidatorInterface;
56
57
/**
58
 * @author Thomas Rabaix <[email protected]>
59
 */
60
abstract class AbstractAdmin implements AdminInterface, DomainObjectInterface, AdminTreeInterface
61
{
62
    public const CONTEXT_MENU = 'menu';
63
    public const CONTEXT_DASHBOARD = 'dashboard';
64
65
    public const CLASS_REGEX =
66
        '@
67
        (?:([A-Za-z0-9]*)\\\)?        # vendor name / app name
68
        (Bundle\\\)?                  # optional bundle directory
69
        ([A-Za-z0-9]+?)(?:Bundle)?\\\ # bundle name, with optional suffix
70
        (
71
            Entity|Document|Model|PHPCR|CouchDocument|Phpcr|
72
            Doctrine\\\Orm|Doctrine\\\Phpcr|Doctrine\\\MongoDB|Doctrine\\\CouchDB
73
        )\\\(.*)@x';
74
75
    public const MOSAIC_ICON_CLASS = 'fa fa-th-large fa-fw';
76
77
    /**
78
     * The list FieldDescription constructed from the configureListField method.
79
     *
80
     * @var array
81
     */
82
    protected $listFieldDescriptions = [];
83
84
    /**
85
     * The show FieldDescription constructed from the configureShowFields method.
86
     *
87
     * @var array
88
     */
89
    protected $showFieldDescriptions = [];
90
91
    /**
92
     * The list FieldDescription constructed from the configureFormField method.
93
     *
94
     * @var array
95
     */
96
    protected $formFieldDescriptions = [];
97
98
    /**
99
     * The filter FieldDescription constructed from the configureFilterField method.
100
     *
101
     * @var array
102
     */
103
    protected $filterFieldDescriptions = [];
104
105
    /**
106
     * The number of result to display in the list.
107
     *
108
     * @var int
109
     */
110
    protected $maxPerPage = 32;
111
112
    /**
113
     * The maximum number of page numbers to display in the list.
114
     *
115
     * @var int
116
     */
117
    protected $maxPageLinks = 25;
118
119
    /**
120
     * The base route name used to generate the routing information.
121
     *
122
     * @var string
123
     */
124
    protected $baseRouteName;
125
126
    /**
127
     * The base route pattern used to generate the routing information.
128
     *
129
     * @var string
130
     */
131
    protected $baseRoutePattern;
132
133
    /**
134
     * The base name controller used to generate the routing information.
135
     *
136
     * @var string
137
     */
138
    protected $baseControllerName;
139
140
    /**
141
     * The label class name  (used in the title/breadcrumb ...).
142
     *
143
     * @var string
144
     */
145
    protected $classnameLabel;
146
147
    /**
148
     * The translation domain to be used to translate messages.
149
     *
150
     * @var string
151
     */
152
    protected $translationDomain = 'messages';
153
154
    /**
155
     * Options to set to the form (ie, validation_groups).
156
     *
157
     * @var array
158
     */
159
    protected $formOptions = [];
160
161
    /**
162
     * Default values to the datagrid.
163
     *
164
     * @var array
165
     */
166
    protected $datagridValues = [
167
        '_page' => 1,
168
        '_per_page' => 32,
169
    ];
170
171
    /**
172
     * Predefined per page options.
173
     *
174
     * @var array
175
     */
176
    protected $perPageOptions = [16, 32, 64, 128, 192];
177
178
    /**
179
     * Pager type.
180
     *
181
     * @var string
182
     */
183
    protected $pagerType = Pager::TYPE_DEFAULT;
184
185
    /**
186
     * The code related to the admin.
187
     *
188
     * @var string
189
     */
190
    protected $code;
191
192
    /**
193
     * The label.
194
     *
195
     * @var string
196
     */
197
    protected $label;
198
199
    /**
200
     * Whether or not to persist the filters in the session.
201
     *
202
     * NEXT_MAJOR: remove this property
203
     *
204
     * @var bool
205
     *
206
     * @deprecated since 3.x, to be removed in 4.0.
207
     */
208
    protected $persistFilters = false;
209
210
    /**
211
     * Array of routes related to this admin.
212
     *
213
     * @var RouteCollection
214
     */
215
    protected $routes;
216
217
    /**
218
     * The subject only set in edit/update/create mode.
219
     *
220
     * @var object
221
     */
222
    protected $subject;
223
224
    /**
225
     * Define a Collection of child admin, ie /admin/order/{id}/order-element/{childId}.
226
     *
227
     * @var array
228
     */
229
    protected $children = [];
230
231
    /**
232
     * Reference the parent collection.
233
     *
234
     * @var AdminInterface|null
235
     */
236
    protected $parent = null;
237
238
    /**
239
     * The related parent association, ie if OrderElement has a parent property named order,
240
     * then the $parentAssociationMapping must be a string named `order`.
241
     *
242
     * @var string
243
     */
244
    protected $parentAssociationMapping = null;
245
246
    /**
247
     * Reference the parent FieldDescription related to this admin
248
     * only set for FieldDescription which is associated to an Sub Admin instance.
249
     *
250
     * @var FieldDescriptionInterface
251
     */
252
    protected $parentFieldDescription;
253
254
    /**
255
     * If true then the current admin is part of the nested admin set (from the url).
256
     *
257
     * @var bool
258
     */
259
    protected $currentChild = false;
260
261
    /**
262
     * The uniqid is used to avoid clashing with 2 admin related to the code
263
     * ie: a Block linked to a Block.
264
     *
265
     * @var string
266
     */
267
    protected $uniqid;
268
269
    /**
270
     * The Entity or Document manager.
271
     *
272
     * @var ModelManagerInterface
273
     */
274
    protected $modelManager;
275
276
    /**
277
     * The current request object.
278
     *
279
     * @var \Symfony\Component\HttpFoundation\Request
280
     */
281
    protected $request;
282
283
    /**
284
     * The translator component.
285
     *
286
     * NEXT_MAJOR: remove this property
287
     *
288
     * @var \Symfony\Component\Translation\TranslatorInterface
289
     *
290
     * @deprecated since 3.9, to be removed with 4.0
291
     */
292
    protected $translator;
293
294
    /**
295
     * The related form contractor.
296
     *
297
     * @var FormContractorInterface
298
     */
299
    protected $formContractor;
300
301
    /**
302
     * The related list builder.
303
     *
304
     * @var ListBuilderInterface
305
     */
306
    protected $listBuilder;
307
308
    /**
309
     * The related view builder.
310
     *
311
     * @var ShowBuilderInterface
312
     */
313
    protected $showBuilder;
314
315
    /**
316
     * The related datagrid builder.
317
     *
318
     * @var DatagridBuilderInterface
319
     */
320
    protected $datagridBuilder;
321
322
    /**
323
     * @var RouteBuilderInterface
324
     */
325
    protected $routeBuilder;
326
327
    /**
328
     * The datagrid instance.
329
     *
330
     * @var \Sonata\AdminBundle\Datagrid\DatagridInterface
331
     */
332
    protected $datagrid;
333
334
    /**
335
     * The router instance.
336
     *
337
     * @var RouteGeneratorInterface
338
     */
339
    protected $routeGenerator;
340
341
    /**
342
     * @var SecurityHandlerInterface
343
     */
344
    protected $securityHandler = null;
345
346
    /**
347
     * @var ValidatorInterface
348
     */
349
    protected $validator = null;
350
351
    /**
352
     * The configuration pool.
353
     *
354
     * @var Pool
355
     */
356
    protected $configurationPool;
357
358
    /**
359
     * @var MenuItemInterface
360
     */
361
    protected $menu;
362
363
    /**
364
     * @var MenuFactoryInterface
365
     */
366
    protected $menuFactory;
367
368
    /**
369
     * @var array
370
     */
371
    protected $loaded = [
372
        'view_fields' => false,
373
        'view_groups' => false,
374
        'routes' => false,
375
        'tab_menu' => false,
376
    ];
377
378
    /**
379
     * @var array
380
     */
381
    protected $formTheme = [];
382
383
    /**
384
     * @var array
385
     */
386
    protected $filterTheme = [];
387
388
    /**
389
     * @var array
390
     *
391
     * @deprecated since 3.x, will be dropped in 4.0. Use TemplateRegistry services instead
392
     */
393
    protected $templates = [];
394
395
    /**
396
     * @var AdminExtensionInterface[]
397
     */
398
    protected $extensions = [];
399
400
    /**
401
     * @var LabelTranslatorStrategyInterface
402
     */
403
    protected $labelTranslatorStrategy;
404
405
    /**
406
     * Setting to true will enable preview mode for
407
     * the entity and show a preview button in the
408
     * edit/create forms.
409
     *
410
     * @var bool
411
     */
412
    protected $supportsPreviewMode = false;
413
414
    /**
415
     * Roles and permissions per role.
416
     *
417
     * @var array 'role' => ['permission', 'permission']
418
     */
419
    protected $securityInformation = [];
420
421
    protected $cacheIsGranted = [];
422
423
    /**
424
     * Action list for the search result.
425
     *
426
     * @var string[]
427
     */
428
    protected $searchResultActions = ['edit', 'show'];
429
430
    protected $listModes = [
431
        'list' => [
432
            'class' => 'fa fa-list fa-fw',
433
        ],
434
        'mosaic' => [
435
            'class' => self::MOSAIC_ICON_CLASS,
436
        ],
437
    ];
438
439
    /**
440
     * The Access mapping.
441
     *
442
     * @var array [action1 => requiredRole1, action2 => [requiredRole2, requiredRole3]]
443
     */
444
    protected $accessMapping = [];
445
446
    /**
447
     * @var MutableTemplateRegistryInterface
448
     */
449
    private $templateRegistry;
450
451
    /**
452
     * The class name managed by the admin class.
453
     *
454
     * @var string
455
     */
456
    private $class;
457
458
    /**
459
     * The subclasses supported by the admin class.
460
     *
461
     * @var array
462
     */
463
    private $subClasses = [];
464
465
    /**
466
     * The list collection.
467
     *
468
     * @var array
469
     */
470
    private $list;
471
472
    /**
473
     * @var FieldDescriptionCollection
474
     */
475
    private $show;
476
477
    /**
478
     * @var Form
479
     */
480
    private $form;
481
482
    /**
483
     * @var DatagridInterface
484
     */
485
    private $filter;
486
487
    /**
488
     * The cached base route name.
489
     *
490
     * @var string
491
     */
492
    private $cachedBaseRouteName;
493
494
    /**
495
     * The cached base route pattern.
496
     *
497
     * @var string
498
     */
499
    private $cachedBaseRoutePattern;
500
501
    /**
502
     * The form group disposition.
503
     *
504
     * @var array|bool
505
     */
506
    private $formGroups = false;
507
508
    /**
509
     * The form tabs disposition.
510
     *
511
     * @var array|bool
512
     */
513
    private $formTabs = false;
514
515
    /**
516
     * The view group disposition.
517
     *
518
     * @var array|bool
519
     */
520
    private $showGroups = false;
521
522
    /**
523
     * The view tab disposition.
524
     *
525
     * @var array|bool
526
     */
527
    private $showTabs = false;
528
529
    /**
530
     * The manager type to use for the admin.
531
     *
532
     * @var string
533
     */
534
    private $managerType;
535
536
    /**
537
     * Component responsible for persisting filters.
538
     *
539
     * @var FilterPersisterInterface|null
540
     */
541
    private $filterPersister;
542
543
    /**
544
     * @param string $code
545
     * @param string $class
546
     * @param string $baseControllerName
547
     */
548
    public function __construct($code, $class, $baseControllerName)
549
    {
550
        $this->code = $code;
551
        $this->class = $class;
552
        $this->baseControllerName = $baseControllerName;
553
554
        $this->predefinePerPageOptions();
555
        $this->datagridValues['_per_page'] = $this->maxPerPage;
556
    }
557
558
    /**
559
     * {@inheritdoc}
560
     *
561
     * NEXT_MAJOR: return null to indicate no override
562
     */
563
    public function getExportFormats()
564
    {
565
        return [
566
            'json', 'xml', 'csv', 'xls',
567
        ];
568
    }
569
570
    /**
571
     * {@inheritdoc}
572
     */
573
    public function getExportFields()
574
    {
575
        $fields = $this->getModelManager()->getExportFields($this->getClass());
576
577
        foreach ($this->getExtensions() as $extension) {
578
            if (method_exists($extension, 'configureExportFields')) {
579
                $fields = $extension->configureExportFields($this, $fields);
580
            }
581
        }
582
583
        return $fields;
584
    }
585
586
    public function getDataSourceIterator()
587
    {
588
        $datagrid = $this->getDatagrid();
589
        $datagrid->buildPager();
590
591
        $fields = [];
592
593
        foreach ($this->getExportFields() as $key => $field) {
594
            $label = $this->getTranslationLabel($field, 'export', 'label');
595
            $transLabel = $this->trans($label);
596
597
            // NEXT_MAJOR: Remove this hack, because all field labels will be translated with the major release
598
            // No translation key exists
599
            if ($transLabel == $label) {
600
                $fields[$key] = $field;
601
            } else {
602
                $fields[$transLabel] = $field;
603
            }
604
        }
605
606
        return $this->getModelManager()->getDataSourceIterator($datagrid, $fields);
607
    }
608
609
    public function validate(ErrorElement $errorElement, $object): void
610
    {
611
    }
612
613
    /**
614
     * define custom variable.
615
     */
616
    public function initialize(): void
617
    {
618
        if (!$this->classnameLabel) {
619
            $this->classnameLabel = substr($this->getClass(), strrpos($this->getClass(), '\\') + 1);
620
        }
621
622
        $this->configure();
623
    }
624
625
    public function update($object)
626
    {
627
        $this->preUpdate($object);
628
        foreach ($this->extensions as $extension) {
629
            $extension->preUpdate($this, $object);
630
        }
631
632
        $result = $this->getModelManager()->update($object);
633
        // BC compatibility
634
        if (null !== $result) {
635
            $object = $result;
636
        }
637
638
        $this->postUpdate($object);
639
        foreach ($this->extensions as $extension) {
640
            $extension->postUpdate($this, $object);
641
        }
642
643
        return $object;
644
    }
645
646
    public function create($object)
647
    {
648
        $this->prePersist($object);
649
        foreach ($this->extensions as $extension) {
650
            $extension->prePersist($this, $object);
651
        }
652
653
        $result = $this->getModelManager()->create($object);
654
        // BC compatibility
655
        if (null !== $result) {
656
            $object = $result;
657
        }
658
659
        $this->postPersist($object);
660
        foreach ($this->extensions as $extension) {
661
            $extension->postPersist($this, $object);
662
        }
663
664
        $this->createObjectSecurity($object);
665
666
        return $object;
667
    }
668
669
    public function delete($object): void
670
    {
671
        $this->preRemove($object);
672
        foreach ($this->extensions as $extension) {
673
            $extension->preRemove($this, $object);
674
        }
675
676
        $this->getSecurityHandler()->deleteObjectSecurity($this, $object);
677
        $this->getModelManager()->delete($object);
678
679
        $this->postRemove($object);
680
        foreach ($this->extensions as $extension) {
681
            $extension->postRemove($this, $object);
682
        }
683
    }
684
685
    public function preValidate($object): void
686
    {
687
    }
688
689
    public function preUpdate($object): void
690
    {
691
    }
692
693
    public function postUpdate($object): void
694
    {
695
    }
696
697
    public function prePersist($object): void
698
    {
699
    }
700
701
    public function postPersist($object): void
702
    {
703
    }
704
705
    public function preRemove($object): void
706
    {
707
    }
708
709
    public function postRemove($object): void
710
    {
711
    }
712
713
    public function preBatchAction($actionName, ProxyQueryInterface $query, array &$idx, $allElements): void
714
    {
715
    }
716
717
    public function getFilterParameters()
718
    {
719
        $parameters = [];
720
721
        // build the values array
722
        if ($this->hasRequest()) {
723
            $filters = $this->request->query->get('filter', []);
724
725
            // if filter persistence is configured
726
            // NEXT_MAJOR: remove `$this->persistFilters !== false` from the condition
727
            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.x, 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...
728
                // if reset filters is asked, remove from storage
729
                if ('reset' === $this->request->query->get('filters')) {
730
                    $this->filterPersister->reset($this->getCode());
731
                }
732
733
                // if no filters, fetch from storage
734
                // otherwise save to storage
735
                if (empty($filters)) {
736
                    $filters = $this->filterPersister->get($this->getCode());
737
                } else {
738
                    $this->filterPersister->set($this->getCode(), $filters);
739
                }
740
            }
741
742
            $parameters = array_merge(
743
                $this->getModelManager()->getDefaultSortValues($this->getClass()),
744
                $this->datagridValues,
745
                $this->getDefaultFilterValues(),
746
                $filters
747
            );
748
749
            if (!$this->determinedPerPageValue($parameters['_per_page'])) {
750
                $parameters['_per_page'] = $this->maxPerPage;
751
            }
752
753
            // always force the parent value
754
            if ($this->isChild() && $this->getParentAssociationMapping()) {
755
                $name = str_replace('.', '__', $this->getParentAssociationMapping());
756
                $parameters[$name] = ['value' => $this->request->get($this->getParent()->getIdParameter())];
757
            }
758
        }
759
760
        return $parameters;
761
    }
762
763
    /**
764
     * Returns the name of the parent related field, so the field can be use to set the default
765
     * value (ie the parent object) or to filter the object.
766
     *
767
     * @return string the name of the parent related field
768
     */
769
    public function getParentAssociationMapping()
770
    {
771
        return $this->parentAssociationMapping;
772
    }
773
774
    /**
775
     * Returns the baseRoutePattern used to generate the routing information.
776
     *
777
     * @throws \RuntimeException
778
     *
779
     * @return string the baseRoutePattern used to generate the routing information
780
     */
781
    public function getBaseRoutePattern()
782
    {
783
        if (null !== $this->cachedBaseRoutePattern) {
784
            return $this->cachedBaseRoutePattern;
785
        }
786
787
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route pattern
788
            if (!$this->baseRoutePattern) {
789
                preg_match(self::CLASS_REGEX, $this->class, $matches);
790
791
                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...
792
                    throw new \RuntimeException(sprintf('Please define a default `baseRoutePattern` value for the admin class `%s`', get_class($this)));
793
                }
794
            }
795
796
            $this->cachedBaseRoutePattern = sprintf('%s/%s/%s',
797
                $this->getParent()->getBaseRoutePattern(),
798
                $this->getParent()->getRouterIdParameter(),
799
                $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...
800
            );
801
        } elseif ($this->baseRoutePattern) {
802
            $this->cachedBaseRoutePattern = $this->baseRoutePattern;
803
        } else {
804
            preg_match(self::CLASS_REGEX, $this->class, $matches);
805
806
            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...
807
                throw new \RuntimeException(sprintf('Please define a default `baseRoutePattern` value for the admin class `%s`', get_class($this)));
808
            }
809
810
            $this->cachedBaseRoutePattern = sprintf('/%s%s/%s',
811
                empty($matches[1]) ? '' : $this->urlize($matches[1], '-').'/',
812
                $this->urlize($matches[3], '-'),
813
                $this->urlize($matches[5], '-')
814
            );
815
        }
816
817
        return $this->cachedBaseRoutePattern;
818
    }
819
820
    /**
821
     * Returns the baseRouteName used to generate the routing information.
822
     *
823
     * @throws \RuntimeException
824
     *
825
     * @return string the baseRouteName used to generate the routing information
826
     */
827
    public function getBaseRouteName()
828
    {
829
        if (null !== $this->cachedBaseRouteName) {
830
            return $this->cachedBaseRouteName;
831
        }
832
833
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route name
834
            if (!$this->baseRouteName) {
835
                preg_match(self::CLASS_REGEX, $this->class, $matches);
836
837
                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...
838
                    throw new \RuntimeException(sprintf('Cannot automatically determine base route name, please define a default `baseRouteName` value for the admin class `%s`', get_class($this)));
839
                }
840
            }
841
842
            $this->cachedBaseRouteName = sprintf('%s_%s',
843
                $this->getParent()->getBaseRouteName(),
844
                $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...
845
            );
846
        } elseif ($this->baseRouteName) {
847
            $this->cachedBaseRouteName = $this->baseRouteName;
848
        } else {
849
            preg_match(self::CLASS_REGEX, $this->class, $matches);
850
851
            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...
852
                throw new \RuntimeException(sprintf('Cannot automatically determine base route name, please define a default `baseRouteName` value for the admin class `%s`', get_class($this)));
853
            }
854
855
            $this->cachedBaseRouteName = sprintf('admin_%s%s_%s',
856
                empty($matches[1]) ? '' : $this->urlize($matches[1]).'_',
857
                $this->urlize($matches[3]),
858
                $this->urlize($matches[5])
859
            );
860
        }
861
862
        return $this->cachedBaseRouteName;
863
    }
864
865
    public function getClass()
866
    {
867
        if ($this->hasActiveSubClass()) {
868
            if ($this->getParentFieldDescription()) {
869
                throw new \RuntimeException('Feature not implemented: an embedded admin cannot have subclass');
870
            }
871
872
            $subClass = $this->getRequest()->query->get('subclass');
873
874
            if (!$this->hasSubClass($subClass)) {
875
                throw new \RuntimeException(sprintf('Subclass "%" is not defined.', $subClass));
876
            }
877
878
            return $this->getSubClass($subClass);
879
        }
880
881
        // see https://github.com/sonata-project/SonataCoreBundle/commit/247eeb0a7ca7211142e101754769d70bc402a5b4
882
        if ($this->subject && is_object($this->subject)) {
883
            return ClassUtils::getClass($this->subject);
884
        }
885
886
        return $this->class;
887
    }
888
889
    public function getSubClasses()
890
    {
891
        return $this->subClasses;
892
    }
893
894
    /**
895
     * NEXT_MAJOR: remove this method.
896
     */
897
    public function addSubClass($subClass): void
898
    {
899
        @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...
900
            'Method "%s" is deprecated since 3.30 and will be removed in 4.0.',
901
            __METHOD__
902
        ), E_USER_DEPRECATED);
903
904
        if (!in_array($subClass, $this->subClasses)) {
905
            $this->subClasses[] = $subClass;
906
        }
907
    }
908
909
    public function setSubClasses(array $subClasses): void
910
    {
911
        $this->subClasses = $subClasses;
912
    }
913
914
    public function hasSubClass($name)
915
    {
916
        return isset($this->subClasses[$name]);
917
    }
918
919
    public function hasActiveSubClass()
920
    {
921
        if (count($this->subClasses) > 0 && $this->request) {
922
            return null !== $this->getRequest()->query->get('subclass');
923
        }
924
925
        return false;
926
    }
927
928
    public function getActiveSubClass()
929
    {
930
        if (!$this->hasActiveSubClass()) {
931
            return;
932
        }
933
934
        return $this->getSubClass($this->getActiveSubclassCode());
935
    }
936
937
    public function getActiveSubclassCode()
938
    {
939
        if (!$this->hasActiveSubClass()) {
940
            return;
941
        }
942
943
        $subClass = $this->getRequest()->query->get('subclass');
944
945
        if (!$this->hasSubClass($subClass)) {
946
            return;
947
        }
948
949
        return $subClass;
950
    }
951
952
    public function getBatchActions()
953
    {
954
        $actions = [];
955
956
        if ($this->hasRoute('delete') && $this->hasAccess('delete')) {
957
            $actions['delete'] = [
958
                'label' => 'action_delete',
959
                'translation_domain' => 'SonataAdminBundle',
960
                'ask_confirmation' => true, // by default always true
961
            ];
962
        }
963
964
        $actions = $this->configureBatchActions($actions);
965
966
        foreach ($this->getExtensions() as $extension) {
967
            $actions = $extension->configureBatchActions($this, $actions);
968
        }
969
970
        foreach ($actions  as $name => &$action) {
971
            if (!array_key_exists('label', $action)) {
972
                $action['label'] = $this->getTranslationLabel($name, 'batch', 'label');
973
            }
974
975
            if (!array_key_exists('translation_domain', $action)) {
976
                $action['translation_domain'] = $this->getTranslationDomain();
977
            }
978
        }
979
980
        return $actions;
981
    }
982
983
    public function getRoutes()
984
    {
985
        $this->buildRoutes();
986
987
        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...
988
    }
989
990
    public function getRouterIdParameter()
991
    {
992
        return '{'.$this->getIdParameter().'}';
993
    }
994
995
    public function getIdParameter()
996
    {
997
        $parameter = 'id';
998
999
        for ($i = 0; $i < $this->getChildDepth(); ++$i) {
1000
            $parameter = 'child'.ucfirst($parameter);
1001
        }
1002
1003
        return $parameter;
1004
    }
1005
1006
    public function hasRoute($name)
1007
    {
1008
        if (!$this->routeGenerator) {
1009
            throw new \RuntimeException('RouteGenerator cannot be null');
1010
        }
1011
1012
        return $this->routeGenerator->hasAdminRoute($this, $name);
1013
    }
1014
1015
    public function isCurrentRoute($name, $adminCode = null)
1016
    {
1017
        if (!$this->hasRequest()) {
1018
            return false;
1019
        }
1020
1021
        $request = $this->getRequest();
1022
        $route = $request->get('_route');
1023
1024
        if ($adminCode) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $adminCode of type string|null 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...
1025
            $admin = $this->getConfigurationPool()->getAdminByAdminCode($adminCode);
1026
        } else {
1027
            $admin = $this;
1028
        }
1029
1030
        if (!$admin) {
1031
            return false;
1032
        }
1033
1034
        return ($admin->getBaseRouteName().'_'.$name) == $route;
1035
    }
1036
1037
    public function generateObjectUrl($name, $object, array $parameters = [], $absolute = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1038
    {
1039
        $parameters['id'] = $this->getUrlsafeIdentifier($object);
1040
1041
        return $this->generateUrl($name, $parameters, $absolute);
1042
    }
1043
1044
    public function generateUrl($name, array $parameters = [], $absolute = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1045
    {
1046
        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...
1047
    }
1048
1049
    public function generateMenuUrl($name, array $parameters = [], $absolute = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1050
    {
1051
        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...
1052
    }
1053
1054
    final public function setTemplateRegistry(MutableTemplateRegistryInterface $templateRegistry): void
1055
    {
1056
        $this->templateRegistry = $templateRegistry;
1057
    }
1058
1059
    public function setTemplates(array $templates): void
1060
    {
1061
        // NEXT_MAJOR: Remove this line
1062
        $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.x, 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...
1063
1064
        $this->getTemplateRegistry()->setTemplates($templates);
1065
    }
1066
1067
    /**
1068
     * {@inheritdoc}
1069
     */
1070
    public function setTemplate($name, $template): void
1071
    {
1072
        // NEXT_MAJOR: Remove this line
1073
        $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.x, 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...
1074
1075
        $this->getTemplateRegistry()->setTemplate($name, $template);
1076
    }
1077
1078
    /**
1079
     * @deprecated since 3.x, will be dropped in 4.0. Use TemplateRegistry services instead
1080
     */
1081
    public function getTemplates(): array
1082
    {
1083
        return $this->getTemplateRegistry()->getTemplates();
1084
    }
1085
1086
    /**
1087
     * @deprecated since 3.x, will be dropped in 4.0. Use TemplateRegistry services instead
1088
     *
1089
     * @param string $name
1090
     *
1091
     * @return null|string
1092
     */
1093
    public function getTemplate($name)
1094
    {
1095
        return $this->getTemplateRegistry()->getTemplate($name);
1096
    }
1097
1098
    public function getNewInstance()
1099
    {
1100
        $object = $this->getModelManager()->getModelInstance($this->getClass());
1101
        foreach ($this->getExtensions() as $extension) {
1102
            $extension->alterNewInstance($this, $object);
1103
        }
1104
1105
        return $object;
1106
    }
1107
1108
    public function getFormBuilder()
1109
    {
1110
        $this->formOptions['data_class'] = $this->getClass();
1111
1112
        $formBuilder = $this->getFormContractor()->getFormBuilder(
1113
            $this->getUniqid(),
1114
            $this->formOptions
1115
        );
1116
1117
        $this->defineFormBuilder($formBuilder);
1118
1119
        return $formBuilder;
1120
    }
1121
1122
    /**
1123
     * This method is being called by the main admin class and the child class,
1124
     * the getFormBuilder is only call by the main admin class.
1125
     */
1126
    public function defineFormBuilder(FormBuilderInterface $formBuilder): void
1127
    {
1128
        $mapper = new FormMapper($this->getFormContractor(), $formBuilder, $this);
1129
1130
        $this->configureFormFields($mapper);
1131
1132
        foreach ($this->getExtensions() as $extension) {
1133
            $extension->configureFormFields($mapper);
1134
        }
1135
1136
        $this->attachInlineValidator();
1137
    }
1138
1139
    public function attachAdminClass(FieldDescriptionInterface $fieldDescription): void
1140
    {
1141
        $pool = $this->getConfigurationPool();
1142
1143
        $adminCode = $fieldDescription->getOption('admin_code');
1144
1145
        if (null !== $adminCode) {
1146
            $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...
1147
        } else {
1148
            $admin = $pool->getAdminByClass($fieldDescription->getTargetEntity());
1149
        }
1150
1151
        if (!$admin) {
1152
            return;
1153
        }
1154
1155
        if ($this->hasRequest()) {
1156
            $admin->setRequest($this->getRequest());
1157
        }
1158
1159
        $fieldDescription->setAssociationAdmin($admin);
1160
    }
1161
1162
    public function getObject($id)
1163
    {
1164
        $object = $this->getModelManager()->find($this->getClass(), $id);
1165
        foreach ($this->getExtensions() as $extension) {
1166
            $extension->alterObject($this, $object);
1167
        }
1168
1169
        return $object;
1170
    }
1171
1172
    public function getForm()
1173
    {
1174
        $this->buildForm();
1175
1176
        return $this->form;
1177
    }
1178
1179
    public function getList()
1180
    {
1181
        $this->buildList();
1182
1183
        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...
1184
    }
1185
1186
    public function createQuery($context = 'list')
1187
    {
1188
        if (func_num_args() > 0) {
1189
            @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...
1190
                'The $context argument of '.__METHOD__.' is deprecated since 3.3, to be removed in 4.0.',
1191
                E_USER_DEPRECATED
1192
            );
1193
        }
1194
        $query = $this->getModelManager()->createQuery($this->getClass());
1195
1196
        foreach ($this->extensions as $extension) {
1197
            $extension->configureQuery($this, $query, $context);
1198
        }
1199
1200
        return $query;
1201
    }
1202
1203
    public function getDatagrid()
1204
    {
1205
        $this->buildDatagrid();
1206
1207
        return $this->datagrid;
1208
    }
1209
1210
    public function buildTabMenu($action, AdminInterface $childAdmin = null): void
1211
    {
1212
        if ($this->loaded['tab_menu']) {
1213
            return;
1214
        }
1215
1216
        $this->loaded['tab_menu'] = true;
1217
1218
        $menu = $this->menuFactory->createItem('root');
1219
        $menu->setChildrenAttribute('class', 'nav navbar-nav');
1220
        $menu->setExtra('translation_domain', $this->translationDomain);
1221
1222
        // Prevents BC break with KnpMenuBundle v1.x
1223
        if (method_exists($menu, 'setCurrentUri')) {
1224
            $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...
1225
        }
1226
1227
        $this->configureTabMenu($menu, $action, $childAdmin);
1228
1229
        foreach ($this->getExtensions() as $extension) {
1230
            $extension->configureTabMenu($this, $menu, $action, $childAdmin);
1231
        }
1232
1233
        $this->menu = $menu;
1234
    }
1235
1236
    /**
1237
     * @param string $action
1238
     *
1239
     * @return ItemInterface
1240
     */
1241
    public function getSideMenu($action, AdminInterface $childAdmin = null)
1242
    {
1243
        if ($this->isChild()) {
1244
            return $this->getParent()->getSideMenu($action, $this);
1245
        }
1246
1247
        $this->buildTabMenu($action, $childAdmin);
1248
1249
        return $this->menu;
1250
    }
1251
1252
    /**
1253
     * Returns the root code.
1254
     *
1255
     * @return string the root code
1256
     */
1257
    public function getRootCode()
1258
    {
1259
        return $this->getRoot()->getCode();
1260
    }
1261
1262
    /**
1263
     * Returns the master admin.
1264
     *
1265
     * @return AbstractAdmin the root admin class
1266
     */
1267
    public function getRoot()
1268
    {
1269
        $parentFieldDescription = $this->getParentFieldDescription();
1270
1271
        if (!$parentFieldDescription) {
1272
            return $this;
1273
        }
1274
1275
        return $parentFieldDescription->getAdmin()->getRoot();
1276
    }
1277
1278
    public function setBaseControllerName($baseControllerName): void
1279
    {
1280
        $this->baseControllerName = $baseControllerName;
1281
    }
1282
1283
    public function getBaseControllerName()
1284
    {
1285
        return $this->baseControllerName;
1286
    }
1287
1288
    /**
1289
     * @param string $label
1290
     */
1291
    public function setLabel($label): void
1292
    {
1293
        $this->label = $label;
1294
    }
1295
1296
    public function getLabel()
1297
    {
1298
        return $this->label;
1299
    }
1300
1301
    /**
1302
     * @param bool $persist
1303
     *
1304
     * NEXT_MAJOR: remove this method
1305
     *
1306
     * @deprecated since 3.x, to be removed in 4.0.
1307
     */
1308
    public function setPersistFilters($persist): void
1309
    {
1310
        @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...
1311
            'The '.__METHOD__.' method is deprecated since version 3.x and will be removed in 4.0.',
1312
            E_USER_DEPRECATED
1313
        );
1314
1315
        $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.x, 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...
1316
    }
1317
1318
    /**
1319
     * @param FilterPersisterInterface|null $filterPersister
1320
     */
1321
    public function setFilterPersister(FilterPersisterInterface $filterPersister = null): void
1322
    {
1323
        $this->filterPersister = $filterPersister;
1324
    }
1325
1326
    /**
1327
     * @param int $maxPerPage
1328
     */
1329
    public function setMaxPerPage($maxPerPage): void
1330
    {
1331
        $this->maxPerPage = $maxPerPage;
1332
    }
1333
1334
    /**
1335
     * @return int
1336
     */
1337
    public function getMaxPerPage()
1338
    {
1339
        return $this->maxPerPage;
1340
    }
1341
1342
    /**
1343
     * @param int $maxPageLinks
1344
     */
1345
    public function setMaxPageLinks($maxPageLinks): void
1346
    {
1347
        $this->maxPageLinks = $maxPageLinks;
1348
    }
1349
1350
    /**
1351
     * @return int
1352
     */
1353
    public function getMaxPageLinks()
1354
    {
1355
        return $this->maxPageLinks;
1356
    }
1357
1358
    public function getFormGroups()
1359
    {
1360
        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 1360 which is incompatible with the return type declared by the interface Sonata\AdminBundle\Admin...nterface::getFormGroups of type array.
Loading history...
1361
    }
1362
1363
    public function setFormGroups(array $formGroups): void
1364
    {
1365
        $this->formGroups = $formGroups;
1366
    }
1367
1368
    public function removeFieldFromFormGroup($key): void
1369
    {
1370
        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...
1371
            unset($this->formGroups[$name]['fields'][$key]);
1372
1373
            if (empty($this->formGroups[$name]['fields'])) {
1374
                unset($this->formGroups[$name]);
1375
            }
1376
        }
1377
    }
1378
1379
    /**
1380
     * @param array $group
1381
     */
1382
    public function reorderFormGroup($group, array $keys): void
1383
    {
1384
        $formGroups = $this->getFormGroups();
1385
        $formGroups[$group]['fields'] = array_merge(array_flip($keys), $formGroups[$group]['fields']);
1386
        $this->setFormGroups($formGroups);
0 ignored issues
show
Bug introduced by
It seems like $formGroups defined by $this->getFormGroups() on line 1384 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...
1387
    }
1388
1389
    public function getFormTabs()
1390
    {
1391
        return $this->formTabs;
1392
    }
1393
1394
    public function setFormTabs(array $formTabs): void
1395
    {
1396
        $this->formTabs = $formTabs;
1397
    }
1398
1399
    public function getShowTabs()
1400
    {
1401
        return $this->showTabs;
1402
    }
1403
1404
    public function setShowTabs(array $showTabs): void
1405
    {
1406
        $this->showTabs = $showTabs;
1407
    }
1408
1409
    public function getShowGroups()
1410
    {
1411
        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 1411 which is incompatible with the return type declared by the interface Sonata\AdminBundle\Admin...nterface::getShowGroups of type array.
Loading history...
1412
    }
1413
1414
    public function setShowGroups(array $showGroups): void
1415
    {
1416
        $this->showGroups = $showGroups;
1417
    }
1418
1419
    public function reorderShowGroup($group, array $keys): void
1420
    {
1421
        $showGroups = $this->getShowGroups();
1422
        $showGroups[$group]['fields'] = array_merge(array_flip($keys), $showGroups[$group]['fields']);
1423
        $this->setShowGroups($showGroups);
0 ignored issues
show
Bug introduced by
It seems like $showGroups defined by $this->getShowGroups() on line 1421 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...
1424
    }
1425
1426
    public function setParentFieldDescription(FieldDescriptionInterface $parentFieldDescription): void
1427
    {
1428
        $this->parentFieldDescription = $parentFieldDescription;
1429
    }
1430
1431
    public function getParentFieldDescription()
1432
    {
1433
        return $this->parentFieldDescription;
1434
    }
1435
1436
    public function hasParentFieldDescription()
1437
    {
1438
        return $this->parentFieldDescription instanceof FieldDescriptionInterface;
1439
    }
1440
1441
    public function setSubject($subject): void
1442
    {
1443
        if (is_object($subject) && !is_a($subject, $this->getClass(), true)) {
1444
            $message = <<<'EOT'
1445
You are trying to set entity an instance of "%s",
1446
which is not the one registered with this admin class ("%s").
1447
This is deprecated since 3.5 and will no longer be supported in 4.0.
1448
EOT;
1449
1450
            @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...
1451
                sprintf($message, get_class($subject), $this->getClass()),
1452
                E_USER_DEPRECATED
1453
            ); // NEXT_MAJOR : throw an exception instead
1454
        }
1455
1456
        $this->subject = $subject;
1457
    }
1458
1459
    public function getSubject()
1460
    {
1461
        if (null === $this->subject && $this->request && !$this->hasParentFieldDescription()) {
1462
            $id = $this->request->get($this->getIdParameter());
1463
1464
            if (null !== $id) {
1465
                $this->subject = $this->getObject($id);
1466
            }
1467
        }
1468
1469
        return $this->subject;
1470
    }
1471
1472
    public function hasSubject()
1473
    {
1474
        return (bool) $this->getSubject();
1475
    }
1476
1477
    public function getFormFieldDescriptions()
1478
    {
1479
        $this->buildForm();
1480
1481
        return $this->formFieldDescriptions;
1482
    }
1483
1484
    public function getFormFieldDescription($name)
1485
    {
1486
        return $this->hasFormFieldDescription($name) ? $this->formFieldDescriptions[$name] : null;
1487
    }
1488
1489
    /**
1490
     * Returns true if the admin has a FieldDescription with the given $name.
1491
     *
1492
     * @param string $name
1493
     *
1494
     * @return bool
1495
     */
1496
    public function hasFormFieldDescription($name)
1497
    {
1498
        return array_key_exists($name, $this->formFieldDescriptions) ? true : false;
1499
    }
1500
1501
    public function addFormFieldDescription($name, FieldDescriptionInterface $fieldDescription): void
1502
    {
1503
        $this->formFieldDescriptions[$name] = $fieldDescription;
1504
    }
1505
1506
    /**
1507
     * remove a FieldDescription.
1508
     *
1509
     * @param string $name
1510
     */
1511
    public function removeFormFieldDescription($name): void
1512
    {
1513
        unset($this->formFieldDescriptions[$name]);
1514
    }
1515
1516
    /**
1517
     * build and return the collection of form FieldDescription.
1518
     *
1519
     * @return array collection of form FieldDescription
1520
     */
1521
    public function getShowFieldDescriptions()
1522
    {
1523
        $this->buildShow();
1524
1525
        return $this->showFieldDescriptions;
1526
    }
1527
1528
    /**
1529
     * Returns the form FieldDescription with the given $name.
1530
     *
1531
     * @param string $name
1532
     *
1533
     * @return FieldDescriptionInterface
1534
     */
1535
    public function getShowFieldDescription($name)
1536
    {
1537
        $this->buildShow();
1538
1539
        return $this->hasShowFieldDescription($name) ? $this->showFieldDescriptions[$name] : null;
1540
    }
1541
1542
    public function hasShowFieldDescription($name)
1543
    {
1544
        return array_key_exists($name, $this->showFieldDescriptions);
1545
    }
1546
1547
    public function addShowFieldDescription($name, FieldDescriptionInterface $fieldDescription): void
1548
    {
1549
        $this->showFieldDescriptions[$name] = $fieldDescription;
1550
    }
1551
1552
    public function removeShowFieldDescription($name): void
1553
    {
1554
        unset($this->showFieldDescriptions[$name]);
1555
    }
1556
1557
    public function getListFieldDescriptions()
1558
    {
1559
        $this->buildList();
1560
1561
        return $this->listFieldDescriptions;
1562
    }
1563
1564
    public function getListFieldDescription($name)
1565
    {
1566
        return $this->hasListFieldDescription($name) ? $this->listFieldDescriptions[$name] : null;
1567
    }
1568
1569
    public function hasListFieldDescription($name)
1570
    {
1571
        $this->buildList();
1572
1573
        return array_key_exists($name, $this->listFieldDescriptions) ? true : false;
1574
    }
1575
1576
    public function addListFieldDescription($name, FieldDescriptionInterface $fieldDescription): void
1577
    {
1578
        $this->listFieldDescriptions[$name] = $fieldDescription;
1579
    }
1580
1581
    public function removeListFieldDescription($name): void
1582
    {
1583
        unset($this->listFieldDescriptions[$name]);
1584
    }
1585
1586
    public function getFilterFieldDescription($name)
1587
    {
1588
        return $this->hasFilterFieldDescription($name) ? $this->filterFieldDescriptions[$name] : null;
1589
    }
1590
1591
    public function hasFilterFieldDescription($name)
1592
    {
1593
        return array_key_exists($name, $this->filterFieldDescriptions) ? true : false;
1594
    }
1595
1596
    public function addFilterFieldDescription($name, FieldDescriptionInterface $fieldDescription): void
1597
    {
1598
        $this->filterFieldDescriptions[$name] = $fieldDescription;
1599
    }
1600
1601
    public function removeFilterFieldDescription($name): void
1602
    {
1603
        unset($this->filterFieldDescriptions[$name]);
1604
    }
1605
1606
    public function getFilterFieldDescriptions()
1607
    {
1608
        $this->buildDatagrid();
1609
1610
        return $this->filterFieldDescriptions;
1611
    }
1612
1613
    public function addChild(AdminInterface $child): void
1614
    {
1615
        for ($parentAdmin = $this; null !== $parentAdmin; $parentAdmin = $parentAdmin->getParent()) {
1616
            if ($parentAdmin->getCode() !== $child->getCode()) {
1617
                continue;
1618
            }
1619
1620
            throw new \RuntimeException(sprintf(
1621
                'Circular reference detected! The child admin `%s` is already in the parent tree of the `%s` admin.',
1622
                $child->getCode(), $this->getCode()
1623
            ));
1624
        }
1625
1626
        $this->children[$child->getCode()] = $child;
1627
1628
        $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...
1629
    }
1630
1631
    public function hasChild($code)
1632
    {
1633
        return isset($this->children[$code]);
1634
    }
1635
1636
    public function getChildren()
1637
    {
1638
        return $this->children;
1639
    }
1640
1641
    public function getChild($code)
1642
    {
1643
        return $this->hasChild($code) ? $this->children[$code] : null;
1644
    }
1645
1646
    public function setParent(AdminInterface $parent): void
1647
    {
1648
        $this->parent = $parent;
1649
    }
1650
1651
    public function getParent()
1652
    {
1653
        return $this->parent;
1654
    }
1655
1656
    final public function getRootAncestor()
1657
    {
1658
        $parent = $this;
1659
1660
        while ($parent->isChild()) {
1661
            $parent = $parent->getParent();
1662
        }
1663
1664
        return $parent;
1665
    }
1666
1667
    final public function getChildDepth()
1668
    {
1669
        $parent = $this;
1670
        $depth = 0;
1671
1672
        while ($parent->isChild()) {
1673
            $parent = $parent->getParent();
1674
            ++$depth;
1675
        }
1676
1677
        return $depth;
1678
    }
1679
1680
    final public function getCurrentLeafChildAdmin()
1681
    {
1682
        $child = $this->getCurrentChildAdmin();
1683
1684
        if (null === $child) {
1685
            return;
1686
        }
1687
1688
        for ($c = $child; null !== $c; $c = $child->getCurrentChildAdmin()) {
1689
            $child = $c;
1690
        }
1691
1692
        return $child;
1693
    }
1694
1695
    public function isChild()
1696
    {
1697
        return $this->parent instanceof AdminInterface;
1698
    }
1699
1700
    /**
1701
     * Returns true if the admin has children, false otherwise.
1702
     *
1703
     * @return bool if the admin has children
1704
     */
1705
    public function hasChildren()
1706
    {
1707
        return count($this->children) > 0;
1708
    }
1709
1710
    public function setUniqid($uniqid): void
1711
    {
1712
        $this->uniqid = $uniqid;
1713
    }
1714
1715
    public function getUniqid()
1716
    {
1717
        if (!$this->uniqid) {
1718
            $this->uniqid = 's'.uniqid();
1719
        }
1720
1721
        return $this->uniqid;
1722
    }
1723
1724
    /**
1725
     * {@inheritdoc}
1726
     */
1727
    public function getClassnameLabel()
1728
    {
1729
        return $this->classnameLabel;
1730
    }
1731
1732
    public function getPersistentParameters()
1733
    {
1734
        $parameters = [];
1735
1736
        foreach ($this->getExtensions() as $extension) {
1737
            $params = $extension->getPersistentParameters($this);
1738
1739
            if (!is_array($params)) {
1740
                throw new \RuntimeException(sprintf('The %s::getPersistentParameters must return an array', get_class($extension)));
1741
            }
1742
1743
            $parameters = array_merge($parameters, $params);
1744
        }
1745
1746
        return $parameters;
1747
    }
1748
1749
    /**
1750
     * {@inheritdoc}
1751
     */
1752
    public function getPersistentParameter($name)
1753
    {
1754
        $parameters = $this->getPersistentParameters();
1755
1756
        return $parameters[$name] ?? null;
1757
    }
1758
1759
    public function setCurrentChild($currentChild): void
1760
    {
1761
        $this->currentChild = $currentChild;
1762
    }
1763
1764
    public function getCurrentChild()
1765
    {
1766
        return $this->currentChild;
1767
    }
1768
1769
    /**
1770
     * Returns the current child admin instance.
1771
     *
1772
     * @return AdminInterface|null the current child admin instance
1773
     */
1774
    public function getCurrentChildAdmin()
1775
    {
1776
        foreach ($this->children as $children) {
1777
            if ($children->getCurrentChild()) {
1778
                return $children;
1779
            }
1780
        }
1781
    }
1782
1783
    public function trans($id, array $parameters = [], $domain = null, $locale = null)
1784
    {
1785
        @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...
1786
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
1787
            E_USER_DEPRECATED
1788
        );
1789
1790
        $domain = $domain ?: $this->getTranslationDomain();
1791
1792
        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...
1793
    }
1794
1795
    /**
1796
     * Translate a message id.
1797
     *
1798
     * NEXT_MAJOR: remove this method
1799
     *
1800
     * @param string      $id
1801
     * @param int         $count
1802
     * @param string|null $domain
1803
     * @param string|null $locale
1804
     *
1805
     * @return string the translated string
1806
     *
1807
     * @deprecated since 3.9, to be removed with 4.0
1808
     */
1809
    public function transChoice($id, $count, array $parameters = [], $domain = null, $locale = null)
1810
    {
1811
        @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...
1812
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
1813
            E_USER_DEPRECATED
1814
        );
1815
1816
        $domain = $domain ?: $this->getTranslationDomain();
1817
1818
        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...
1819
    }
1820
1821
    public function setTranslationDomain($translationDomain): void
1822
    {
1823
        $this->translationDomain = $translationDomain;
1824
    }
1825
1826
    public function getTranslationDomain()
1827
    {
1828
        return $this->translationDomain;
1829
    }
1830
1831
    /**
1832
     * {@inheritdoc}
1833
     *
1834
     * NEXT_MAJOR: remove this method
1835
     *
1836
     * @deprecated since 3.9, to be removed with 4.0
1837
     */
1838
    public function setTranslator(TranslatorInterface $translator): void
1839
    {
1840
        $args = func_get_args();
1841
        if (isset($args[1]) && $args[1]) {
1842
            @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...
1843
                'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
1844
                E_USER_DEPRECATED
1845
            );
1846
        }
1847
1848
        $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...
1849
    }
1850
1851
    /**
1852
     * {@inheritdoc}
1853
     *
1854
     * NEXT_MAJOR: remove this method
1855
     *
1856
     * @deprecated since 3.9, to be removed with 4.0
1857
     */
1858
    public function getTranslator()
1859
    {
1860
        @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...
1861
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
1862
            E_USER_DEPRECATED
1863
        );
1864
1865
        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...
1866
    }
1867
1868
    public function getTranslationLabel($label, $context = '', $type = '')
1869
    {
1870
        return $this->getLabelTranslatorStrategy()->getLabel($label, $context, $type);
1871
    }
1872
1873
    public function setRequest(Request $request): void
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...
1874
    {
1875
        $this->request = $request;
1876
1877
        foreach ($this->getChildren() as $children) {
1878
            $children->setRequest($request);
1879
        }
1880
    }
1881
1882
    public function getRequest()
1883
    {
1884
        if (!$this->request) {
1885
            throw new \RuntimeException('The Request object has not been set');
1886
        }
1887
1888
        return $this->request;
1889
    }
1890
1891
    public function hasRequest()
1892
    {
1893
        return null !== $this->request;
1894
    }
1895
1896
    public function setFormContractor(FormContractorInterface $formBuilder): void
1897
    {
1898
        $this->formContractor = $formBuilder;
1899
    }
1900
1901
    /**
1902
     * @return FormContractorInterface
1903
     */
1904
    public function getFormContractor()
1905
    {
1906
        return $this->formContractor;
1907
    }
1908
1909
    public function setDatagridBuilder(DatagridBuilderInterface $datagridBuilder): void
1910
    {
1911
        $this->datagridBuilder = $datagridBuilder;
1912
    }
1913
1914
    public function getDatagridBuilder()
1915
    {
1916
        return $this->datagridBuilder;
1917
    }
1918
1919
    public function setListBuilder(ListBuilderInterface $listBuilder): void
1920
    {
1921
        $this->listBuilder = $listBuilder;
1922
    }
1923
1924
    public function getListBuilder()
1925
    {
1926
        return $this->listBuilder;
1927
    }
1928
1929
    public function setShowBuilder(ShowBuilderInterface $showBuilder): void
1930
    {
1931
        $this->showBuilder = $showBuilder;
1932
    }
1933
1934
    /**
1935
     * @return ShowBuilderInterface
1936
     */
1937
    public function getShowBuilder()
1938
    {
1939
        return $this->showBuilder;
1940
    }
1941
1942
    public function setConfigurationPool(Pool $configurationPool): void
1943
    {
1944
        $this->configurationPool = $configurationPool;
1945
    }
1946
1947
    /**
1948
     * @return Pool
1949
     */
1950
    public function getConfigurationPool()
1951
    {
1952
        return $this->configurationPool;
1953
    }
1954
1955
    public function setRouteGenerator(RouteGeneratorInterface $routeGenerator): void
1956
    {
1957
        $this->routeGenerator = $routeGenerator;
1958
    }
1959
1960
    /**
1961
     * @return RouteGeneratorInterface
1962
     */
1963
    public function getRouteGenerator()
1964
    {
1965
        return $this->routeGenerator;
1966
    }
1967
1968
    public function getCode()
1969
    {
1970
        return $this->code;
1971
    }
1972
1973
    public function getBaseCodeRoute()
1974
    {
1975
        if ($this->isChild()) {
1976
            return $this->getParent()->getBaseCodeRoute().'|'.$this->getCode();
1977
        }
1978
1979
        return $this->getCode();
1980
    }
1981
1982
    public function getModelManager()
1983
    {
1984
        return $this->modelManager;
1985
    }
1986
1987
    public function setModelManager(ModelManagerInterface $modelManager): void
1988
    {
1989
        $this->modelManager = $modelManager;
1990
    }
1991
1992
    public function getManagerType()
1993
    {
1994
        return $this->managerType;
1995
    }
1996
1997
    /**
1998
     * @param string $type
1999
     */
2000
    public function setManagerType($type): void
2001
    {
2002
        $this->managerType = $type;
2003
    }
2004
2005
    public function getObjectIdentifier()
2006
    {
2007
        return $this->getCode();
2008
    }
2009
2010
    /**
2011
     * Set the roles and permissions per role.
2012
     */
2013
    public function setSecurityInformation(array $information): void
2014
    {
2015
        $this->securityInformation = $information;
2016
    }
2017
2018
    public function getSecurityInformation()
2019
    {
2020
        return $this->securityInformation;
2021
    }
2022
2023
    /**
2024
     * Return the list of permissions the user should have in order to display the admin.
2025
     *
2026
     * @param string $context
2027
     *
2028
     * @return array
2029
     */
2030
    public function getPermissionsShow($context)
2031
    {
2032
        switch ($context) {
2033
            case self::CONTEXT_DASHBOARD:
2034
            case self::CONTEXT_MENU:
2035
            default:
2036
                return ['LIST'];
2037
        }
2038
    }
2039
2040
    public function showIn($context)
2041
    {
2042
        switch ($context) {
2043
            case self::CONTEXT_DASHBOARD:
2044
            case self::CONTEXT_MENU:
2045
            default:
2046
                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...
2047
        }
2048
    }
2049
2050
    public function createObjectSecurity($object): void
2051
    {
2052
        $this->getSecurityHandler()->createObjectSecurity($this, $object);
2053
    }
2054
2055
    public function setSecurityHandler(SecurityHandlerInterface $securityHandler): void
2056
    {
2057
        $this->securityHandler = $securityHandler;
2058
    }
2059
2060
    public function getSecurityHandler()
2061
    {
2062
        return $this->securityHandler;
2063
    }
2064
2065
    public function isGranted($name, $object = null)
2066
    {
2067
        $key = md5(json_encode($name).($object ? '/'.spl_object_hash($object) : ''));
2068
2069
        if (!array_key_exists($key, $this->cacheIsGranted)) {
2070
            $this->cacheIsGranted[$key] = $this->securityHandler->isGranted($this, $name, $object ?: $this);
2071
        }
2072
2073
        return $this->cacheIsGranted[$key];
2074
    }
2075
2076
    public function getUrlsafeIdentifier($entity)
2077
    {
2078
        return $this->getModelManager()->getUrlsafeIdentifier($entity);
2079
    }
2080
2081
    public function getNormalizedIdentifier($entity)
2082
    {
2083
        return $this->getModelManager()->getNormalizedIdentifier($entity);
2084
    }
2085
2086
    public function id($entity)
2087
    {
2088
        return $this->getNormalizedIdentifier($entity);
2089
    }
2090
2091
    public function setValidator($validator): void
2092
    {
2093
        // NEXT_MAJOR: Move ValidatorInterface check to method signature
2094
        if (!$validator instanceof ValidatorInterface) {
2095
            throw new \InvalidArgumentException(
2096
                'Argument 1 must be an instance of Symfony\Component\Validator\Validator\ValidatorInterface'
2097
            );
2098
        }
2099
2100
        $this->validator = $validator;
2101
    }
2102
2103
    public function getValidator()
2104
    {
2105
        return $this->validator;
2106
    }
2107
2108
    public function getShow()
2109
    {
2110
        $this->buildShow();
2111
2112
        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...
2113
    }
2114
2115
    public function setFormTheme(array $formTheme): void
2116
    {
2117
        $this->formTheme = $formTheme;
2118
    }
2119
2120
    public function getFormTheme()
2121
    {
2122
        return $this->formTheme;
2123
    }
2124
2125
    public function setFilterTheme(array $filterTheme): void
2126
    {
2127
        $this->filterTheme = $filterTheme;
2128
    }
2129
2130
    public function getFilterTheme()
2131
    {
2132
        return $this->filterTheme;
2133
    }
2134
2135
    public function addExtension(AdminExtensionInterface $extension): void
2136
    {
2137
        $this->extensions[] = $extension;
2138
    }
2139
2140
    public function getExtensions()
2141
    {
2142
        return $this->extensions;
2143
    }
2144
2145
    public function setMenuFactory(MenuFactoryInterface $menuFactory): void
2146
    {
2147
        $this->menuFactory = $menuFactory;
2148
    }
2149
2150
    public function getMenuFactory()
2151
    {
2152
        return $this->menuFactory;
2153
    }
2154
2155
    public function setRouteBuilder(RouteBuilderInterface $routeBuilder): void
2156
    {
2157
        $this->routeBuilder = $routeBuilder;
2158
    }
2159
2160
    public function getRouteBuilder()
2161
    {
2162
        return $this->routeBuilder;
2163
    }
2164
2165
    public function toString($object)
2166
    {
2167
        if (!is_object($object)) {
2168
            return '';
2169
        }
2170
2171
        if (method_exists($object, '__toString') && null !== $object->__toString()) {
2172
            return (string) $object;
2173
        }
2174
2175
        return sprintf('%s:%s', ClassUtils::getClass($object), spl_object_hash($object));
2176
    }
2177
2178
    public function setLabelTranslatorStrategy(LabelTranslatorStrategyInterface $labelTranslatorStrategy): void
2179
    {
2180
        $this->labelTranslatorStrategy = $labelTranslatorStrategy;
2181
    }
2182
2183
    public function getLabelTranslatorStrategy()
2184
    {
2185
        return $this->labelTranslatorStrategy;
2186
    }
2187
2188
    public function supportsPreviewMode()
2189
    {
2190
        return $this->supportsPreviewMode;
2191
    }
2192
2193
    /**
2194
     * Set custom per page options.
2195
     */
2196
    public function setPerPageOptions(array $options): void
2197
    {
2198
        $this->perPageOptions = $options;
2199
    }
2200
2201
    /**
2202
     * Returns predefined per page options.
2203
     *
2204
     * @return array
2205
     */
2206
    public function getPerPageOptions()
2207
    {
2208
        return $this->perPageOptions;
2209
    }
2210
2211
    /**
2212
     * Set pager type.
2213
     *
2214
     * @param string $pagerType
2215
     */
2216
    public function setPagerType($pagerType): void
2217
    {
2218
        $this->pagerType = $pagerType;
2219
    }
2220
2221
    /**
2222
     * Get pager type.
2223
     *
2224
     * @return string
2225
     */
2226
    public function getPagerType()
2227
    {
2228
        return $this->pagerType;
2229
    }
2230
2231
    /**
2232
     * Returns true if the per page value is allowed, false otherwise.
2233
     *
2234
     * @param int $perPage
2235
     *
2236
     * @return bool
2237
     */
2238
    public function determinedPerPageValue($perPage)
2239
    {
2240
        return in_array($perPage, $this->perPageOptions);
2241
    }
2242
2243
    public function isAclEnabled()
2244
    {
2245
        return $this->getSecurityHandler() instanceof AclSecurityHandlerInterface;
2246
    }
2247
2248
    public function getObjectMetadata($object)
2249
    {
2250
        return new Metadata($this->toString($object));
2251
    }
2252
2253
    public function getListModes()
2254
    {
2255
        return $this->listModes;
2256
    }
2257
2258
    public function setListMode($mode): void
2259
    {
2260
        if (!$this->hasRequest()) {
2261
            throw new \RuntimeException(sprintf('No request attached to the current admin: %s', $this->getCode()));
2262
        }
2263
2264
        $this->getRequest()->getSession()->set(sprintf('%s.list_mode', $this->getCode()), $mode);
2265
    }
2266
2267
    public function getListMode()
2268
    {
2269
        if (!$this->hasRequest()) {
2270
            return 'list';
2271
        }
2272
2273
        return $this->getRequest()->getSession()->get(sprintf('%s.list_mode', $this->getCode()), 'list');
2274
    }
2275
2276
    public function getAccessMapping()
2277
    {
2278
        return $this->accessMapping;
2279
    }
2280
2281
    public function checkAccess($action, $object = null): void
2282
    {
2283
        $access = $this->getAccess();
2284
2285
        if (!array_key_exists($action, $access)) {
2286
            throw new \InvalidArgumentException(sprintf(
2287
                'Action "%s" could not be found in access mapping.'
2288
                .' Please make sure your action is defined into your admin class accessMapping property.',
2289
                $action
2290
            ));
2291
        }
2292
2293
        if (!is_array($access[$action])) {
2294
            $access[$action] = [$access[$action]];
2295
        }
2296
2297
        foreach ($access[$action] as $role) {
2298
            if (false === $this->isGranted($role, $object)) {
2299
                throw new AccessDeniedException(sprintf('Access Denied to the action %s and role %s', $action, $role));
2300
            }
2301
        }
2302
    }
2303
2304
    /**
2305
     * {@inheritdoc}
2306
     */
2307
    public function hasAccess($action, $object = null)
2308
    {
2309
        $access = $this->getAccess();
2310
2311
        if (!array_key_exists($action, $access)) {
2312
            return false;
2313
        }
2314
2315
        if (!is_array($access[$action])) {
2316
            $access[$action] = [$access[$action]];
2317
        }
2318
2319
        foreach ($access[$action] as $role) {
2320
            if (false === $this->isGranted($role, $object)) {
2321
                return false;
2322
            }
2323
        }
2324
2325
        return true;
2326
    }
2327
2328
    final public function getActionButtons($action, $object = null)
2329
    {
2330
        $buttonList = [];
2331
2332
        if (in_array($action, ['tree', 'show', 'edit', 'delete', 'list', 'batch'])
2333
            && $this->hasAccess('create')
2334
            && $this->hasRoute('create')
2335
        ) {
2336
            $buttonList['create'] = [
2337
                'template' => $this->getTemplateRegistry()->getTemplate('button_create'),
2338
            ];
2339
        }
2340
2341
        if (in_array($action, ['show', 'delete', 'acl', 'history'])
2342
            && $this->canAccessObject('edit', $object)
2343
            && $this->hasRoute('edit')
2344
        ) {
2345
            $buttonList['edit'] = [
2346
                'template' => $this->getTemplateRegistry()->getTemplate('button_edit'),
2347
            ];
2348
        }
2349
2350
        if (in_array($action, ['show', 'edit', 'acl'])
2351
            && $this->canAccessObject('history', $object)
2352
            && $this->hasRoute('history')
2353
        ) {
2354
            $buttonList['history'] = [
2355
                'template' => $this->getTemplateRegistry()->getTemplate('button_history'),
2356
            ];
2357
        }
2358
2359
        if (in_array($action, ['edit', 'history'])
2360
            && $this->isAclEnabled()
2361
            && $this->canAccessObject('acl', $object)
2362
            && $this->hasRoute('acl')
2363
        ) {
2364
            $buttonList['acl'] = [
2365
                'template' => $this->getTemplateRegistry()->getTemplate('button_acl'),
2366
            ];
2367
        }
2368
2369
        if (in_array($action, ['edit', 'history', 'acl'])
2370
            && $this->canAccessObject('show', $object)
2371
            && count($this->getShow()) > 0
2372
            && $this->hasRoute('show')
2373
        ) {
2374
            $buttonList['show'] = [
2375
                'template' => $this->getTemplateRegistry()->getTemplate('button_show'),
2376
            ];
2377
        }
2378
2379
        if (in_array($action, ['show', 'edit', 'delete', 'acl', 'batch'])
2380
            && $this->hasAccess('list')
2381
            && $this->hasRoute('list')
2382
        ) {
2383
            $buttonList['list'] = [
2384
                'template' => $this->getTemplateRegistry()->getTemplate('button_list'),
2385
            ];
2386
        }
2387
2388
        $buttonList = $this->configureActionButtons($buttonList, $action, $object);
2389
2390
        foreach ($this->getExtensions() as $extension) {
2391
            $buttonList = $extension->configureActionButtons($this, $buttonList, $action, $object);
2392
        }
2393
2394
        return $buttonList;
2395
    }
2396
2397
    /**
2398
     * {@inheritdoc}
2399
     */
2400
    public function getDashboardActions()
2401
    {
2402
        $actions = [];
2403
2404
        if ($this->hasRoute('create') && $this->hasAccess('create')) {
2405
            $actions['create'] = [
2406
                'label' => 'link_add',
2407
                'translation_domain' => 'SonataAdminBundle',
2408
                'template' => $this->getTemplateRegistry()->getTemplate('action_create'),
2409
                'url' => $this->generateUrl('create'),
2410
                'icon' => 'plus-circle',
2411
            ];
2412
        }
2413
2414
        if ($this->hasRoute('list') && $this->hasAccess('list')) {
2415
            $actions['list'] = [
2416
                'label' => 'link_list',
2417
                'translation_domain' => 'SonataAdminBundle',
2418
                'url' => $this->generateUrl('list'),
2419
                'icon' => 'list',
2420
            ];
2421
        }
2422
2423
        return $actions;
2424
    }
2425
2426
    /**
2427
     * {@inheritdoc}
2428
     */
2429
    final public function showMosaicButton($isShown): void
2430
    {
2431
        if ($isShown) {
2432
            $this->listModes['mosaic'] = ['class' => self::MOSAIC_ICON_CLASS];
2433
        } else {
2434
            unset($this->listModes['mosaic']);
2435
        }
2436
    }
2437
2438
    /**
2439
     * {@inheritdoc}
2440
     */
2441
    final public function getSearchResultLink($object)
2442
    {
2443
        foreach ($this->searchResultActions as $action) {
2444
            if ($this->hasRoute($action) && $this->hasAccess($action, $object)) {
2445
                return $this->generateObjectUrl($action, $object);
2446
            }
2447
        }
2448
    }
2449
2450
    /**
2451
     * Checks if a filter type is set to a default value.
2452
     *
2453
     * @param string $name
2454
     *
2455
     * @return bool
2456
     */
2457
    final public function isDefaultFilter($name)
2458
    {
2459
        $filter = $this->getFilterParameters();
2460
        $default = $this->getDefaultFilterValues();
2461
2462
        if (!array_key_exists($name, $filter) || !array_key_exists($name, $default)) {
2463
            return false;
2464
        }
2465
2466
        return $filter[$name] == $default[$name];
2467
    }
2468
2469
    /**
2470
     * Check object existence and access, without throw Exception.
2471
     *
2472
     * @param string $action
2473
     * @param object $object
2474
     *
2475
     * @return bool
2476
     */
2477
    public function canAccessObject($action, $object)
2478
    {
2479
        return $object && $this->id($object) && $this->hasAccess($action, $object);
2480
    }
2481
2482
    /**
2483
     * Hook to run after initilization.
2484
     */
2485
    protected function configure(): void
2486
    {
2487
    }
2488
2489
    /**
2490
     * urlize the given word.
2491
     *
2492
     * @param string $word
2493
     * @param string $sep  the separator
2494
     *
2495
     * @return string
2496
     */
2497
    final protected function urlize($word, $sep = '_')
2498
    {
2499
        return strtolower(preg_replace('/[^a-z0-9_]/i', $sep.'$1', $word));
2500
    }
2501
2502
    final protected function getTemplateRegistry(): MutableTemplateRegistryInterface
2503
    {
2504
        return $this->templateRegistry;
2505
    }
2506
2507
    /**
2508
     * Returns a list of default filters.
2509
     *
2510
     * @return array
2511
     */
2512
    final protected function getDefaultFilterValues()
2513
    {
2514
        $defaultFilterValues = [];
2515
2516
        $this->configureDefaultFilterValues($defaultFilterValues);
2517
2518
        foreach ($this->getExtensions() as $extension) {
2519
            // NEXT_MAJOR: remove method check in next major release
2520
            if (method_exists($extension, 'configureDefaultFilterValues')) {
2521
                $extension->configureDefaultFilterValues($this, $defaultFilterValues);
2522
            }
2523
        }
2524
2525
        return $defaultFilterValues;
2526
    }
2527
2528
    protected function configureFormFields(FormMapper $form): void
2529
    {
2530
    }
2531
2532
    protected function configureListFields(ListMapper $list): void
2533
    {
2534
    }
2535
2536
    protected function configureDatagridFilters(DatagridMapper $filter): void
2537
    {
2538
    }
2539
2540
    protected function configureShowFields(ShowMapper $show): void
2541
    {
2542
    }
2543
2544
    protected function configureRoutes(RouteCollection $collection): void
2545
    {
2546
    }
2547
2548
    protected function configureActionButtons($buttonList, $action, $object = null)
0 ignored issues
show
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 $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...
2549
    {
2550
        return $buttonList;
2551
    }
2552
2553
    /**
2554
     * Allows you to customize batch actions.
2555
     *
2556
     * @param array $actions List of actions
2557
     *
2558
     * @return array
2559
     */
2560
    protected function configureBatchActions($actions)
2561
    {
2562
        return $actions;
2563
    }
2564
2565
    /**
2566
     * NEXT_MAJOR: remove this method.
2567
     *
2568
     * @return mixed
2569
     *
2570
     * @deprecated Use configureTabMenu instead
2571
     */
2572
    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...
2573
    {
2574
    }
2575
2576
    /**
2577
     * Configures the tab menu in your admin.
2578
     *
2579
     * @param string $action
2580
     *
2581
     * @return mixed
2582
     */
2583
    protected function configureTabMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null)
2584
    {
2585
        // Use configureSideMenu not to mess with previous overrides
2586
        // TODO remove once deprecation period is over
2587
        $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...
2588
    }
2589
2590
    /**
2591
     * build the view FieldDescription array.
2592
     */
2593
    protected function buildShow(): void
2594
    {
2595
        if ($this->show) {
2596
            return;
2597
        }
2598
2599
        $this->show = new FieldDescriptionCollection();
2600
        $mapper = new ShowMapper($this->showBuilder, $this->show, $this);
2601
2602
        $this->configureShowFields($mapper);
2603
2604
        foreach ($this->getExtensions() as $extension) {
2605
            $extension->configureShowFields($mapper);
2606
        }
2607
    }
2608
2609
    /**
2610
     * build the list FieldDescription array.
2611
     */
2612
    protected function buildList(): void
2613
    {
2614
        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...
2615
            return;
2616
        }
2617
2618
        $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...
2619
2620
        $mapper = new ListMapper($this->getListBuilder(), $this->list, $this);
2621
2622
        if (count($this->getBatchActions()) > 0) {
2623
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
2624
                $this->getClass(),
2625
                'batch',
2626
                [
2627
                    'label' => 'batch',
2628
                    'code' => '_batch',
2629
                    'sortable' => false,
2630
                    'virtual_field' => true,
2631
                ]
2632
            );
2633
2634
            $fieldDescription->setAdmin($this);
2635
            $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('batch'));
2636
2637
            $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...
2638
        }
2639
2640
        $this->configureListFields($mapper);
2641
2642
        foreach ($this->getExtensions() as $extension) {
2643
            $extension->configureListFields($mapper);
2644
        }
2645
2646
        if ($this->hasRequest() && $this->getRequest()->isXmlHttpRequest()) {
2647
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
2648
                $this->getClass(),
2649
                'select',
2650
                [
2651
                    'label' => false,
2652
                    'code' => '_select',
2653
                    'sortable' => false,
2654
                    'virtual_field' => false,
2655
                ]
2656
            );
2657
2658
            $fieldDescription->setAdmin($this);
2659
            $fieldDescription->setTemplate($this->getTemplateRegistry()->getTemplate('select'));
2660
2661
            $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...
2662
        }
2663
    }
2664
2665
    /**
2666
     * Build the form FieldDescription collection.
2667
     */
2668
    protected function buildForm(): void
2669
    {
2670
        if ($this->form) {
2671
            return;
2672
        }
2673
2674
        // append parent object if any
2675
        // todo : clean the way the Admin class can retrieve set the object
2676
        if ($this->isChild() && $this->getParentAssociationMapping()) {
2677
            $parent = $this->getParent()->getObject($this->request->get($this->getParent()->getIdParameter()));
2678
2679
            $propertyAccessor = $this->getConfigurationPool()->getPropertyAccessor();
2680
            $propertyPath = new PropertyPath($this->getParentAssociationMapping());
2681
2682
            $object = $this->getSubject();
2683
2684
            $value = $propertyAccessor->getValue($object, $propertyPath);
2685
2686
            if (is_array($value) || ($value instanceof \Traversable && $value instanceof \ArrayAccess)) {
2687
                $value[] = $parent;
2688
                $propertyAccessor->setValue($object, $propertyPath, $value);
2689
            } else {
2690
                $propertyAccessor->setValue($object, $propertyPath, $parent);
2691
            }
2692
        }
2693
2694
        $formBuilder = $this->getFormBuilder();
2695
        $formBuilder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event): void {
2696
            $this->preValidate($event->getData());
2697
        }, 100);
2698
2699
        $this->form = $formBuilder->getForm();
2700
    }
2701
2702
    /**
2703
     * Gets the subclass corresponding to the given name.
2704
     *
2705
     * @param string $name The name of the sub class
2706
     *
2707
     * @return string the subclass
2708
     */
2709
    protected function getSubClass($name)
2710
    {
2711
        if ($this->hasSubClass($name)) {
2712
            return $this->subClasses[$name];
2713
        }
2714
2715
        throw new \RuntimeException(sprintf(
2716
            'Unable to find the subclass `%s` for admin `%s`',
2717
            $name,
2718
            get_class($this)
2719
        ));
2720
    }
2721
2722
    /**
2723
     * Attach the inline validator to the model metadata, this must be done once per admin.
2724
     */
2725
    protected function attachInlineValidator(): void
2726
    {
2727
        $admin = $this;
2728
2729
        // add the custom inline validation option
2730
        $metadata = $this->validator->getMetadataFor($this->getClass());
2731
2732
        $metadata->addConstraint(new InlineConstraint([
2733
            'service' => $this,
2734
            'method' => function (ErrorElement $errorElement, $object) use ($admin): void {
2735
                /* @var \Sonata\AdminBundle\Admin\AdminInterface $admin */
2736
2737
                // This avoid the main validation to be cascaded to children
2738
                // The problem occurs when a model Page has a collection of Page as property
2739
                if ($admin->hasSubject() && spl_object_hash($object) !== spl_object_hash($admin->getSubject())) {
2740
                    return;
2741
                }
2742
2743
                $admin->validate($errorElement, $object);
2744
2745
                foreach ($admin->getExtensions() as $extension) {
2746
                    $extension->validate($admin, $errorElement, $object);
2747
                }
2748
            },
2749
            'serializingWarning' => true,
2750
        ]));
2751
    }
2752
2753
    /**
2754
     * Predefine per page options.
2755
     */
2756
    protected function predefinePerPageOptions(): void
2757
    {
2758
        array_unshift($this->perPageOptions, $this->maxPerPage);
2759
        $this->perPageOptions = array_unique($this->perPageOptions);
2760
        sort($this->perPageOptions);
2761
    }
2762
2763
    /**
2764
     * Return list routes with permissions name.
2765
     *
2766
     * @return array
2767
     */
2768
    protected function getAccess()
2769
    {
2770
        $access = array_merge([
2771
            'acl' => 'MASTER',
2772
            'export' => 'EXPORT',
2773
            'historyCompareRevisions' => 'EDIT',
2774
            'historyViewRevision' => 'EDIT',
2775
            'history' => 'EDIT',
2776
            'edit' => 'EDIT',
2777
            'show' => 'VIEW',
2778
            'create' => 'CREATE',
2779
            'delete' => 'DELETE',
2780
            'batchDelete' => 'DELETE',
2781
            'list' => 'LIST',
2782
        ], $this->getAccessMapping());
2783
2784
        foreach ($this->extensions as $extension) {
2785
            $access = array_merge($access, $extension->getAccessMapping($this));
2786
        }
2787
2788
        return $access;
2789
    }
2790
2791
    /**
2792
     * Returns a list of default filters.
2793
     */
2794
    protected function configureDefaultFilterValues(array &$filterValues): void
2795
    {
2796
    }
2797
2798
    /**
2799
     * {@inheritdoc}
2800
     */
2801
    private function buildDatagrid(): void
2802
    {
2803
        if ($this->datagrid) {
2804
            return;
2805
        }
2806
2807
        $filterParameters = $this->getFilterParameters();
2808
2809
        // transform _sort_by from a string to a FieldDescriptionInterface for the datagrid.
2810
        if (isset($filterParameters['_sort_by']) && is_string($filterParameters['_sort_by'])) {
2811
            if ($this->hasListFieldDescription($filterParameters['_sort_by'])) {
2812
                $filterParameters['_sort_by'] = $this->getListFieldDescription($filterParameters['_sort_by']);
2813
            } else {
2814
                $filterParameters['_sort_by'] = $this->getModelManager()->getNewFieldDescriptionInstance(
2815
                    $this->getClass(),
2816
                    $filterParameters['_sort_by'],
2817
                    []
2818
                );
2819
2820
                $this->getListBuilder()->buildField(null, $filterParameters['_sort_by'], $this);
2821
            }
2822
        }
2823
2824
        // initialize the datagrid
2825
        $this->datagrid = $this->getDatagridBuilder()->getBaseDatagrid($this, $filterParameters);
2826
2827
        $this->datagrid->getPager()->setMaxPageLinks($this->maxPageLinks);
2828
2829
        $mapper = new DatagridMapper($this->getDatagridBuilder(), $this->datagrid, $this);
2830
2831
        // build the datagrid filter
2832
        $this->configureDatagridFilters($mapper);
2833
2834
        // ok, try to limit to add parent filter
2835
        if ($this->isChild() && $this->getParentAssociationMapping() && !$mapper->has($this->getParentAssociationMapping())) {
2836
            $mapper->add($this->getParentAssociationMapping(), null, [
2837
                'show_filter' => false,
2838
                'label' => false,
2839
                'field_type' => ModelHiddenType::class,
2840
                'field_options' => [
2841
                    'model_manager' => $this->getModelManager(),
2842
                ],
2843
                'operator_type' => HiddenType::class,
2844
            ], null, null, [
2845
                'admin_code' => $this->getParent()->getCode(),
2846
            ]);
2847
        }
2848
2849
        foreach ($this->getExtensions() as $extension) {
2850
            $extension->configureDatagridFilters($mapper);
2851
        }
2852
    }
2853
2854
    /**
2855
     * Build all the related urls to the current admin.
2856
     */
2857
    private function buildRoutes(): void
2858
    {
2859
        if ($this->loaded['routes']) {
2860
            return;
2861
        }
2862
2863
        $this->loaded['routes'] = true;
2864
2865
        $this->routes = new RouteCollection(
2866
            $this->getBaseCodeRoute(),
2867
            $this->getBaseRouteName(),
2868
            $this->getBaseRoutePattern(),
2869
            $this->getBaseControllerName()
2870
        );
2871
2872
        $this->routeBuilder->build($this, $this->routes);
2873
2874
        $this->configureRoutes($this->routes);
2875
2876
        foreach ($this->getExtensions() as $extension) {
2877
            $extension->configureRoutes($this, $this->routes);
2878
        }
2879
    }
2880
}
2881