Completed
Push — wip-platform ( 50ba02...c03fc2 )
by
unknown
29:01
created

CoreAdmin::removeAllFieldsFromShowGroup()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.2
c 0
b 0
f 0
cc 4
eloc 6
nc 4
nop 2
1
<?php
2
3
/*
4
 * Copyright (C) 2015-2017 Libre Informatique
5
 *
6
 * This file is licenced under the GNU LGPL v3.
7
 * For the full copyright and license information, please view the LICENSE.md
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Blast\Bundle\CoreBundle\Admin;
12
13
use Sonata\AdminBundle\Datagrid\DatagridMapper;
14
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
15
use Sonata\AdminBundle\Datagrid\ListMapper;
16
use Sonata\AdminBundle\Form\FormMapper;
17
use Sonata\AdminBundle\Mapper\BaseMapper;
18
use Sonata\AdminBundle\Show\ShowMapper;
19
use Sonata\AdminBundle\Route\RouteCollection;
20
use Sonata\AdminBundle\Admin\AbstractAdmin as SonataAdmin;
21
use Sonata\DoctrineORMAdminBundle\Admin\FieldDescription;
22
use Blast\Bundle\CoreBundle\Tools\Reflection\ClassAnalyzer;
23
use Blast\Bundle\CoreBundle\Admin\Traits\CollectionsManager;
24
use Blast\Bundle\CoreBundle\Admin\Traits\Mapper;
25
use Blast\Bundle\CoreBundle\Admin\Traits\Templates;
26
use Blast\Bundle\CoreBundle\Admin\Traits\PreEvents;
27
use Blast\Bundle\CoreBundle\Admin\Traits\ManyToManyManager;
28
use Blast\Bundle\CoreBundle\Admin\Traits\Actions;
29
use Blast\Bundle\CoreBundle\Admin\Traits\ListActions;
30
use Blast\Bundle\CoreBundle\CodeGenerator\CodeGeneratorRegistry;
31
use Symfony\Component\PropertyAccess\PropertyAccess;
32
33
abstract class CoreAdmin extends SonataAdmin implements \JsonSerializable
34
{
35
    use CollectionsManager,
36
        ManyToManyManager,
37
        Mapper,
38
        Templates,
39
        PreEvents,
40
        Actions,
41
        ListActions
42
    ;
43
44
    protected $extraTemplates = [];
45
46
    /**
47
     * @var string
48
     */
49
    protected $translationLabelPrefix = 'blast.core';
50
51
    public function configure()
52
    {
53
        parent::configure();
54
55
        $this->getLabelTranslatorStrategy()->setPrefix($this->translationLabelPrefix);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sonata\AdminBundle\Trans...slatorStrategyInterface as the method setPrefix() does only exist in the following implementations of said interface: Blast\Bundle\CoreBundle\...LabelTranslatorStrategy.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
56
    }
57
58
    /**
59
     * Configure routes for list actions.
60
     *
61
     * @param RouteCollection $collection
62
     */
63
    protected function configureRoutes(RouteCollection $collection)
64
    {
65
        parent::configureRoutes($collection);
66
        $collection->add('duplicate', $this->getRouterIdParameter() . '/duplicate');
67
        $collection->add('generateEntityCode');
68
69
        /* Needed or not needed ...
70
         * in sonata-project/admin-bundle/Controller/CRUDController.php
71
         * the batchAction method
72
         * throw exception if the http method is not POST
73
        */
74
        if ($collection->get('batch')) {
75
            $collection->get('batch')->setMethods(['POST']);
76
        }
77
    }
78
79 View Code Duplication
    public function getBaseRouteName()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
80
    {
81
        $configuredBaseRoute = $this->getBaseRouteMapping();
82
83
        if (count($configuredBaseRoute) > 0) {
84
            $this->cachedBaseRouteName = null;
0 ignored issues
show
Bug introduced by
The property cachedBaseRouteName cannot be accessed from this context as it is declared private in class Sonata\AdminBundle\Admin\AbstractAdmin.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
85
            if (isset($configuredBaseRoute['name']) && $this->baseRouteName === null) {
86
                $this->baseRouteName = $configuredBaseRoute['name'];
87
            }
88
        }
89
90
        return parent::getBaseRouteName();
91
    }
92
93 View Code Duplication
    public function getBaseRoutePattern()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
