Completed
Push — master ( 2e2098...52aca0 )
by Jordi Sala
03:56
created

AbstractAdmin   F

Complexity

Total Complexity 413

Size/Duplication

Total Lines 3237
Duplicated Lines 0 %

Coupling/Cohesion

Components 4
Dependencies 32

Importance

Changes 0
Metric Value
wmc 413
lcom 4
cbo 32
dl 0
loc 3237
rs 0.5217
c 0
b 0
f 0

218 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
A getExportFormats() 0 6 1
A getExportFields() 0 12 3
A getDataSourceIterator() 0 22 3
A validate() 0 3 1
A initialize() 0 8 2
A update() 0 20 4
B create() 0 22 4
A delete() 0 15 3
A preValidate() 0 3 1
A preUpdate() 0 3 1
A postUpdate() 0 3 1
A prePersist() 0 3 1
A postPersist() 0 3 1
A preRemove() 0 3 1
A postRemove() 0 3 1
A preBatchAction() 0 3 1
C getFilterParameters() 0 37 8
A getParentAssociationMapping() 0 4 1
D getBaseRoutePattern() 0 38 9
D getBaseRouteName() 0 37 9
D getClass() 0 27 9
A getSubClasses() 0 4 1
A addSubClass() 0 6 2
A setSubClasses() 0 4 1
A hasSubClass() 0 4 1
A hasActiveSubClass() 0 8 3
A getActiveSubClass() 0 8 2
A getActiveSubclassCode() 0 14 3
C getBatchActions() 0 30 7
A getRoutes() 0 6 1
A getRouterIdParameter() 0 4 1
A getIdParameter() 0 10 2
A hasRoute() 0 8 2
A isCurrentRoute() 0 21 4
A generateObjectUrl() 0 6 1
A generateUrl() 0 4 1
A generateMenuUrl() 0 4 1
A setTemplates() 0 4 1
A setTemplate() 0 4 1
A getTemplates() 0 4 1
A getTemplate() 0 6 2
A getNewInstance() 0 9 2
A getFormBuilder() 0 13 1
A defineFormBuilder() 0 12 2
B attachAdminClass() 0 22 4
A getObject() 0 9 2
A getForm() 0 6 1
A getList() 0 6 1
A createQuery() 0 16 3
A getDatagrid() 0 6 1
B buildTabMenu() 0 25 4
A getSideMenu() 0 10 2
A getRootCode() 0 4 1
A getRoot() 0 10 2
A setBaseControllerName() 0 4 1
A getBaseControllerName() 0 4 1
A setLabel() 0 4 1
A getLabel() 0 4 1
A setPersistFilters() 0 4 1
A setMaxPerPage() 0 4 1
A getMaxPerPage() 0 4 1
A setMaxPageLinks() 0 4 1
A getMaxPageLinks() 0 4 1
A getFormGroups() 0 4 1
A setFormGroups() 0 4 1
A removeFieldFromFormGroup() 0 10 3
A reorderFormGroup() 0 6 1
A getFormTabs() 0 4 1
A setFormTabs() 0 4 1
A getShowTabs() 0 4 1
A setShowTabs() 0 4 1
A getShowGroups() 0 4 1
A setShowGroups() 0 4 1
A reorderShowGroup() 0 6 1
A setParentFieldDescription() 0 4 1
A getParentFieldDescription() 0 4 1
A hasParentFieldDescription() 0 4 1
A setSubject() 0 17 3
A getSubject() 0 9 4
A hasSubject() 0 4 1
A getFormFieldDescriptions() 0 6 1
A getFormFieldDescription() 0 4 2
A hasFormFieldDescription() 0 4 2
A addFormFieldDescription() 0 4 1
A removeFormFieldDescription() 0 4 1
A getShowFieldDescriptions() 0 6 1
A getShowFieldDescription() 0 6 2
A hasShowFieldDescription() 0 4 1
A addShowFieldDescription() 0 4 1
A removeShowFieldDescription() 0 4 1
A getListFieldDescriptions() 0 6 1
A getListFieldDescription() 0 4 2
A hasListFieldDescription() 0 6 2
A addListFieldDescription() 0 4 1
A removeListFieldDescription() 0 4 1
A getFilterFieldDescription() 0 4 2
A hasFilterFieldDescription() 0 4 2
A addFilterFieldDescription() 0 4 1
A removeFilterFieldDescription() 0 4 1
A getFilterFieldDescriptions() 0 6 1
A addChild() 0 17 3
A hasChild() 0 4 1
A getChildren() 0 4 1
A getChild() 0 4 2
A setParent() 0 4 1
A getParent() 0 4 1
A getRootAncestor() 0 10 2
A getChildDepth() 0 12 2
A getCurrentLeafChildAdmin() 0 14 3
A isChild() 0 4 1
A hasChildren() 0 4 1
A setUniqid() 0 4 1
A getUniqid() 0 8 2
A getClassnameLabel() 0 4 1
A getPersistentParameters() 0 16 3
A getPersistentParameter() 0 6 2
A setCurrentChild() 0 4 1
A getCurrentChild() 0 4 1
A getCurrentChildAdmin() 0 10 3
A trans() 0 11 2
A transChoice() 0 11 2
A setTranslationDomain() 0 4 1
A getTranslationDomain() 0 4 1
A setTranslator() 0 12 3
A getTranslator() 0 9 1
A getTranslationLabel() 0 4 1
A setRequest() 0 8 2
A getRequest() 0 8 2
A hasRequest() 0 4 1
A setFormContractor() 0 4 1
A getFormContractor() 0 4 1
A setDatagridBuilder() 0 4 1
A getDatagridBuilder() 0 4 1
A setListBuilder() 0 4 1
A getListBuilder() 0 4 1
A setShowBuilder() 0 4 1
A getShowBuilder() 0 4 1
A setConfigurationPool() 0 4 1
A getConfigurationPool() 0 4 1
A setRouteGenerator() 0 4 1
A getRouteGenerator() 0 4 1
A getCode() 0 4 1
A getBaseCodeRoute() 0 8 2
A getModelManager() 0 4 1
A setModelManager() 0 4 1
A getManagerType() 0 4 1
A setManagerType() 0 4 1
A getObjectIdentifier() 0 4 1
A setSecurityInformation() 0 4 1
A getSecurityInformation() 0 4 1
A getPermissionsShow() 0 9 3
A showIn() 0 9 3
A createObjectSecurity() 0 4 1
A setSecurityHandler() 0 4 1
A getSecurityHandler() 0 4 1
A isGranted() 0 10 4
A getUrlsafeIdentifier() 0 4 1
A getNormalizedIdentifier() 0 4 1
A id() 0 4 1
A setValidator() 0 12 3
A getValidator() 0 4 1
A getShow() 0 6 1
A setFormTheme() 0 4 1
A getFormTheme() 0 4 1
A setFilterTheme() 0 4 1
A getFilterTheme() 0 4 1
A addExtension() 0 4 1
A getExtensions() 0 4 1
A setMenuFactory() 0 4 1
A getMenuFactory() 0 4 1
A setRouteBuilder() 0 4 1
A getRouteBuilder() 0 4 1
A toString() 0 12 4
A setLabelTranslatorStrategy() 0 4 1
A getLabelTranslatorStrategy() 0 4 1
A supportsPreviewMode() 0 4 1
A setPerPageOptions() 0 4 1
A getPerPageOptions() 0 4 1
A setPagerType() 0 4 1
A getPagerType() 0 4 1
A determinedPerPageValue() 0 4 1
A isAclEnabled() 0 4 1
A getObjectMetadata() 0 4 1
A getListModes() 0 4 1
A setListMode() 0 8 2
A getListMode() 0 8 2
A getAccessMapping() 0 4 1
B checkAccess() 0 22 5
B hasAccess() 0 20 5
C getActionButtons() 0 68 22
B getDashboardActions() 0 25 5
A showMosaicButton() 0 8 2
A getSearchResultLink() 0 10 4
A isDefaultFilter() 0 11 3
A canAccessObject() 0 4 3
A configure() 0 3 1
A urlize() 0 4 1
A getDefaultFilterValues() 0 15 3
A configureFormFields() 0 3 1
A configureListFields() 0 3 1
A configureDatagridFilters() 0 3 1
A configureShowFields() 0 3 1
A configureRoutes() 0 3 1
A configureActionButtons() 0 4 1
A configureBatchActions() 0 4 1
A configureSideMenu() 0 3 1
A configureTabMenu() 0 6 1
A buildShow() 0 15 3
B buildList() 0 52 6
C buildForm() 0 28 7
A getSubClass() 0 12 2
B attachInlineValidator() 0 32 5
A predefinePerPageOptions() 0 6 1
A getAccess() 0 22 2
A configureDefaultFilterValues() 0 3 1
C buildDatagrid() 0 52 9
A buildRoutes() 0 23 3

How to fix   Complexity   

Complex Class

Complex classes like AbstractAdmin often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AbstractAdmin, and based on these observations, apply Extract Interface, too.

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

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

Loading history...
468
469
    /**
470
     * The cached base route name.
471
     *
472
     * @var string
473
     */
474
    private $cachedBaseRouteName;
475
476
    /**
477
     * The cached base route pattern.
478
     *
479
     * @var string
480
     */
481
    private $cachedBaseRoutePattern;
482
483
    /**
484
     * The form group disposition.
485
     *
486
     * @var array|bool
487
     */
488
    private $formGroups = false;
489
490
    /**
491
     * The form tabs disposition.
492
     *
493
     * @var array|bool
494
     */
495
    private $formTabs = false;
496
497
    /**
498
     * The view group disposition.
499
     *
500
     * @var array|bool
501
     */
502
    private $showGroups = false;
503
504
    /**
505
     * The view tab disposition.
506
     *
507
     * @var array|bool
508
     */
509
    private $showTabs = false;
510
511
    /**
512
     * The manager type to use for the admin.
513
     *
514
     * @var string
515
     */
516
    private $managerType;
517
518
    /**
519
     * @param string $code
520
     * @param string $class
521
     * @param string $baseControllerName
522
     */
523
    public function __construct($code, $class, $baseControllerName)
524
    {
525
        $this->code = $code;
526
        $this->class = $class;
527
        $this->baseControllerName = $baseControllerName;
528
529
        $this->predefinePerPageOptions();
530
        $this->datagridValues['_per_page'] = $this->maxPerPage;
531
    }
532
533
    /**
534
     * {@inheritdoc}
535
     *
536
     * NEXT_MAJOR: return null to indicate no override
537
     */
538
    public function getExportFormats()
539
    {
540
        return array(
541
            'json', 'xml', 'csv', 'xls',
542
        );
543
    }
544
545
    /**
546
     * {@inheritdoc}
547
     */
548
    public function getExportFields()
549
    {
550
        $fields = $this->getModelManager()->getExportFields($this->getClass());
551
552
        foreach ($this->getExtensions() as $extension) {
553
            if (method_exists($extension, 'configureExportFields')) {
554
                $fields = $extension->configureExportFields($this, $fields);
555
            }
556
        }
557
558
        return $fields;
559
    }
560
561
    /**
562
     * {@inheritdoc}
563
     */
564
    public function getDataSourceIterator()
565
    {
566
        $datagrid = $this->getDatagrid();
567
        $datagrid->buildPager();
568
569
        $fields = array();
570
571
        foreach ($this->getExportFields() as $key => $field) {
572
            $label = $this->getTranslationLabel($field, 'export', 'label');
573
            $transLabel = $this->trans($label);
574
575
            // NEXT_MAJOR: Remove this hack, because all field labels will be translated with the major release
576
            // No translation key exists
577
            if ($transLabel == $label) {
578
                $fields[$key] = $field;
579
            } else {
580
                $fields[$transLabel] = $field;
581
            }
582
        }
583
584
        return $this->getModelManager()->getDataSourceIterator($datagrid, $fields);
585
    }
586
587
    /**
588
     * {@inheritdoc}
589
     */
590
    public function validate(ErrorElement $errorElement, $object)
591
    {
592
    }
593
594
    /**
595
     * define custom variable.
596
     */
597
    public function initialize()
598
    {
599
        if (!$this->classnameLabel) {
600
            $this->classnameLabel = substr($this->getClass(), strrpos($this->getClass(), '\\') + 1);
601
        }
602
603
        $this->configure();
604
    }
605
606
    /**
607
     * {@inheritdoc}
608
     */
609
    public function update($object)
610
    {
611
        $this->preUpdate($object);
612
        foreach ($this->extensions as $extension) {
613
            $extension->preUpdate($this, $object);
614
        }
615
616
        $result = $this->getModelManager()->update($object);
617
        // BC compatibility
618
        if (null !== $result) {
619
            $object = $result;
620
        }
621
622
        $this->postUpdate($object);
623
        foreach ($this->extensions as $extension) {
624
            $extension->postUpdate($this, $object);
625
        }
626
627
        return $object;
628
    }
629
630
    /**
631
     * {@inheritdoc}
632
     */
633
    public function create($object)
634
    {
635
        $this->prePersist($object);
636
        foreach ($this->extensions as $extension) {
637
            $extension->prePersist($this, $object);
638
        }
639
640
        $result = $this->getModelManager()->create($object);
641
        // BC compatibility
642
        if (null !== $result) {
643
            $object = $result;
644
        }
645
646
        $this->postPersist($object);
647
        foreach ($this->extensions as $extension) {
648
            $extension->postPersist($this, $object);
649
        }
650
651
        $this->createObjectSecurity($object);
652
653
        return $object;
654
    }
655
656
    /**
657
     * {@inheritdoc}
658
     */
659
    public function delete($object)
660
    {
661
        $this->preRemove($object);
662
        foreach ($this->extensions as $extension) {
663
            $extension->preRemove($this, $object);
664
        }
665
666
        $this->getSecurityHandler()->deleteObjectSecurity($this, $object);
667
        $this->getModelManager()->delete($object);
668
669
        $this->postRemove($object);
670
        foreach ($this->extensions as $extension) {
671
            $extension->postRemove($this, $object);
672
        }
673
    }
674
675
    /**
676
     * {@inheritdoc}
677
     */
678
    public function preValidate($object)
679
    {
680
    }
681
682
    /**
683
     * {@inheritdoc}
684
     */
685
    public function preUpdate($object)
686
    {
687
    }
688
689
    /**
690
     * {@inheritdoc}
691
     */
692
    public function postUpdate($object)
693
    {
694
    }
695
696
    /**
697
     * {@inheritdoc}
698
     */
699
    public function prePersist($object)
700
    {
701
    }
702
703
    /**
704
     * {@inheritdoc}
705
     */
706
    public function postPersist($object)
707
    {
708
    }
709
710
    /**
711
     * {@inheritdoc}
712
     */
713
    public function preRemove($object)
714
    {
715
    }
716
717
    /**
718
     * {@inheritdoc}
719
     */
720
    public function postRemove($object)
721
    {
722
    }
723
724
    /**
725
     * {@inheritdoc}
726
     */
727
    public function preBatchAction($actionName, ProxyQueryInterface $query, array &$idx, $allElements)
728
    {
729
    }
730
731
    /**
732
     * {@inheritdoc}
733
     */
734
    public function getFilterParameters()
735
    {
736
        $parameters = array();
737
738
        // build the values array
739
        if ($this->hasRequest()) {
740
            $filters = $this->request->query->get('filter', array());
741
742
            // if persisting filters, save filters to session, or pull them out of session if no new filters set
743
            if ($this->persistFilters) {
744
                if ($filters == array() && $this->request->query->get('filters') != 'reset') {
745
                    $filters = $this->request->getSession()->get($this->getCode().'.filter.parameters', array());
746
                } else {
747
                    $this->request->getSession()->set($this->getCode().'.filter.parameters', $filters);
748
                }
749
            }
750
751
            $parameters = array_merge(
752
                $this->getModelManager()->getDefaultSortValues($this->getClass()),
753
                $this->datagridValues,
754
                $this->getDefaultFilterValues(),
755
                $filters
756
            );
757
758
            if (!$this->determinedPerPageValue($parameters['_per_page'])) {
759
                $parameters['_per_page'] = $this->maxPerPage;
760
            }
761
762
            // always force the parent value
763
            if ($this->isChild() && $this->getParentAssociationMapping()) {
764
                $name = str_replace('.', '__', $this->getParentAssociationMapping());
765
                $parameters[$name] = array('value' => $this->request->get($this->getParent()->getIdParameter()));
766
            }
767
        }
768
769
        return $parameters;
770
    }
771
772
    /**
773
     * Returns the name of the parent related field, so the field can be use to set the default
774
     * value (ie the parent object) or to filter the object.
775
     *
776
     * @return string the name of the parent related field
777
     */
778
    public function getParentAssociationMapping()
779
    {
780
        return $this->parentAssociationMapping;
781
    }
782
783
    /**
784
     * Returns the baseRoutePattern used to generate the routing information.
785
     *
786
     * @throws \RuntimeException
787
     *
788
     * @return string the baseRoutePattern used to generate the routing information
789
     */
790
    public function getBaseRoutePattern()
791
    {
792
        if (null !== $this->cachedBaseRoutePattern) {
793
            return $this->cachedBaseRoutePattern;
794
        }
795
796
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route pattern
797
            if (!$this->baseRoutePattern) {
798
                preg_match(self::CLASS_REGEX, $this->class, $matches);
799
800
                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...
801
                    throw new \RuntimeException(sprintf('Please define a default `baseRoutePattern` value for the admin class `%s`', get_class($this)));
802
                }
803
            }
804
805
            $this->cachedBaseRoutePattern = sprintf('%s/%s/%s',
806
                $this->getParent()->getBaseRoutePattern(),
807
                $this->getParent()->getRouterIdParameter(),
808
                $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...
809
            );
810
        } elseif ($this->baseRoutePattern) {
811
            $this->cachedBaseRoutePattern = $this->baseRoutePattern;
812
        } else {
813
            preg_match(self::CLASS_REGEX, $this->class, $matches);
814
815
            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...
816
                throw new \RuntimeException(sprintf('Please define a default `baseRoutePattern` value for the admin class `%s`', get_class($this)));
817
            }
818
819
            $this->cachedBaseRoutePattern = sprintf('/%s%s/%s',
820
                empty($matches[1]) ? '' : $this->urlize($matches[1], '-').'/',
821
                $this->urlize($matches[3], '-'),
822
                $this->urlize($matches[5], '-')
823
            );
824
        }
825
826
        return $this->cachedBaseRoutePattern;
827
    }
828
829
    /**
830
     * Returns the baseRouteName used to generate the routing information.
831
     *
832
     * @throws \RuntimeException
833
     *
834
     * @return string the baseRouteName used to generate the routing information
835
     */
836
    public function getBaseRouteName()
837
    {
838
        if (null !== $this->cachedBaseRouteName) {
839
            return $this->cachedBaseRouteName;
840
        }
841
842
        if ($this->isChild()) { // the admin class is a child, prefix it with the parent route name
843
            if (!$this->baseRouteName) {
844
                preg_match(self::CLASS_REGEX, $this->class, $matches);
845
846
                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...
847
                    throw new \RuntimeException(sprintf('Cannot automatically determine base route name, please define a default `baseRouteName` value for the admin class `%s`', get_class($this)));
848
                }
849
            }
850
851
            $this->cachedBaseRouteName = sprintf('%s_%s',
852
                $this->getParent()->getBaseRouteName(),
853
                $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...
854
            );
855
        } elseif ($this->baseRouteName) {
856
            $this->cachedBaseRouteName = $this->baseRouteName;
857
        } else {
858
            preg_match(self::CLASS_REGEX, $this->class, $matches);
859
860
            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...
861
                throw new \RuntimeException(sprintf('Cannot automatically determine base route name, please define a default `baseRouteName` value for the admin class `%s`', get_class($this)));
862
            }
863
864
            $this->cachedBaseRouteName = sprintf('admin_%s%s_%s',
865
                empty($matches[1]) ? '' : $this->urlize($matches[1]).'_',
866
                $this->urlize($matches[3]),
867
                $this->urlize($matches[5])
868
            );
869
        }
870
871
        return $this->cachedBaseRouteName;
872
    }
873
874
    /**
875
     * {@inheritdoc}
876
     */
877
    public function getClass()
878
    {
879
        // see https://github.com/sonata-project/SonataCoreBundle/commit/247eeb0a7ca7211142e101754769d70bc402a5b4
880
        if ($this->hasSubject() && is_object($this->getSubject())) {
881
            return ClassUtils::getClass($this->getSubject());
882
        }
883
884
        if (!$this->hasActiveSubClass()) {
885
            if (count($this->getSubClasses()) > 0) {
886
                $subject = $this->getSubject();
887
888
                if ($subject && is_object($subject)) {
889
                    return ClassUtils::getClass($subject);
890
                }
891
            }
892
893
            return $this->class;
894
        }
895
896
        if ($this->getParentFieldDescription() && $this->hasActiveSubClass()) {
897
            throw new \RuntimeException('Feature not implemented: an embedded admin cannot have subclass');
898
        }
899
900
        $subClass = $this->getRequest()->query->get('subclass');
901
902
        return $this->getSubClass($subClass);
903
    }
904
905
    /**
906
     * {@inheritdoc}
907
     */
908
    public function getSubClasses()
909
    {
910
        return $this->subClasses;
911
    }
912
913
    /**
914
     * {@inheritdoc}
915
     */
916
    public function addSubClass($subClass)
917
    {
918
        if (!in_array($subClass, $this->subClasses)) {
919
            $this->subClasses[] = $subClass;
920
        }
921
    }
922
923
    /**
924
     * {@inheritdoc}
925
     */
926
    public function setSubClasses(array $subClasses)
927
    {
928
        $this->subClasses = $subClasses;
929
    }
930
931
    /**
932
     * {@inheritdoc}
933
     */
934
    public function hasSubClass($name)
935
    {
936
        return isset($this->subClasses[$name]);
937
    }
938
939
    /**
940
     * {@inheritdoc}
941
     */
942
    public function hasActiveSubClass()
943
    {
944
        if (count($this->subClasses) > 0 && $this->request) {
945
            return null !== $this->getRequest()->query->get('subclass');
946
        }
947
948
        return false;
949
    }