94
    {
95
        $configuredBaseRoute = $this->getBaseRouteMapping();
96
97
        if (count($configuredBaseRoute) > 0) {
98
            $this->cachedBaseRoutePattern = null;
0 ignored issues
show
Bug introduced by
The property cachedBaseRoutePattern cannot be accessed from this context as it is declared private in class Sonata\AdminBundle\Admin\AbstractAdmin.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
99
            if (isset($configuredBaseRoute['pattern']) && $this->baseRoutePattern === null) {
100
                $this->baseRoutePattern = $configuredBaseRoute['pattern'];
101
            }
102
        }
103
104
        return parent::getBaseRoutePattern();
105
    }
106
107
    public function getFormTheme()
108
    {
109
        return array_merge($this->formTheme, $this->getFormThemeMapping());
110
    }
111
112
    /**
113
     * @param DatagridMapper $mapper
114
     */
115
    protected function configureDatagridFilters(DatagridMapper $mapper)
116
    {
117
        if (!$this->configureMapper($mapper)) {
118
            $this->fallbackConfiguration($mapper, __FUNCTION__);
119
        }
120
    }
121
122
    /**
123
     * @param ListMapper $mapper
124
     */
125
    protected function configureListFields(ListMapper $mapper)
126
    {
127
        if (!$this->configureMapper($mapper)) {
128
            $this->fallbackConfiguration($mapper, __FUNCTION__);
129
        }
130
    }
131
132
    /**
133
     * @param FormMapper $mapper
134
     */
135
    protected function configureFormFields(FormMapper $mapper)
136
    {
137
        if (!$this->configureMapper($mapper)) {
138
            $this->fallbackConfiguration($mapper, __FUNCTION__);
139
        }
140
    }
141
142
    /**
143
     * @param ShowMapper $mapper
144
     */
145
    protected function configureShowFields(ShowMapper $mapper)
146
    {
147
        if (!$this->configureMapper($mapper)) {
148
            $this->fallbackConfiguration($mapper, __FUNCTION__);
149
        }
150
    }
151
152
    /**
153
     * Changes nested entity link (in show views) to /show instead of /edit as default.
154
     *
155
     * @param BaseMapper $mapper
156
     */
157
    protected function fixShowRoutes(BaseMapper $mapper)
158
    {
159
        foreach (['getShow', 'getList'] as $fct) {
160
            foreach ($this->$fct()->getElements() as $field) {
161
                if ($field instanceof FieldDescription) {
162
                    $options = $field->getOptions();
163
                    if ($options['route']['name'] != 'edit') {
164
                        continue;
165
                    }
166
167
                    $options['route']['name'] = 'show';
168
                    $field->setOptions($options);
169
                }
170
            }
171
        }
172
173
        return $this;
174
    }
175
176
    protected function getCurrentComposition()
177
    {
178
        // traits of the current Entity
179
        $classes = ClassAnalyzer::getTraits($this->getClass());
180
181
        $interfaces = ClassAnalyzer::getInterfaces($this->getClass());
182
183
        // implementations of the current Entity
184
        foreach (array_reverse($interfaces) as $interface) {
185
            $classes[] = $interface;
186
        }
187
188
        // inheritance of the current Entity
189
        foreach (array_reverse([$this->getClass()] + class_parents($this->getClass())) as $class) {
190
            $classes[] = $class;
191
        }
192
193
        // inheritance of the current Admin
194
        foreach (array_reverse([$this->getOriginalClass()] + $this->getParentClasses()) as $admin) {
195
            $classes[] = $admin;
196
        }
197
198
        return $classes;
199
    }
200
201
    private function fallbackConfiguration(BaseMapper $mapper, $function)
202
    {
203
        // fallback
204
        $rm = new \ReflectionMethod($this->getParentClass(), $function);
205
        if ($rm->class == $this->getParentClass()) {
206
            $this->configureFields($function, $mapper, $this->getParentClass());
207
        }
208
    }
209
210
    /**
211
     * Returns the level of depth of an array.
212
     *
213
     * @param array $array
214
     * @param int   $level : do not use, just used for recursivity
215
     *
216
     * @return int : depth
217
     */
218
    private static function arrayDepth($array, $level = 0)
219
    {
220
        if (!$array) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $array 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...
221
            return $level;
222
        }
223
224
        if (!is_array($array)) {
225
            return $level;
226
        }
227
228
        $level++;
229
        foreach ($array as $key => $value) {
230
            if (is_array($value)) {
231
                $level = $level < self::arrayDepth($value, $level) ? self::arrayDepth($value, $level) : $level;
232
            }
233
        }
234
235
        return $level;
236
    }
237
238
    protected function getOriginalClass()
239
    {
240
        return get_called_class();
241
    }
242
243
    protected function getParentClasses()
244
    {
245
        return class_parents($this->getOriginalClass());
246
    }