950
951
    /**
952
     * {@inheritdoc}
953
     */
954
    public function getActiveSubClass()
955
    {
956
        if (!$this->hasActiveSubClass()) {
957
            return;
958
        }
959
960
        return $this->getClass();
961
    }
962
963
    /**
964
     * {@inheritdoc}
965
     */
966
    public function getActiveSubclassCode()
967
    {
968
        if (!$this->hasActiveSubClass()) {
969
            return;
970
        }
971
972
        $subClass = $this->getRequest()->query->get('subclass');
973
974
        if (!$this->hasSubClass($subClass)) {
975
            return;
976
        }
977
978
        return $subClass;
979
    }
980
981
    /**
982
     * {@inheritdoc}
983
     */
984
    final public function getBatchActions()
985
    {
986
        $actions = array();
987
988
        if ($this->hasRoute('delete') && $this->hasAccess('delete')) {
989
            $actions['delete'] = array(
990
                'label' => 'action_delete',
991
                'translation_domain' => 'SonataAdminBundle',
992
                'ask_confirmation' => true, // by default always true
993
            );
994
        }
995
996
        $actions = $this->configureBatchActions($actions);
997
998
        foreach ($this->getExtensions() as $extension) {
999
            $actions = $extension->configureBatchActions($this, $actions);
1000
        }
1001
1002
        foreach ($actions  as $name => &$action) {
1003
            if (!array_key_exists('label', $action)) {
1004
                $action['label'] = $this->getTranslationLabel($name, 'batch', 'label');
1005
            }
1006
1007
            if (!array_key_exists('translation_domain', $action)) {
1008
                $action['translation_domain'] = $this->getTranslationDomain();
1009
            }
1010
        }
1011
1012
        return $actions;
1013
    }
1014
1015
    /**
1016
     * {@inheritdoc}
1017
     */
1018
    public function getRoutes()
1019
    {
1020
        $this->buildRoutes();
1021
1022
        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...
1023
    }
1024
1025
    /**
1026
     * {@inheritdoc}
1027
     */
1028
    public function getRouterIdParameter()
1029
    {
1030
        return '{'.$this->getIdParameter().'}';
1031
    }
1032
1033
    /**
1034
     * {@inheritdoc}
1035
     */
1036
    public function getIdParameter()
1037
    {
1038
        $parameter = 'id';
1039
1040
        for ($i = 0; $i < $this->getChildDepth(); ++$i) {
1041
            $parameter = 'child'.ucfirst($parameter);
1042
        }
1043
1044
        return $parameter;
1045
    }
1046
1047
    /**
1048
     * {@inheritdoc}
1049
     */
1050
    public function hasRoute($name)
1051
    {
1052
        if (!$this->routeGenerator) {
1053
            throw new \RuntimeException('RouteGenerator cannot be null');
1054
        }
1055
1056
        return $this->routeGenerator->hasAdminRoute($this, $name);
1057
    }
1058
1059
    /**
1060
     * {@inheritdoc}
1061
     */
1062
    public function isCurrentRoute($name, $adminCode = null)
1063
    {
1064
        if (!$this->hasRequest()) {
1065
            return false;
1066
        }
1067
1068
        $request = $this->getRequest();
1069
        $route = $request->get('_route');
1070
1071
        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...
1072
            $admin = $this->getConfigurationPool()->getAdminByAdminCode($adminCode);
1073
        } else {
1074
            $admin = $this;
1075
        }
1076
1077
        if (!$admin) {
1078
            return false;
1079
        }
1080
1081
        return ($admin->getBaseRouteName().'_'.$name) == $route;
1082
    }
1083
1084
    /**
1085
     * {@inheritdoc}
1086
     */
1087
    public function generateObjectUrl($name, $object, array $parameters = array(), $absolute = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1088
    {
1089
        $parameters['id'] = $this->getUrlsafeIdentifier($object);
1090
1091
        return $this->generateUrl($name, $parameters, $absolute);
1092
    }
1093
1094
    /**
1095
     * {@inheritdoc}
1096
     */
1097
    public function generateUrl($name, array $parameters = array(), $absolute = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1098
    {
1099
        return $this->routeGenerator->generateUrl($this, $name, $parameters, $absolute);
1100
    }
1101
1102
    /**
1103
     * {@inheritdoc}
1104
     */
1105
    public function generateMenuUrl($name, array $parameters = array(), $absolute = RoutingUrlGeneratorInterface::ABSOLUTE_PATH)
1106
    {
1107
        return $this->routeGenerator->generateMenuUrl($this, $name, $parameters, $absolute);
1108
    }
1109
1110
    /**
1111
     * {@inheritdoc}
1112
     */
1113
    public function setTemplates(array $templates)
1114
    {
1115
        $this->templates = $templates;
1116
    }
1117
1118
    /**
1119
     * {@inheritdoc}
1120
     */
1121
    public function setTemplate($name, $template)
1122
    {
1123
        $this->templates[$name] = $template;
1124
    }
1125
1126
    /**
1127
     * {@inheritdoc}
1128
     */
1129
    public function getTemplates()
1130
    {
1131
        return $this->templates;
1132
    }
1133
1134
    /**
1135
     * {@inheritdoc}
1136
     */
1137
    public function getTemplate($name)
1138
    {
1139
        if (isset($this->templates[$name])) {
1140
            return $this->templates[$name];
1141
        }
1142
    }
1143
1144
    /**
1145
     * {@inheritdoc}
1146
     */
1147
    public function getNewInstance()
1148
    {
1149
        $object = $this->getModelManager()->getModelInstance($this->getClass());
1150
        foreach ($this->getExtensions() as $extension) {
1151
            $extension->alterNewInstance($this, $object);
1152
        }
1153
1154
        return $object;
1155
    }
1156
1157
    /**
1158
     * {@inheritdoc}
1159
     */
1160
    public function getFormBuilder()
1161
    {
1162
        $this->formOptions['data_class'] = $this->getClass();
1163
1164
        $formBuilder = $this->getFormContractor()->getFormBuilder(
1165
            $this->getUniqid(),
1166
            $this->formOptions
1167
        );
1168
1169
        $this->defineFormBuilder($formBuilder);
1170
1171
        return $formBuilder;
1172
    }
1173
1174
    /**
1175
     * This method is being called by the main admin class and the child class,
1176
     * the getFormBuilder is only call by the main admin class.
1177
     *
1178
     * @param FormBuilderInterface $formBuilder
1179
     */
1180
    public function defineFormBuilder(FormBuilderInterface $formBuilder)
1181
    {
1182
        $mapper = new FormMapper($this->getFormContractor(), $formBuilder, $this);
1183
1184
        $this->configureFormFields($mapper);
1185
1186
        foreach ($this->getExtensions() as $extension) {
1187
            $extension->configureFormFields($mapper);
1188
        }
1189
1190
        $this->attachInlineValidator();
1191
    }
1192
1193
    /**
1194
     * {@inheritdoc}
1195
     */
1196
    public function attachAdminClass(FieldDescriptionInterface $fieldDescription)
1197
    {
1198
        $pool = $this->getConfigurationPool();
1199
1200
        $adminCode = $fieldDescription->getOption('admin_code');
1201
1202
        if ($adminCode !== null) {
1203
            $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...
1204
        } else {
1205
            $admin = $pool->getAdminByClass($fieldDescription->getTargetEntity());
1206
        }
1207
1208
        if (!$admin) {
1209
            return;
1210
        }
1211
1212
        if ($this->hasRequest()) {
1213
            $admin->setRequest($this->getRequest());
1214
        }
1215
1216
        $fieldDescription->setAssociationAdmin($admin);
1217
    }
1218
1219
    /**
1220
     * {@inheritdoc}
1221
     */
1222
    public function getObject($id)
1223
    {
1224
        $object = $this->getModelManager()->find($this->getClass(), $id);
1225
        foreach ($this->getExtensions() as $extension) {
1226
            $extension->alterObject($this, $object);
1227
        }
1228
1229
        return $object;
1230
    }
1231
1232
    /**
1233
     * {@inheritdoc}
1234
     */
1235
    public function getForm()
1236
    {
1237
        $this->buildForm();
1238
1239
        return $this->form;
1240
    }
1241
1242
    /**
1243
     * {@inheritdoc}
1244
     */
1245
    public function getList()
1246
    {
1247
        $this->buildList();
1248
1249
        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...
1250
    }
1251
1252
    /**
1253
     * {@inheritdoc}
1254
     */
1255
    public function createQuery($context = 'list')
1256
    {
1257
        if (func_num_args() > 0) {
1258
            @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...
1259
                'The $context argument of '.__METHOD__.' is deprecated since 3.3, to be removed in 4.0.',
1260
                E_USER_DEPRECATED
1261
            );
1262
        }
1263
        $query = $this->getModelManager()->createQuery($this->class);
1264
1265
        foreach ($this->extensions as $extension) {
1266
            $extension->configureQuery($this, $query, $context);
1267
        }
1268
1269
        return $query;
1270
    }
1271
1272
    /**
1273
     * {@inheritdoc}
1274
     */
1275
    public function getDatagrid()
1276
    {
1277
        $this->buildDatagrid();
1278
1279
        return $this->datagrid;
1280
    }
1281
1282
    /**
1283
     * {@inheritdoc}
1284
     */
1285
    public function buildTabMenu($action, AdminInterface $childAdmin = null)
1286
    {
1287
        if ($this->loaded['tab_menu']) {
1288
            return;
1289
        }
1290
1291
        $this->loaded['tab_menu'] = true;
1292
1293
        $menu = $this->menuFactory->createItem('root');
1294
        $menu->setChildrenAttribute('class', 'nav navbar-nav');
1295
        $menu->setExtra('translation_domain', $this->translationDomain);
1296
1297
        // Prevents BC break with KnpMenuBundle v1.x
1298
        if (method_exists($menu, 'setCurrentUri')) {
1299
            $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...
1300
        }
1301
1302
        $this->configureTabMenu($menu, $action, $childAdmin);
1303
1304
        foreach ($this->getExtensions() as $extension) {
1305
            $extension->configureTabMenu($this, $menu, $action, $childAdmin);
1306
        }
1307
1308
        $this->menu = $menu;
1309
    }
1310
1311
    /**
1312
     * @param string         $action
1313
     * @param AdminInterface $childAdmin
1314
     *
1315
     * @return ItemInterface
1316
     */
1317
    public function getSideMenu($action, AdminInterface $childAdmin = null)
1318
    {
1319
        if ($this->isChild()) {
1320
            return $this->getParent()->getSideMenu($action, $this);
1321
        }
1322
1323
        $this->buildTabMenu($action, $childAdmin);
1324
1325
        return $this->menu;
1326
    }
1327
1328
    /**
1329
     * Returns the root code.
1330
     *
1331
     * @return string the root code
1332
     */
1333
    public function getRootCode()
1334
    {
1335
        return $this->getRoot()->getCode();
1336
    }
1337
1338
    /**
1339
     * Returns the master admin.
1340
     *
1341
     * @return AbstractAdmin the root admin class
1342
     */
1343
    public function getRoot()
1344
    {
1345
        $parentFieldDescription = $this->getParentFieldDescription();
1346
1347
        if (!$parentFieldDescription) {
1348
            return $this;
1349
        }
1350
1351
        return $parentFieldDescription->getAdmin()->getRoot();
1352
    }
1353
1354
    /**
1355
     * {@inheritdoc}
1356
     */
1357
    public function setBaseControllerName($baseControllerName)
1358
    {
1359
        $this->baseControllerName = $baseControllerName;
1360
    }
1361
1362
    /**
1363
     * {@inheritdoc}
1364
     */
1365
    public function getBaseControllerName()
1366
    {
1367
        return $this->baseControllerName;
1368
    }
1369
1370
    /**
1371
     * @param string $label
1372
     */
1373
    public function setLabel($label)
1374
    {
1375
        $this->label = $label;
1376
    }
1377
1378
    /**
1379
     * {@inheritdoc}
1380
     */
1381
    public function getLabel()
1382
    {
1383
        return $this->label;
1384
    }
1385
1386
    /**
1387
     * @param bool $persist
1388
     */
1389
    public function setPersistFilters($persist)
1390
    {
1391
        $this->persistFilters = $persist;
1392
    }
1393
1394
    /**
1395
     * @param int $maxPerPage
1396
     */
1397
    public function setMaxPerPage($maxPerPage)
1398
    {
1399
        $this->maxPerPage = $maxPerPage;
1400
    }
1401
1402
    /**
1403
     * @return int
1404
     */
1405
    public function getMaxPerPage()
1406
    {
1407
        return $this->maxPerPage;
1408
    }
1409
1410
    /**
1411
     * @param int $maxPageLinks
1412
     */
1413
    public function setMaxPageLinks($maxPageLinks)
1414
    {
1415
        $this->maxPageLinks = $maxPageLinks;
1416
    }
1417
1418
    /**
1419
     * @return int
1420
     */
1421
    public function getMaxPageLinks()
1422
    {
1423
        return $this->maxPageLinks;
1424
    }
1425
1426
    /**
1427
     * {@inheritdoc}
1428
     */
1429
    public function getFormGroups()
1430
    {
1431
        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 1431 which is incompatible with the return type declared by the interface Sonata\AdminBundle\Admin...nterface::getFormGroups of type array.
Loading history...
1432
    }
1433
1434
    /**
1435
     * {@inheritdoc}
1436
     */
1437
    public function setFormGroups(array $formGroups)
1438
    {
1439
        $this->formGroups = $formGroups;
1440
    }
1441
1442
    /**
1443
     * {@inheritdoc}
1444
     */
1445
    public function removeFieldFromFormGroup($key)
1446
    {
1447
        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...
1448
            unset($this->formGroups[$name]['fields'][$key]);
1449
1450
            if (empty($this->formGroups[$name]['fields'])) {
1451
                unset($this->formGroups[$name]);
1452
            }
1453
        }
1454
    }