247
248
    protected function getParentClass()
249
    {
250
        return get_parent_class($this->getOriginalClass());
251
    }
252
253
    protected function getGrandParentClass()
254
    {
255
        return get_parent_class(get_parent_class($this->getOriginalClass()));
256
    }
257
258
    /**
259
     * @param string $view     'list', 'show', 'form', etc
260
     * @param string $template template name
261
     */
262
    public function addExtraTemplate($view, $template)
263
    {
264
        if (empty($this->extraTemplates[$view])) {
265
            $this->extraTemplates[$view] = [];
266
        }
267
        if (!in_array($template, $this->extraTemplates[$view])) {
268
            $this->extraTemplates[$view][] = $template;
269
        }
270
    }
271
272
    /**
273
     * @param string $view 'list', 'show', 'form', etc
274
     *
275
     * @return array array of template names
276
     */
277
    public function getExtraTemplates($view)
278
    {
279
        if (empty($this->extraTemplates[$view])) {
280
            $this->extraTemplates[$view] = [];
281
        }
282
283
        return $this->extraTemplates[$view];
284
    }
285
286
    /**
287
     * @param string $view 'list', 'show', 'form', etc
288
     * @param array  $link link (array keys should be: 'label', 'url', 'class', 'title')
289
     */
290
    public function addHelperLink($view, $link)
291
    {
292
        if (empty($this->helperLinks[$view])) {
293
            $this->helperLinks[$view] = [];
294
        }
295
296
        // Do not add links without URL
297
        if (empty($link['url'])) {
298
            return;
299
        }
300
301
        // Do not add two links with the same URL
302
        foreach ($this->helperLinks[$view] as $l) {
303
            if ($l['url'] == $link['url']) {
304
                return;
305
            }
306
        }
307
308
        $this->helperLinks[$view][] = $link;
309
    }
310
311
    /**
312
     * @param string $view 'list', 'show', 'form', etc
313
     *
314
     * @return array array of links (each link is an array with keys 'label', 'url', 'class' and 'title')
315
     */
316
    public function getHelperLinks($view)
317
    {
318
        if (empty($this->helperLinks[$view])) {
319
            $this->helperLinks[$view] = [];
320
        }
321
322
        return $this->helperLinks[$view];
323
    }
324
325
    /**
326
     * Checks if a Bundle is installed.
327
     *
328
     * @param string $bundle Bundle name or class FQN
329
     */
330
    public function bundleExists($bundle)
331
    {
332
        $kernelBundles = $this->getConfigurationPool()->getContainer()->getParameter('kernel.bundles');
333
        if (array_key_exists($bundle, $kernelBundles)) {
334
            return true;
335
        }
336
        if (in_array($bundle, $kernelBundles)) {
337
            return true;
338
        }
339
340
        return false;
341
    }
342
343
    /**
344
     * Rename a form tab after form fields have been configured.
345
     *
346
     * TODO: groups of the renamed tab are still prefixed with the old tab name
347
     *
348
     * @param type $tabName    the name of the tab to be renamed
349
     * @param type $newTabName the new name for the tab
350
     */
351 View Code Duplication
    public function renameFormTab($tabName, $newTabName, $keepOrder = true)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
352
    {
353
        $tabs = $this->getFormTabs();
354
355
        if (!$tabs) {
356
            return;
357
        }
358
359
        if (!isset($tabs[$tabName])) {
360
            throw new \Exception(sprintf('Tab %s does not exist.', $tabName));
361
        }
362
        if (isset($tabs[$newTabName])) {
363
            return;
364
        }
365
366
        if ($keepOrder) {
367
            $keys = array_keys($tabs);
368
            $keys[array_search($tabName, $keys)] = $newTabName;
369
            $tabs = array_combine($keys, $tabs);
370
        } else {
371
            $tabs[$newTabName] = $tabs[$tabName];
372
            unset($tabs[$tabName]);
373
        }
374
375
        $this->setFormTabs($tabs);
376
    }
377
378
    /**
379
     * Rename a show tab after show fields have been configured.
380
     *
381
     * TODO: groups of the renamed tab are still prefixed with the old tab name
382
     *
383
     * @param type $tabName    the name of the tab to be renamed
384
     * @param type $newTabName the new name for the tab
385
     */
386 View Code Duplication
    public function renameShowTab($tabName, $newTabName, $keepOrder = true)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