1455
1456
    /**
1457
     * @param array $group
1458
     * @param array $keys
1459
     */
1460
    public function reorderFormGroup($group, array $keys)
1461
    {
1462
        $formGroups = $this->getFormGroups();
1463
        $formGroups[$group]['fields'] = array_merge(array_flip($keys), $formGroups[$group]['fields']);
1464
        $this->setFormGroups($formGroups);
0 ignored issues
show
Bug introduced by
It seems like $formGroups defined by $this->getFormGroups() on line 1462 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...
1465
    }
1466
1467
    /**
1468
     * {@inheritdoc}
1469
     */
1470
    public function getFormTabs()
1471
    {
1472
        return $this->formTabs;
1473
    }
1474
1475
    /**
1476
     * {@inheritdoc}
1477
     */
1478
    public function setFormTabs(array $formTabs)
1479
    {
1480
        $this->formTabs = $formTabs;
1481
    }
1482
1483
    /**
1484
     * {@inheritdoc}
1485
     */
1486
    public function getShowTabs()
1487
    {
1488
        return $this->showTabs;
1489
    }
1490
1491
    /**
1492
     * {@inheritdoc}
1493
     */
1494
    public function setShowTabs(array $showTabs)
1495
    {
1496
        $this->showTabs = $showTabs;
1497
    }
1498
1499
    /**
1500
     * {@inheritdoc}
1501
     */
1502
    public function getShowGroups()
1503
    {
1504
        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 1504 which is incompatible with the return type declared by the interface Sonata\AdminBundle\Admin...nterface::getShowGroups of type array.
Loading history...
1505
    }
1506
1507
    /**
1508
     * {@inheritdoc}
1509
     */
1510
    public function setShowGroups(array $showGroups)
1511
    {
1512
        $this->showGroups = $showGroups;
1513
    }
1514
1515
    /**
1516
     * {@inheritdoc}
1517
     */
1518
    public function reorderShowGroup($group, array $keys)
1519
    {
1520
        $showGroups = $this->getShowGroups();
1521
        $showGroups[$group]['fields'] = array_merge(array_flip($keys), $showGroups[$group]['fields']);
1522
        $this->setShowGroups($showGroups);
0 ignored issues
show
Bug introduced by
It seems like $showGroups defined by $this->getShowGroups() on line 1520 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...
1523
    }
1524
1525
    /**
1526
     * {@inheritdoc}
1527
     */
1528
    public function setParentFieldDescription(FieldDescriptionInterface $parentFieldDescription)
1529
    {
1530
        $this->parentFieldDescription = $parentFieldDescription;
1531
    }
1532
1533
    /**
1534
     * {@inheritdoc}
1535
     */
1536
    public function getParentFieldDescription()
1537
    {
1538
        return $this->parentFieldDescription;
1539
    }
1540
1541
    /**
1542
     * {@inheritdoc}
1543
     */
1544
    public function hasParentFieldDescription()
1545
    {
1546
        return $this->parentFieldDescription instanceof FieldDescriptionInterface;
1547
    }
1548
1549
    /**
1550
     * {@inheritdoc}
1551
     */
1552
    public function setSubject($subject)
1553
    {
1554
        if (is_object($subject) && !is_a($subject, $this->class, true)) {
1555
            $message = <<<'EOT'
1556
You are trying to set entity an instance of "%s",
1557
which is not the one registered with this admin class ("%s").
1558
This is deprecated since 3.5 and will no longer be supported in 4.0.
1559
EOT;
1560
1561
            @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...
1562
                sprintf($message, get_class($subject), $this->class),
1563
                E_USER_DEPRECATED
1564
            ); // NEXT_MAJOR : throw an exception instead
1565
        }
1566
1567
        $this->subject = $subject;
1568
    }
1569
1570
    /**
1571
     * {@inheritdoc}
1572
     */
1573
    public function getSubject()
1574
    {
1575
        if ($this->subject === null && $this->request && !$this->hasParentFieldDescription()) {
1576
            $id = $this->request->get($this->getIdParameter());
1577
            $this->subject = $this->getModelManager()->find($this->class, $id);
1578
        }
1579
1580
        return $this->subject;
1581
    }
1582
1583
    /**
1584
     * {@inheritdoc}
1585
     */
1586
    public function hasSubject()
1587
    {
1588
        return $this->subject != null;
1589
    }
1590
1591
    /**
1592
     * {@inheritdoc}
1593
     */
1594
    public function getFormFieldDescriptions()
1595
    {
1596
        $this->buildForm();
1597
1598
        return $this->formFieldDescriptions;
1599
    }
1600
1601
    /**
1602
     * {@inheritdoc}
1603
     */
1604
    public function getFormFieldDescription($name)
1605
    {
1606
        return $this->hasFormFieldDescription($name) ? $this->formFieldDescriptions[$name] : null;
1607
    }
1608
1609
    /**
1610
     * Returns true if the admin has a FieldDescription with the given $name.
1611
     *
1612
     * @param string $name
1613
     *
1614
     * @return bool
1615
     */
1616
    public function hasFormFieldDescription($name)
1617
    {
1618
        return array_key_exists($name, $this->formFieldDescriptions) ? true : false;
1619
    }
1620
1621
    /**
1622
     * {@inheritdoc}
1623
     */
1624
    public function addFormFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1625
    {
1626
        $this->formFieldDescriptions[$name] = $fieldDescription;
1627
    }
1628
1629
    /**
1630
     * remove a FieldDescription.
1631
     *
1632
     * @param string $name
1633
     */
1634
    public function removeFormFieldDescription($name)
1635
    {
1636
        unset($this->formFieldDescriptions[$name]);
1637
    }
1638
1639
    /**
1640
     * build and return the collection of form FieldDescription.
1641
     *
1642
     * @return array collection of form FieldDescription
1643
     */
1644
    public function getShowFieldDescriptions()
1645
    {
1646
        $this->buildShow();
1647
1648
        return $this->showFieldDescriptions;
1649
    }
1650
1651
    /**
1652
     * Returns the form FieldDescription with the given $name.
1653
     *
1654
     * @param string $name
1655
     *
1656
     * @return FieldDescriptionInterface
1657
     */
1658
    public function getShowFieldDescription($name)
1659
    {
1660
        $this->buildShow();
1661
1662
        return $this->hasShowFieldDescription($name) ? $this->showFieldDescriptions[$name] : null;
1663
    }
1664
1665
    /**
1666
     * {@inheritdoc}
1667
     */
1668
    public function hasShowFieldDescription($name)
1669
    {
1670
        return array_key_exists($name, $this->showFieldDescriptions);
1671
    }
1672
1673
    /**
1674
     * {@inheritdoc}
1675
     */
1676
    public function addShowFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1677
    {
1678
        $this->showFieldDescriptions[$name] = $fieldDescription;
1679
    }
1680
1681
    /**
1682
     * {@inheritdoc}
1683
     */
1684
    public function removeShowFieldDescription($name)
1685
    {
1686
        unset($this->showFieldDescriptions[$name]);
1687
    }
1688
1689
    /**
1690
     * {@inheritdoc}
1691
     */
1692
    public function getListFieldDescriptions()
1693
    {
1694
        $this->buildList();
1695
1696
        return $this->listFieldDescriptions;
1697
    }
1698
1699
    /**
1700
     * {@inheritdoc}
1701
     */
1702
    public function getListFieldDescription($name)
1703
    {
1704
        return $this->hasListFieldDescription($name) ? $this->listFieldDescriptions[$name] : null;
1705
    }
1706
1707
    /**
1708
     * {@inheritdoc}
1709
     */
1710
    public function hasListFieldDescription($name)
1711
    {
1712
        $this->buildList();
1713
1714
        return array_key_exists($name, $this->listFieldDescriptions) ? true : false;
1715
    }
1716
1717
    /**
1718
     * {@inheritdoc}
1719
     */
1720
    public function addListFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1721
    {
1722
        $this->listFieldDescriptions[$name] = $fieldDescription;
1723
    }
1724
1725
    /**
1726
     * {@inheritdoc}
1727
     */
1728
    public function removeListFieldDescription($name)
1729
    {
1730
        unset($this->listFieldDescriptions[$name]);
1731
    }
1732
1733
    /**
1734
     * {@inheritdoc}
1735
     */
1736
    public function getFilterFieldDescription($name)
1737
    {
1738
        return $this->hasFilterFieldDescription($name) ? $this->filterFieldDescriptions[$name] : null;
1739
    }
1740
1741
    /**
1742
     * {@inheritdoc}
1743
     */