387
    {
388
        $tabs = $this->getShowTabs();
389
390
        if (!$tabs) {
391
            return;
392
        }
393
394
        if (!isset($tabs[$tabName])) {
395
            throw new \Exception(sprintf('Tab %s does not exist.', $tabName));
396
        }
397
        if (isset($tabs[$newTabName])) {
398
            return;
399
        }
400
401
        if ($keepOrder) {
402
            $keys = array_keys($tabs);
403
            $keys[array_search($tabName, $keys)] = $newTabName;
404
            $tabs = array_combine($keys, $tabs);
405
        } else {
406
            $tabs[$newTabName] = $tabs[$tabName];
407
            unset($tabs[$tabName]);
408
        }
409
410
        $this->setShowTabs($tabs);
411
    }
412
413
    /**
414
     * Rename a form group.
415
     *
416
     * @param string $group        the old group name
417
     * @param string $tab          the tab the group belongs to
418
     * @param string $newGroupName the new group name
419
     *
420
     * @return self
421
     */
422
    public function renameFormGroup($group, $tab, $newGroupName)
423
    {
424
        $groups = $this->getFormGroups();
425
426
        // When the default tab is used, the tabname is not prepended to the index in the group array
427
        if ($tab !== 'default') {
428
            $group = $tab . '.' . $group;
429
        }
430
        $newGroup = ($tab !== 'default') ? $tab . '.' . $newGroupName : $newGroupName;
431
432
        if (isset($groups[$newGroup])) {
433
            throw new \Exception(sprintf('%s form group already exists.', $newGroup));
434
        }
435
        if (!array_key_exists($group, $groups)) {
436
            throw new \Exception(sprintf('form group « %s » doesn\'t exist.', $group));
437
        }
438
439
        $groups[$newGroup] = $groups[$group];
440
        $groups[$newGroup]['name'] = $newGroupName;
441
        unset($groups[$group]);
442
443
        $tabs = $this->getFormTabs();
444
        $key = array_search($group, $tabs[$tab]['groups']);
445
446
        if (false !== $key) {
447
            $tabs[$tab]['groups'][$key] = $newGroup;
448
        }
449
450
        $this->setFormTabs($tabs);
451
        $this->setFormGroups($groups);
452
453
        return $this;
454
    }
455
456
    /**
457
     * Removes tab in current form Mapper.
458
     *
459
     * @param string|array $tabNames name or array of names of tabs to be removed
460
     * @param FormMapper   $mapper   Sonata Admin form mapper
461
     */
462 View Code Duplication
    public function removeTab($tabNames, $mapper)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
463
    {
464
        $currentTabs = $this->getFormTabs();
465
        foreach ($currentTabs as $k => $item) {
0 ignored issues
show
Bug introduced by
The expression $currentTabs 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...
466
            if (is_array($tabNames) && in_array($item['name'], $tabNames) || !is_array($tabNames) && $item['name'] === $tabNames) {
467
                foreach ($item['groups'] as $groupName) {
468
                    $this->removeAllFieldsFromFormGroup($groupName, $mapper);
469
                }
470
                unset($currentTabs[$k]);
471
            }
472
        }
473
        $this->setFormTabs($currentTabs);
474
    }
475
476
    /**
477
     * Removes tab in current show Mapper.
478
     *
479
     * @param string|array $tabNames name or array of names of tabs to be removed
480
     * @param ShowMapper   $mapper   Sonata Admin form mapper
481
     */
482 View Code Duplication
    public function removeShowTab($tabNames, $mapper)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
483
    {
484
        $currentTabs = $this->getShowTabs();
485
        foreach ($currentTabs as $k => $item) {
0 ignored issues
show
Bug introduced by
The expression $currentTabs 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...
486
            if (is_array($tabNames) && in_array($item['name'], $tabNames) || !is_array($tabNames) && $item['name'] === $tabNames) {
487
                foreach ($item['groups'] as $groupName) {
488
                    $this->removeAllFieldsFromShowGroup($groupName, $mapper);
489
                }
490
                unset($currentTabs[$k]);
491
            }
492
        }
493
        $this->setShowTabs($currentTabs);
494
    }
495
496
    /**
497
     * Removes all fields from form groups and remove them from mapper.
498
     *
499
     * @param string     $groupName Name of the group to remove
500
     * @param FormMapper $mapper    Sonata Admin form mapper
501
     */
502
    public function removeAllFieldsFromFormGroup($groupName, $mapper)
503
    {
504
        $formGroups = $this->getFormGroups();
505
        foreach ($formGroups as $name => $formGroup) {
0 ignored issues
show
Bug introduced by
The expression $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...
506
            if ($name === $groupName) {
507
                foreach ($formGroups[$name]['fields'] as $key => $field) {
508
                    $mapper->remove($key);
509
                }
510
            }
511
        }
512
    }