1744
    public function hasFilterFieldDescription($name)
1745
    {
1746
        return array_key_exists($name, $this->filterFieldDescriptions) ? true : false;
1747
    }
1748
1749
    /**
1750
     * {@inheritdoc}
1751
     */
1752
    public function addFilterFieldDescription($name, FieldDescriptionInterface $fieldDescription)
1753
    {
1754
        $this->filterFieldDescriptions[$name] = $fieldDescription;
1755
    }
1756
1757
    /**
1758
     * {@inheritdoc}
1759
     */
1760
    public function removeFilterFieldDescription($name)
1761
    {
1762
        unset($this->filterFieldDescriptions[$name]);
1763
    }
1764
1765
    /**
1766
     * {@inheritdoc}
1767
     */
1768
    public function getFilterFieldDescriptions()
1769
    {
1770
        $this->buildDatagrid();
1771
1772
        return $this->filterFieldDescriptions;
1773
    }
1774
1775
    /**
1776
     * {@inheritdoc}
1777
     */
1778
    public function addChild(AdminInterface $child)
1779
    {
1780
        for ($parentAdmin = $this; null !== $parentAdmin; $parentAdmin = $parentAdmin->getParent()) {
1781
            if ($parentAdmin->getCode() !== $child->getCode()) {
1782
                continue;
1783
            }
1784
1785
            throw new \RuntimeException(sprintf(
1786
                'Circular reference detected! The child admin `%s` is already in the parent tree of the `%s` admin.',
1787
                $child->getCode(), $this->getCode()
1788
            ));
1789
        }
1790
1791
        $this->children[$child->getCode()] = $child;
1792
1793
        $child->setParent($this);
1794
    }
1795
1796
    /**
1797
     * {@inheritdoc}
1798
     */
1799
    public function hasChild($code)
1800
    {
1801
        return isset($this->children[$code]);
1802
    }
1803
1804
    /**
1805
     * {@inheritdoc}
1806
     */
1807
    public function getChildren()
1808
    {
1809
        return $this->children;
1810
    }
1811
1812
    /**
1813
     * {@inheritdoc}
1814
     */
1815
    public function getChild($code)
1816
    {
1817
        return $this->hasChild($code) ? $this->children[$code] : null;
1818
    }
1819
1820
    /**
1821
     * {@inheritdoc}
1822
     */
1823
    public function setParent(AdminInterface $parent)
1824
    {
1825
        $this->parent = $parent;
1826
    }
1827
1828
    /**
1829
     * {@inheritdoc}
1830
     */
1831
    public function getParent()
1832
    {
1833
        return $this->parent;
1834
    }
1835
1836
    /**
1837
     * {@inheritdoc}
1838
     */
1839
    final public function getRootAncestor()
1840
    {
1841
        $parent = $this;
1842
1843
        while ($parent->isChild()) {
1844
            $parent = $parent->getParent();
1845
        }
1846
1847
        return $parent;
1848
    }
1849
1850
    /**
1851
     * {@inheritdoc}
1852
     */
1853
    final public function getChildDepth()
1854
    {
1855
        $parent = $this;
1856
        $depth = 0;
1857
1858
        while ($parent->isChild()) {
1859
            $parent = $parent->getParent();
1860
            ++$depth;
1861
        }
1862
1863
        return $depth;
1864
    }
1865
1866
    /**
1867
     * {@inheritdoc}
1868
     */
1869
    final public function getCurrentLeafChildAdmin()
1870
    {
1871
        $child = $this->getCurrentChildAdmin();
1872
1873
        if (null === $child) {
1874
            return;
1875
        }
1876
1877
        for ($c = $child; null !== $c; $c = $child->getCurrentChildAdmin()) {
1878
            $child = $c;
1879
        }
1880
1881
        return $child;
1882
    }
1883
1884
    /**
1885
     * {@inheritdoc}
1886
     */
1887
    public function isChild()
1888
    {
1889
        return $this->parent instanceof AdminInterface;
1890
    }
1891
1892
    /**
1893
     * Returns true if the admin has children, false otherwise.
1894
     *
1895
     * @return bool if the admin has children
1896
     */
1897
    public function hasChildren()
1898
    {
1899
        return count($this->children) > 0;
1900
    }
1901
1902
    /**
1903
     * {@inheritdoc}
1904
     */
1905
    public function setUniqid($uniqid)
1906
    {
1907
        $this->uniqid = $uniqid;
1908
    }
1909
1910
    /**
1911
     * {@inheritdoc}
1912
     */
1913
    public function getUniqid()
1914
    {
1915
        if (!$this->uniqid) {
1916
            $this->uniqid = 's'.uniqid();
1917
        }
1918
1919
        return $this->uniqid;
1920
    }
1921
1922
    /**
1923
     * {@inheritdoc}
1924
     */
1925
    public function getClassnameLabel()
1926
    {
1927
        return $this->classnameLabel;
1928
    }
1929
1930
    /**
1931
     * {@inheritdoc}
1932
     */
1933
    public function getPersistentParameters()
1934
    {
1935
        $parameters = array();
1936
1937
        foreach ($this->getExtensions() as $extension) {
1938
            $params = $extension->getPersistentParameters($this);
1939
1940
            if (!is_array($params)) {
1941
                throw new \RuntimeException(sprintf('The %s::getPersistentParameters must return an array', get_class($extension)));
1942
            }
1943
1944
            $parameters = array_merge($parameters, $params);
1945
        }
1946
1947
        return $parameters;
1948
    }
1949
1950
    /**
1951
     * {@inheritdoc}
1952
     */
1953
    public function getPersistentParameter($name)
1954
    {
1955
        $parameters = $this->getPersistentParameters();
1956
1957
        return isset($parameters[$name]) ? $parameters[$name] : null;
1958
    }
1959
1960
    /**
1961
     * {@inheritdoc}
1962
     */
1963
    public function setCurrentChild($currentChild)
1964
    {
1965
        $this->currentChild = $currentChild;
1966
    }
1967
1968
    /**
1969
     * {@inheritdoc}
1970
     */
1971
    public function getCurrentChild()
1972
    {
1973
        return $this->currentChild;
1974
    }
1975
1976
    /**
1977
     * Returns the current child admin instance.
1978
     *
1979
     * @return AdminInterface|null the current child admin instance
1980
     */
1981
    public function getCurrentChildAdmin()
1982
    {
1983
        foreach ($this->children as $children) {
1984
            if ($children->getCurrentChild()) {
1985
                return $children;
1986
            }
1987
        }
1988
1989
        return;
1990
    }
1991
1992
    /**
1993
     * {@inheritdoc}
1994
     */
1995
    public function trans($id, array $parameters = array(), $domain = null, $locale = null)
1996
    {
1997
        @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...
1998
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
1999
            E_USER_DEPRECATED
2000
        );
2001
2002
        $domain = $domain ?: $this->getTranslationDomain();
2003
2004
        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...
2005
    }
2006
2007
    /**
2008
     * Translate a message id.
2009
     *
2010
     * NEXT_MAJOR: remove this method
2011
     *
2012
     * @param string      $id
2013
     * @param int         $count
2014
     * @param array       $parameters
2015
     * @param string|null $domain
2016
     * @param string|null $locale
2017
     *
2018
     * @return string the translated string
2019
     *
2020
     * @deprecated since 3.9, to be removed with 4.0
2021
     */
2022
    public function transChoice($id, $count, array $parameters = array(), $domain = null, $locale = null)
2023
    {
2024
        @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...
2025
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2026
            E_USER_DEPRECATED
2027
        );
2028
2029
        $domain = $domain ?: $this->getTranslationDomain();
2030
2031
        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...
2032
    }
2033
2034
    /**
2035
     * {@inheritdoc}
2036
     */
2037
    public function setTranslationDomain($translationDomain)
2038
    {
2039
        $this->translationDomain = $translationDomain;
2040
    }
2041
2042
    /**
2043
     * {@inheritdoc}
2044
     */
2045
    public function getTranslationDomain()
2046
    {
2047
        return $this->translationDomain;
2048
    }
2049
2050
    /**
2051
     * {@inheritdoc}
2052
     *
2053
     * NEXT_MAJOR: remove this method
2054
     *
2055
     * @deprecated since 3.9, to be removed with 4.0
2056
     */
2057
    public function setTranslator(TranslatorInterface $translator)
2058
    {
2059
        $args = func_get_args();
2060
        if (isset($args[1]) && $args[1]) {
2061
            @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...
2062
                'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2063
                E_USER_DEPRECATED
2064
            );
2065
        }
2066
2067
        $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...
2068
    }
2069
2070
    /**
2071
     * {@inheritdoc}
2072
     *
2073
     * NEXT_MAJOR: remove this method
2074
     *
2075
     * @deprecated since 3.9, to be removed with 4.0
2076
     */
2077
    public function getTranslator()
2078
    {
2079
        @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...
2080
            'The '.__METHOD__.' method is deprecated since version 3.9 and will be removed in 4.0.',
2081
            E_USER_DEPRECATED
2082
        );
2083
2084
        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...
2085
    }
2086
2087
    /**
2088
     * {@inheritdoc}
2089
     */
2090
    public function getTranslationLabel($label, $context = '', $type = '')
2091
    {
2092
        return $this->getLabelTranslatorStrategy()->getLabel($label, $context, $type);
2093
    }
2094
2095
    /**
2096
     * {@inheritdoc}
2097
     */
2098
    public function setRequest(Request $request)
0 ignored issues
show
Bug introduced by
You have injected the Request via parameter $request. This is generally not recommended as there might be multiple instances during a request cycle (f.e. when using sub-requests). Instead, it is recommended to inject the RequestStack and retrieve the current request each time you need it via getCurrentRequest().
Loading history...
2099
    {
2100
        $this->request = $request;
2101
2102
        foreach ($this->getChildren() as $children) {
2103
            $children->setRequest($request);
2104
        }
2105
    }
2106
2107
    /**
2108
     * {@inheritdoc}
2109
     */
2110
    public function getRequest()
2111
    {
2112
        if (!$this->request) {
2113
            throw new \RuntimeException('The Request object has not been set');
2114
        }
2115
2116
        return $this->request;
2117
    }
2118
2119
    /**
2120
     * {@inheritdoc}
2121
     */
2122
    public function hasRequest()
2123
    {
2124
        return $this->request !== null;
2125
    }
2126
2127
    /**
2128
     * {@inheritdoc}
2129
     */
2130
    public function setFormContractor(FormContractorInterface $formBuilder)
2131
    {
2132
        $this->formContractor = $formBuilder;
2133
    }
2134
2135
    /**
2136
     * @return FormContractorInterface
2137
     */
2138
    public function getFormContractor()
2139
    {
2140
        return $this->formContractor;
2141
    }
2142
2143
    /**
2144
     * {@inheritdoc}
2145
     */
2146
    public function setDatagridBuilder(DatagridBuilderInterface $datagridBuilder)
2147
    {
2148
        $this->datagridBuilder = $datagridBuilder;
2149
    }
2150
2151
    /**
2152
     * {@inheritdoc}
2153
     */
2154
    public function getDatagridBuilder()
2155
    {
2156
        return $this->datagridBuilder;
2157
    }