513
514
    /**
515
     * Removes all fields from form groups and remove them from mapper.
516
     *
517
     * @param string     $groupName Name of the group to remove
518
     * @param ShowMapper $mapper    Sonata Admin form mapper
519
     */
520
    public function removeAllFieldsFromShowGroup($groupName, $mapper)
521
    {
522
        $showGroups = $this->getShowGroups();
523
        foreach ($showGroups as $name => $showGroup) {
0 ignored issues
show
Bug introduced by
The expression $showGroups 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...
524
            if ($name === $groupName) {
525
                foreach ($showGroups[$name]['fields'] as $key => $field) {
526
                    $mapper->remove($key);
527
                }
528
            }
529
        }
530
    }
531
532
    public function jsonSerialize()
533
    {
534
        $propertiesToShow = [
535
            'baseRouteName',
536
            'baseRoutePattern',
537
            'extraTemplates',
538
            'listFieldDescriptions',
539
            'showFieldDescriptions',
540
            'formFieldDescriptions',
541
            'filterFieldDescriptions',
542
            'maxPerPage',
543
            'maxPageLinks',
544
            'classnameLabel',
545
            'translationDomain',
546
            'formOptions',
547
            'datagridValues',
548
            'perPageOptions',
549
            'pagerType',
550
            'code',
551
            'label',
552
            'routes',
553
            'subject',
554
            'children',
555
            'parent',
556
            'baseCodeRoute',
557
            'uniqid',
558
            'extensions',
559
            'class',
560
            'subClasses',
561
            'list',
562
            'show',
563
            'form',
564
            'filter',
565
            'formGroups',
566
            'formTabs',
567
            'showGroups',
568
            'showTabs',
569
            'managedCollections',
570
            'helperLinks',
571
            'titles',
572
        ];
573
574
        $properties = [];
575
        foreach ($this as $key => $value) {
576
            if (in_array($key, $propertiesToShow)) {
577
                $properties[$key] = $value;
578
            }
579
        }
580
581
        return $properties;
582
    }
583
584
    /**
585
     * {@inheritdoc}
586
     */
587
    public function prePersist($object)
588
    {
589
        parent::prePersist($object);
590
591
        $hasCodeGenerator = CodeGeneratorRegistry::hasGeneratorForClass(get_class($object));
592
        if ($hasCodeGenerator) {
593
            $accessor = PropertyAccess::createPropertyAccessor();
594
            foreach (CodeGeneratorRegistry::getCodeGenerators(get_class($object)) as $name => $generator) {
595
                $accessor->setValue($object, $name, $generator->generate($object));
596
            }
597
        }
598
    }
599
600
    public function getFlashManager()
601
    {
602
        return $this->getConfigurationPool()->getContainer()->get('sonata.core.flashmessage.manager');
603
    }
604
605
    /**
606
     * {@inheritdoc}
607
     */
608
    public function preBatchAction($actionName, ProxyQueryInterface $query, array &$idx, $allElements)
609
    {
610
        parent::preBatchAction($actionName, $query, $idx, $allElements);
611
612
        if ($actionName === 'delete') {
613
            $cascadingRelationChecker = $this->getConfigurationPool()->getContainer()->get('blast_core.doctrine.orm.cascading_relation_checker');
614
615
            foreach ($idx as $id) {
616
                $entity = $this->getModelManager()->find($this->getClass(), $id);
617
618
                if ($entity !== null) {
619
                    $undeletableAssociations = $cascadingRelationChecker->beforeEntityDelete($entity, $idx);
620
621
                    if (count($undeletableAssociations) > 0) {
622
                        foreach ($undeletableAssociations as $key => $undeletableAssociation) {
623
                            $undeletableAssociations[$key] = $this->getConfigurationPool()->getContainer()->get('translator')->trans('blast.doctrine_relations.' . $undeletableAssociation, [], 'messages');
624
                        }
625
626
                        $errorMessage = 'Cannot delete "%entity%" because it has remaining relation(s) %relations%';
627
628
                        $message = $this->getTranslator()->trans(
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...tAdmin::getTranslator() has been deprecated with message: since 3.9, to be removed with 4.0

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...
629
                            $errorMessage,
630
                            [
631
                                '%relations%' => trim(implode(', ', $undeletableAssociations)),
632
                                '%entity%'    => (string) $entity,
633
                            ],
634
                            'SonataCoreBundle'
635
                        );
636
637
                        $this->getConfigurationPool()->getContainer()->get('session')->getFlashBag()->add('warning', $message);
638
                    }
639
                }
640
            }
641
        }
642
    }
643
}
644