2158
2159
    /**
2160
     * {@inheritdoc}
2161
     */
2162
    public function setListBuilder(ListBuilderInterface $listBuilder)
2163
    {
2164
        $this->listBuilder = $listBuilder;
2165
    }
2166
2167
    /**
2168
     * {@inheritdoc}
2169
     */
2170
    public function getListBuilder()
2171
    {
2172
        return $this->listBuilder;
2173
    }
2174
2175
    /**
2176
     * @param ShowBuilderInterface $showBuilder
2177
     */
2178
    public function setShowBuilder(ShowBuilderInterface $showBuilder)
2179
    {
2180
        $this->showBuilder = $showBuilder;
2181
    }
2182
2183
    /**
2184
     * @return ShowBuilderInterface
2185
     */
2186
    public function getShowBuilder()
2187
    {
2188
        return $this->showBuilder;
2189
    }
2190
2191
    /**
2192
     * {@inheritdoc}
2193
     */
2194
    public function setConfigurationPool(Pool $configurationPool)
2195
    {
2196
        $this->configurationPool = $configurationPool;
2197
    }
2198
2199
    /**
2200
     * @return Pool
2201
     */
2202
    public function getConfigurationPool()
2203
    {
2204
        return $this->configurationPool;
2205
    }
2206
2207
    /**
2208
     * {@inheritdoc}
2209
     */
2210
    public function setRouteGenerator(RouteGeneratorInterface $routeGenerator)
2211
    {
2212
        $this->routeGenerator = $routeGenerator;
2213
    }
2214
2215
    /**
2216
     * @return RouteGeneratorInterface
2217
     */
2218
    public function getRouteGenerator()
2219
    {
2220
        return $this->routeGenerator;
2221
    }
2222
2223
    /**
2224
     * {@inheritdoc}
2225
     */
2226
    public function getCode()
2227
    {
2228
        return $this->code;
2229
    }
2230
2231
    /**
2232
     * {@inheritdoc}
2233
     */
2234
    public function getBaseCodeRoute()
2235
    {
2236
        if ($this->isChild()) {
2237
            return $this->getParent()->getBaseCodeRoute().'|'.$this->getCode();
2238
        }
2239
2240
        return $this->getCode();
2241
    }
2242
2243
    /**
2244
     * {@inheritdoc}
2245
     */
2246
    public function getModelManager()
2247
    {
2248
        return $this->modelManager;
2249
    }
2250
2251
    /**
2252
     * @param ModelManagerInterface $modelManager
2253
     */
2254
    public function setModelManager(ModelManagerInterface $modelManager)
2255
    {
2256
        $this->modelManager = $modelManager;
2257
    }
2258
2259
    /**
2260
     * {@inheritdoc}
2261
     */
2262
    public function getManagerType()
2263
    {
2264
        return $this->managerType;
2265
    }
2266
2267
    /**
2268
     * @param string $type
2269
     */
2270
    public function setManagerType($type)
2271
    {
2272
        $this->managerType = $type;
2273
    }
2274
2275
    /**
2276
     * {@inheritdoc}
2277
     */
2278
    public function getObjectIdentifier()
2279
    {
2280
        return $this->getCode();
2281
    }
2282
2283
    /**
2284
     * Set the roles and permissions per role.
2285
     *
2286
     * @param array $information
2287
     */
2288
    public function setSecurityInformation(array $information)
2289
    {
2290
        $this->securityInformation = $information;
2291
    }
2292
2293
    /**
2294
     * {@inheritdoc}
2295
     */
2296
    public function getSecurityInformation()
2297
    {
2298
        return $this->securityInformation;
2299
    }
2300
2301
    /**
2302
     * Return the list of permissions the user should have in order to display the admin.
2303
     *
2304
     * @param string $context
2305
     *
2306
     * @return array
2307
     */
2308
    public function getPermissionsShow($context)
2309
    {
2310
        switch ($context) {
2311
            case self::CONTEXT_DASHBOARD:
2312
            case self::CONTEXT_MENU:
2313
            default:
2314
                return array('LIST');
2315
        }
2316
    }
2317
2318
    /**
2319
     * {@inheritdoc}
2320
     */
2321
    public function showIn($context)
2322
    {
2323
        switch ($context) {
2324
            case self::CONTEXT_DASHBOARD:
2325
            case self::CONTEXT_MENU:
2326
            default:
2327
                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...
2328
        }
2329
    }
2330
2331
    /**
2332
     * {@inheritdoc}
2333
     */
2334
    public function createObjectSecurity($object)
2335
    {
2336
        $this->getSecurityHandler()->createObjectSecurity($this, $object);
2337
    }
2338
2339
    /**
2340
     * {@inheritdoc}
2341
     */
2342
    public function setSecurityHandler(SecurityHandlerInterface $securityHandler)
2343
    {
2344
        $this->securityHandler = $securityHandler;
2345
    }
2346
2347
    /**
2348
     * {@inheritdoc}
2349
     */
2350
    public function getSecurityHandler()
2351
    {
2352
        return $this->securityHandler;
2353
    }
2354
2355
    /**
2356
     * {@inheritdoc}
2357
     */
2358
    public function isGranted($name, $object = null)
2359
    {
2360
        $key = md5(json_encode($name).($object ? '/'.spl_object_hash($object) : ''));
2361
2362
        if (!array_key_exists($key, $this->cacheIsGranted)) {
2363
            $this->cacheIsGranted[$key] = $this->securityHandler->isGranted($this, $name, $object ?: $this);
2364
        }
2365
2366
        return $this->cacheIsGranted[$key];
2367
    }
2368
2369
    /**
2370
     * {@inheritdoc}
2371
     */
2372
    public function getUrlsafeIdentifier($entity)
2373
    {
2374
        return $this->getModelManager()->getUrlsafeIdentifier($entity);
2375
    }
2376
2377
    /**
2378
     * {@inheritdoc}
2379
     */
2380
    public function getNormalizedIdentifier($entity)
2381
    {
2382
        return $this->getModelManager()->getNormalizedIdentifier($entity);
2383
    }
2384
2385
    /**
2386
     * {@inheritdoc}
2387
     */
2388
    public function id($entity)
2389
    {
2390
        return $this->getNormalizedIdentifier($entity);
2391
    }
2392
2393
    /**
2394
     * {@inheritdoc}
2395
     */
2396
    public function setValidator($validator)
2397
    {
2398
        // TODO: Remove it when bumping requirements to SF 2.5+
2399
        if (!$validator instanceof ValidatorInterface && !$validator instanceof LegacyValidatorInterface) {
0 ignored issues
show
Bug introduced by
The class Symfony\Component\Validator\ValidatorInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
2400
            throw new \InvalidArgumentException(
2401
                'Argument 1 must be an instance of Symfony\Component\Validator\Validator\ValidatorInterface'
2402
                .' or Symfony\Component\Validator\ValidatorInterface'
2403
            );
2404
        }
2405
2406
        $this->validator = $validator;
2407
    }
2408
2409
    /**
2410
     * {@inheritdoc}
2411
     */
2412
    public function getValidator()
2413
    {
2414
        return $this->validator;
2415
    }
2416
2417
    /**
2418
     * {@inheritdoc}
2419
     */
2420
    public function getShow()
2421
    {
2422
        $this->buildShow();
2423
2424
        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...
2425
    }
2426
2427
    /**
2428
     * {@inheritdoc}
2429
     */
2430
    public function setFormTheme(array $formTheme)
2431
    {
2432
        $this->formTheme = $formTheme;
2433
    }
2434
2435
    /**
2436
     * {@inheritdoc}
2437
     */
2438
    public function getFormTheme()
2439
    {
2440
        return $this->formTheme;
2441
    }
2442
2443
    /**
2444
     * {@inheritdoc}
2445
     */
2446
    public function setFilterTheme(array $filterTheme)
2447
    {
2448
        $this->filterTheme = $filterTheme;
2449
    }
2450
2451
    /**
2452
     * {@inheritdoc}
2453
     */
2454
    public function getFilterTheme()
2455
    {
2456
        return $this->filterTheme;
2457
    }
2458
2459
    /**
2460
     * {@inheritdoc}
2461
     */
2462
    public function addExtension(AdminExtensionInterface $extension)
2463
    {
2464
        $this->extensions[] = $extension;
2465
    }
2466
2467
    /**
2468
     * {@inheritdoc}
2469
     */
2470
    public function getExtensions()
2471
    {
2472
        return $this->extensions;
2473
    }
2474
2475
    /**
2476
     * {@inheritdoc}
2477
     */
2478
    public function setMenuFactory(MenuFactoryInterface $menuFactory)
2479
    {
2480
        $this->menuFactory = $menuFactory;
2481
    }
2482
2483
    /**
2484
     * {@inheritdoc}
2485
     */
2486
    public function getMenuFactory()
2487
    {
2488
        return $this->menuFactory;
2489
    }
2490
2491
    /**
2492
     * {@inheritdoc}
2493
     */
2494
    public function setRouteBuilder(RouteBuilderInterface $routeBuilder)
2495
    {
2496
        $this->routeBuilder = $routeBuilder;
2497
    }
2498
2499
    /**
2500
     * {@inheritdoc}
2501
     */
2502
    public function getRouteBuilder()
2503
    {
2504
        return $this->routeBuilder;
2505
    }
2506
2507
    /**
2508
     * {@inheritdoc}
2509
     */
2510
    public function toString($object)
2511
    {
2512
        if (!is_object($object)) {
2513
            return '';
2514
        }
2515
2516
        if (method_exists($object, '__toString') && null !== $object->__toString()) {
2517
            return (string) $object;
2518
        }
2519
2520
        return sprintf('%s:%s', ClassUtils::getClass($object), spl_object_hash($object));
2521
    }
2522
2523
    /**
2524
     * {@inheritdoc}
2525
     */
2526
    public function setLabelTranslatorStrategy(LabelTranslatorStrategyInterface $labelTranslatorStrategy)
2527
    {
2528
        $this->labelTranslatorStrategy = $labelTranslatorStrategy;
2529
    }
2530
2531
    /**
2532
     * {@inheritdoc}
2533
     */
2534
    public function getLabelTranslatorStrategy()
2535
    {
2536
        return $this->labelTranslatorStrategy;
2537
    }
2538
2539
    /**
2540
     * {@inheritdoc}
2541
     */
2542
    public function supportsPreviewMode()
2543
    {
2544
        return $this->supportsPreviewMode;
2545
    }
2546
2547
    /**
2548
     * Set custom per page options.
2549
     *
2550
     * @param array $options
2551
     */
2552
    public function setPerPageOptions(array $options)
2553
    {
2554
        $this->perPageOptions = $options;
2555
    }
2556
2557
    /**
2558
     * Returns predefined per page options.
2559
     *
2560
     * @return array
2561
     */
2562
    public function getPerPageOptions()
2563
    {
2564
        return $this->perPageOptions;
2565
    }
2566
2567
    /**
2568
     * Set pager type.
2569
     *
2570
     * @param string $pagerType
2571
     */
2572
    public function setPagerType($pagerType)
2573
    {
2574
        $this->pagerType = $pagerType;
2575
    }
2576
2577
    /**
2578
     * Get pager type.
2579
     *
2580
     * @return string
2581
     */
2582
    public function getPagerType()
2583
    {
2584
        return $this->pagerType;
2585
    }
2586
2587
    /**
2588
     * Returns true if the per page value is allowed, false otherwise.
2589
     *
2590
     * @param int $perPage
2591
     *
2592
     * @return bool
2593
     */
2594
    public function determinedPerPageValue($perPage)
2595
    {
2596
        return in_array($perPage, $this->perPageOptions);
2597
    }
2598
2599
    /**
2600
     * {@inheritdoc}
2601
     */
2602
    public function isAclEnabled()
2603
    {
2604
        return $this->getSecurityHandler() instanceof AclSecurityHandlerInterface;
2605
    }
2606
2607
    /**
2608
     * {@inheritdoc}
2609
     */
2610
    public function getObjectMetadata($object)
2611
    {
2612
        return new Metadata($this->toString($object));
2613
    }
2614
2615
    /**
2616
     * {@inheritdoc}
2617
     */
2618
    public function getListModes()
2619
    {
2620
        return $this->listModes;
2621
    }
2622
2623
    /**
2624
     * {@inheritdoc}
2625
     */
2626
    public function setListMode($mode)
2627
    {
2628
        if (!$this->hasRequest()) {
2629
            throw new \RuntimeException(sprintf('No request attached to the current admin: %s', $this->getCode()));
2630
        }
2631
2632
        $this->getRequest()->getSession()->set(sprintf('%s.list_mode', $this->getCode()), $mode);
2633
    }
2634
2635
    /**
2636
     * {@inheritdoc}
2637
     */
2638
    public function getListMode()
2639
    {
2640
        if (!$this->hasRequest()) {
2641
            return 'list';
2642
        }
2643
2644
        return $this->getRequest()->getSession()->get(sprintf('%s.list_mode', $this->getCode()), 'list');
2645
    }
2646
2647
    /**
2648
     * {@inheritdoc}
2649
     */
2650
    public function getAccessMapping()
2651
    {
2652
        return $this->accessMapping;
2653
    }
2654
2655
    /**
2656
     * {@inheritdoc}
2657
     */
2658
    public function checkAccess($action, $object = null)
2659
    {
2660
        $access = $this->getAccess();
2661
2662
        if (!array_key_exists($action, $access)) {
2663
            throw new \InvalidArgumentException(sprintf(
2664
                'Action "%s" could not be found in access mapping.'
2665
                .' Please make sure your action is defined into your admin class accessMapping property.',
2666
                $action
2667
            ));
2668
        }
2669
2670
        if (!is_array($access[$action])) {
2671
            $access[$action] = array($access[$action]);
2672
        }
2673
2674
        foreach ($access[$action] as $role) {
2675
            if (false === $this->isGranted($role, $object)) {
2676
                throw new AccessDeniedException(sprintf('Access Denied to the action %s and role %s', $action, $role));
2677
            }
2678
        }
2679
    }
2680
2681
    /**
2682
     * {@inheritdoc}
2683
     */
2684
    public function hasAccess($action, $object = null)
2685
    {
2686
        $access = $this->getAccess();
2687
2688
        if (!array_key_exists($action, $access)) {
2689
            return false;
2690
        }
2691
2692
        if (!is_array($access[$action])) {
2693
            $access[$action] = array($access[$action]);
2694
        }
2695
2696
        foreach ($access[$action] as $role) {
2697
            if (false === $this->isGranted($role, $object)) {
2698
                return false;
2699
            }
2700
        }
2701
2702
        return true;
2703
    }
2704
2705
    /**
2706
     * {@inheritdoc}
2707
     */
2708
    final public function getActionButtons($action, $object = null)
2709
    {
2710
        $buttonList = array();
2711
2712
        if (in_array($action, array('tree', 'show', 'edit', 'delete', 'list', 'batch'))
2713
            && $this->hasAccess('create')
2714
            && $this->hasRoute('create')
2715
        ) {
2716
            $buttonList['create'] = array(
2717
                'template' => $this->getTemplate('button_create'),
2718
            );
2719
        }
2720
2721
        if (in_array($action, array('show', 'delete', 'acl', 'history'))
2722
            && $this->canAccessObject('edit', $object)
2723
            && $this->hasRoute('edit')
2724
        ) {
2725
            $buttonList['edit'] = array(
2726
                'template' => $this->getTemplate('button_edit'),
2727
            );
2728
        }
2729
2730
        if (in_array($action, array('show', 'edit', 'acl'))
2731
            && $this->canAccessObject('history', $object)
2732
            && $this->hasRoute('history')
2733
        ) {
2734
            $buttonList['history'] = array(
2735
                'template' => $this->getTemplate('button_history'),
2736
            );
2737
        }
2738
2739
        if (in_array($action, array('edit', 'history'))
2740
            && $this->isAclEnabled()
2741
            && $this->canAccessObject('acl', $object)
2742
            && $this->hasRoute('acl')
2743
        ) {
2744
            $buttonList['acl'] = array(
2745
                'template' => $this->getTemplate('button_acl'),
2746
            );
2747
        }
2748
2749
        if (in_array($action, array('edit', 'history', 'acl'))
2750
            && $this->canAccessObject('show', $object)
2751
            && count($this->getShow()) > 0
2752
            && $this->hasRoute('show')
2753
        ) {
2754
            $buttonList['show'] = array(
2755
                'template' => $this->getTemplate('button_show'),
2756
            );
2757
        }
2758
2759
        if (in_array($action, array('show', 'edit', 'delete', 'acl', 'batch'))
2760
            && $this->hasAccess('list')
2761
            && $this->hasRoute('list')
2762
        ) {
2763
            $buttonList['list'] = array(
2764
                'template' => $this->getTemplate('button_list'),
2765
            );
2766
        }
2767
2768
        $buttonList = $this->configureActionButtons($buttonList, $action, $object);
2769
2770
        foreach ($this->getExtensions() as $extension) {
2771
            $buttonList = $extension->configureActionButtons($this, $buttonList, $action, $object);
2772
        }
2773
2774
        return $buttonList;
2775
    }
2776
2777
    /**
2778
     * {@inheritdoc}
2779
     */
2780
    public function getDashboardActions()
2781
    {
2782
        $actions = array();
2783
2784
        if ($this->hasRoute('create') && $this->hasAccess('create')) {
2785
            $actions['create'] = array(
2786
                'label' => 'link_add',
2787
                'translation_domain' => 'SonataAdminBundle',
2788
                'template' => $this->getTemplate('action_create'),
2789
                'url' => $this->generateUrl('create'),
2790
                'icon' => 'plus-circle',
2791
            );
2792
        }
2793
2794
        if ($this->hasRoute('list') && $this->hasAccess('list')) {
2795
            $actions['list'] = array(
2796
                'label' => 'link_list',
2797
                'translation_domain' => 'SonataAdminBundle',
2798
                'url' => $this->generateUrl('list'),
2799
                'icon' => 'list',
2800
            );
2801
        }
2802
2803
        return $actions;
2804
    }
2805
2806
    /**
2807
     * {@inheritdoc}
2808
     */
2809
    final public function showMosaicButton($isShown)
2810
    {
2811
        if ($isShown) {
2812
            $this->listModes['mosaic'] = array('class' => self::MOSAIC_ICON_CLASS);
2813
        } else {
2814
            unset($this->listModes['mosaic']);
2815
        }
2816
    }
2817
2818
    /**
2819
     * {@inheritdoc}
2820
     */
2821
    final public function getSearchResultLink($object)
2822
    {
2823
        foreach ($this->searchResultActions as $action) {
2824
            if ($this->hasRoute($action) && $this->hasAccess($action, $object)) {
2825
                return $this->generateObjectUrl($action, $object);
2826
            }
2827
        }
2828
2829
        return;
2830
    }
2831
2832
    /**
2833
     * Checks if a filter type is set to a default value.
2834
     *
2835
     * @param string $name
2836
     *
2837
     * @return bool
2838
     */
2839
    final public function isDefaultFilter($name)
2840
    {
2841
        $filter = $this->getFilterParameters();
2842
        $default = $this->getDefaultFilterValues();
2843
2844
        if (!array_key_exists($name, $filter) || !array_key_exists($name, $default)) {
2845
            return false;
2846
        }
2847
2848
        return $filter[$name] == $default[$name];
2849
    }
2850
2851
    /**
2852
     * Check object existence and access, without throw Exception.
2853
     *
2854
     * @param string $action
2855
     * @param object $object
2856
     *
2857
     * @return bool
2858
     */
2859
    public function canAccessObject($action, $object)
2860
    {
2861
        return $object && $this->id($object) && $this->hasAccess($action, $object);
2862
    }
2863
2864
    /**
2865
     * Hook to run after initilization.
2866
     */
2867
    protected function configure()
2868
    {
2869
    }
2870
2871
    /**
2872
     * urlize the given word.
2873
     *
2874
     * @param string $word
2875
     * @param string $sep  the separator
2876
     *
2877
     * @return string
2878
     */
2879
    final protected function urlize($word, $sep = '_')
2880
    {
2881
        return strtolower(preg_replace('/[^a-z0-9_]/i', $sep.'$1', $word));
2882
    }
2883
2884
    /**
2885
     * Returns a list of default filters.
2886
     *
2887
     * @return array
2888
     */
2889
    final protected function getDefaultFilterValues()
2890
    {
2891
        $defaultFilterValues = array();
2892
2893
        $this->configureDefaultFilterValues($defaultFilterValues);
2894
2895
        foreach ($this->getExtensions() as $extension) {
2896
            // NEXT_MAJOR: remove method check in next major release
2897
            if (method_exists($extension, 'configureDefaultFilterValues')) {
2898
                $extension->configureDefaultFilterValues($this, $defaultFilterValues);
2899
            }
2900
        }
2901
2902
        return $defaultFilterValues;
2903
    }
2904
2905
    /**
2906
     * {@inheritdoc}
2907
     */
2908
    protected function configureFormFields(FormMapper $form)
2909
    {
2910
    }
2911
2912
    /**
2913
     * @param ListMapper $list
2914
     */
2915
    protected function configureListFields(ListMapper $list)
2916
    {
2917
    }
2918
2919
    /**
2920
     * @param DatagridMapper $filter
2921
     */
2922
    protected function configureDatagridFilters(DatagridMapper $filter)
2923
    {
2924
    }
2925
2926
    /**
2927
     * @param ShowMapper $show
2928
     */
2929
    protected function configureShowFields(ShowMapper $show)
2930
    {
2931
    }
2932
2933
    /**
2934
     * @param RouteCollection $collection
2935
     */
2936
    protected function configureRoutes(RouteCollection $collection)
2937
    {
2938
    }
2939
2940
    /**
2941
     * Configure buttons for an action.
2942
     *
2943
     * @param array  $buttonList List of all action buttons
2944
     * @param string $action     Current action route
2945
     * @param object $object     Current object
2946
     *
2947
     * @return array
2948
     */
2949
    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...
2950
    {
2951
        return $buttonList;
2952
    }
2953
2954
    /**
2955
     * Allows you to customize batch actions.
2956
     *
2957
     * @param array $actions List of actions
2958
     *
2959
     * @return array
2960
     */
2961
    protected function configureBatchActions($actions)
2962
    {
2963
        return $actions;
2964
    }
2965
2966
    /**
2967
     * NEXT_MAJOR: remove this method.
2968
     *
2969
     * @param MenuItemInterface $menu
2970
     * @param                   $action
2971
     * @param AdminInterface    $childAdmin
2972
     *
2973
     * @return mixed
2974
     *
2975
     * @deprecated Use configureTabMenu instead
2976
     */
2977
    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...
2978
    {
2979
    }
2980
2981
    /**
2982
     * Configures the tab menu in your admin.
2983
     *
2984
     * @param MenuItemInterface $menu
2985
     * @param string            $action
2986
     * @param AdminInterface    $childAdmin
2987
     *
2988
     * @return mixed
2989
     */
2990
    protected function configureTabMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null)
2991
    {
2992
        // Use configureSideMenu not to mess with previous overrides
2993
        // TODO remove once deprecation period is over
2994
        $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...
2995
    }
2996
2997
    /**
2998
     * build the view FieldDescription array.
2999
     */
3000
    protected function buildShow()
3001
    {
3002
        if ($this->show) {
3003
            return;
3004
        }
3005
3006
        $this->show = new FieldDescriptionCollection();
3007
        $mapper = new ShowMapper($this->showBuilder, $this->show, $this);
3008
3009
        $this->configureShowFields($mapper);
3010
3011
        foreach ($this->getExtensions() as $extension) {
3012
            $extension->configureShowFields($mapper);
3013
        }
3014
    }
3015
3016
    /**
3017
     * build the list FieldDescription array.
3018
     */
3019
    protected function buildList()
3020
    {
3021
        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...
3022
            return;
3023
        }
3024
3025
        $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...
3026
3027
        $mapper = new ListMapper($this->getListBuilder(), $this->list, $this);
3028
3029
        if (count($this->getBatchActions()) > 0) {
3030
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
3031
                $this->getClass(),
3032
                'batch',
3033
                array(
3034
                    'label' => 'batch',
3035
                    'code' => '_batch',
3036
                    'sortable' => false,
3037
                    'virtual_field' => true,
3038
                )
3039
            );
3040
3041
            $fieldDescription->setAdmin($this);
3042
            $fieldDescription->setTemplate($this->getTemplate('batch'));
3043
3044
            $mapper->add($fieldDescription, 'batch');
3045
        }
3046
3047
        $this->configureListFields($mapper);
3048
3049
        foreach ($this->getExtensions() as $extension) {
3050
            $extension->configureListFields($mapper);
3051
        }
3052
3053
        if ($this->hasRequest() && $this->getRequest()->isXmlHttpRequest()) {
3054
            $fieldDescription = $this->getModelManager()->getNewFieldDescriptionInstance(
3055
                $this->getClass(),
3056
                'select',
3057
                array(
3058
                    'label' => false,
3059
                    'code' => '_select',
3060
                    'sortable' => false,
3061
                    'virtual_field' => false,
3062
                )
3063
            );
3064
3065
            $fieldDescription->setAdmin($this);
3066
            $fieldDescription->setTemplate($this->getTemplate('select'));
3067
3068
            $mapper->add($fieldDescription, 'select');
3069
        }
3070
    }
3071
3072
    /**
3073
     * Build the form FieldDescription collection.
3074
     */
3075
    protected function buildForm()
3076
    {
3077
        if ($this->form) {
3078
            return;
3079
        }
3080
3081
        // append parent object if any
3082
        // todo : clean the way the Admin class can retrieve set the object
3083
        if ($this->isChild() && $this->getParentAssociationMapping()) {
3084
            $parent = $this->getParent()->getObject($this->request->get($this->getParent()->getIdParameter()));
3085
3086
            $propertyAccessor = $this->getConfigurationPool()->getPropertyAccessor();
3087
            $propertyPath = new PropertyPath($this->getParentAssociationMapping());
3088
3089
            $object = $this->getSubject();
3090
3091
            $value = $propertyAccessor->getValue($object, $propertyPath);
3092
3093
            if (is_array($value) || ($value instanceof \Traversable && $value instanceof \ArrayAccess)) {
3094
                $value[] = $parent;
3095
                $propertyAccessor->setValue($object, $propertyPath, $value);
3096
            } else {
3097
                $propertyAccessor->setValue($object, $propertyPath, $parent);
3098
            }
3099
        }
3100
3101
        $this->form = $this->getFormBuilder()->getForm();
3102
    }
3103
3104
    /**
3105
     * Gets the subclass corresponding to the given name.
3106
     *
3107
     * @param string $name The name of the sub class
3108
     *
3109
     * @return string the subclass
3110
     */
3111
    protected function getSubClass($name)
3112
    {
3113
        if ($this->hasSubClass($name)) {
3114
            return $this->subClasses[$name];
3115
        }
3116
3117
        throw new \RuntimeException(sprintf(
3118
            'Unable to find the subclass `%s` for admin `%s`',
3119
            $name,
3120
            get_class($this)
3121
        ));
3122
    }
3123
3124
    /**
3125
     * Attach the inline validator to the model metadata, this must be done once per admin.
3126
     */
3127
    protected function attachInlineValidator()
3128
    {
3129
        $admin = $this;
3130
3131
        // add the custom inline validation option
3132
        // TODO: Remove conditional method when bumping requirements to SF 2.5+
3133
        if (method_exists($this->validator, 'getMetadataFor')) {
3134
            $metadata = $this->validator->getMetadataFor($this->getClass());
3135
        } else {
3136
            $metadata = $this->validator->getMetadataFactory()->getMetadataFor($this->getClass());
0 ignored issues
show
Bug introduced by
The method getMetadataFactory() does not seem to exist on object<Symfony\Component...tor\ValidatorInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
3137
        }
3138
3139
        $metadata->addConstraint(new InlineConstraint(array(
3140
            'service' => $this,
3141
            'method' => function (ErrorElement $errorElement, $object) use ($admin) {
3142
                /* @var \Sonata\AdminBundle\Admin\AdminInterface $admin */
3143
3144
                // This avoid the main validation to be cascaded to children
3145
                // The problem occurs when a model Page has a collection of Page as property
3146
                if ($admin->hasSubject() && spl_object_hash($object) !== spl_object_hash($admin->getSubject())) {
3147
                    return;
3148
                }
3149
3150
                $admin->validate($errorElement, $object);
3151
3152
                foreach ($admin->getExtensions() as $extension) {
3153
                    $extension->validate($admin, $errorElement, $object);
3154
                }
3155
            },
3156
            'serializingWarning' => true,
3157
        )));
3158
    }
3159
3160
    /**
3161
     * Predefine per page options.
3162
     */
3163
    protected function predefinePerPageOptions()
3164
    {
3165
        array_unshift($this->perPageOptions, $this->maxPerPage);
3166
        $this->perPageOptions = array_unique($this->perPageOptions);
3167
        sort($this->perPageOptions);
3168
    }
3169
3170
    /**
3171
     * Return list routes with permissions name.
3172
     *
3173
     * @return array
3174
     */
3175
    protected function getAccess()
3176
    {
3177
        $access = array_merge(array(
3178
            'acl' => 'MASTER',
3179
            'export' => 'EXPORT',
3180
            'historyCompareRevisions' => 'EDIT',
3181
            'historyViewRevision' => 'EDIT',
3182
            'history' => 'EDIT',
3183
            'edit' => 'EDIT',
3184
            'show' => 'VIEW',
3185
            'create' => 'CREATE',
3186
            'delete' => 'DELETE',
3187
            'batchDelete' => 'DELETE',
3188
            'list' => 'LIST',
3189
        ), $this->getAccessMapping());
3190
3191
        foreach ($this->extensions as $extension) {
3192
            $access = array_merge($access, $extension->getAccessMapping($this));
3193
        }
3194
3195
        return $access;
3196
    }
3197
3198
    /**
3199
     * Returns a list of default filters.
3200
     *
3201
     * @param array $filterValues
3202
     */
3203
    protected function configureDefaultFilterValues(array &$filterValues)
3204
    {
3205
    }
3206
3207
    /**
3208
     * {@inheritdoc}
3209
     */
3210
    private function buildDatagrid()
3211
    {
3212
        if ($this->datagrid) {
3213
            return;
3214
        }
3215
3216
        $filterParameters = $this->getFilterParameters();
3217
3218
        // transform _sort_by from a string to a FieldDescriptionInterface for the datagrid.
3219
        if (isset($filterParameters['_sort_by']) && is_string($filterParameters['_sort_by'])) {
3220
            if ($this->hasListFieldDescription($filterParameters['_sort_by'])) {
3221
                $filterParameters['_sort_by'] = $this->getListFieldDescription($filterParameters['_sort_by']);
3222
            } else {
3223
                $filterParameters['_sort_by'] = $this->getModelManager()->getNewFieldDescriptionInstance(
3224
                    $this->getClass(),
3225
                    $filterParameters['_sort_by'],
3226
                    array()
3227
                );
3228
3229
                $this->getListBuilder()->buildField(null, $filterParameters['_sort_by'], $this);
3230
            }
3231
        }
3232
3233
        // initialize the datagrid
3234
        $this->datagrid = $this->getDatagridBuilder()->getBaseDatagrid($this, $filterParameters);
3235
3236
        $this->datagrid->getPager()->setMaxPageLinks($this->maxPageLinks);
3237
3238
        $mapper = new DatagridMapper($this->getDatagridBuilder(), $this->datagrid, $this);
3239
3240
        // build the datagrid filter
3241
        $this->configureDatagridFilters($mapper);
3242
3243
        // ok, try to limit to add parent filter
3244
        if ($this->isChild() && $this->getParentAssociationMapping() && !$mapper->has($this->getParentAssociationMapping())) {
3245
            $mapper->add($this->getParentAssociationMapping(), null, array(
3246
                'show_filter' => false,
3247
                'label' => false,
3248
                'field_type' => 'sonata_type_model_hidden',
3249
                'field_options' => array(
3250
                    'model_manager' => $this->getModelManager(),
3251
                ),
3252
                'operator_type' => 'hidden',
3253
            ), null, null, array(
3254
                'admin_code' => $this->getParent()->getCode(),
3255
            ));
3256
        }
3257
3258
        foreach ($this->getExtensions() as $extension) {
3259
            $extension->configureDatagridFilters($mapper);
3260
        }
3261
    }
3262
3263
    /**
3264
     * Build all the related urls to the current admin.
3265
     */
3266
    private function buildRoutes()
3267
    {
3268
        if ($this->loaded['routes']) {
3269
            return;
3270
        }
3271
3272
        $this->loaded['routes'] = true;
3273
3274
        $this->routes = new RouteCollection(
3275
            $this->getBaseCodeRoute(),
3276
            $this->getBaseRouteName(),
3277
            $this->getBaseRoutePattern(),
3278
            $this->getBaseControllerName()
3279
        );
3280
3281
        $this->routeBuilder->build($this, $this->routes);
3282
3283
        $this->configureRoutes($this->routes);
3284
3285
        foreach ($this->getExtensions() as $extension) {
3286
            $extension->configureRoutes($this, $this->routes);
3287
        }
3288
    }
3289
}
3290