Completed
Push — master ( aed070...2ff8b4 )
by Grégoire
03:21
created

AddDependencyCallsCompilerPass   C

Complexity

Total Complexity 73

Size/Duplication

Total Lines 392
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
wmc 73
lcom 1
cbo 5
dl 0
loc 392
rs 5.5447
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
D fixTemplates() 0 44 9
B replaceDefaultArguments() 0 15 7
F process() 0 178 38
B applyConfigurationFromAttribute() 0 27 4
F applyDefaults() 0 91 15

How to fix   Complexity   

Complex Class

Complex classes like AddDependencyCallsCompilerPass 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 AddDependencyCallsCompilerPass, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Sonata Project package.
7
 *
8
 * (c) Thomas Rabaix <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Sonata\AdminBundle\DependencyInjection\Compiler;
15
16
use Doctrine\Common\Inflector\Inflector;
17
use Sonata\AdminBundle\Datagrid\Pager;
18
use Symfony\Component\DependencyInjection\ChildDefinition;
19
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
20
use Symfony\Component\DependencyInjection\ContainerBuilder;
21
use Symfony\Component\DependencyInjection\Definition;
22
use Symfony\Component\DependencyInjection\DefinitionDecorator;
23
use Symfony\Component\DependencyInjection\Reference;
24
25
/**
26
 * Add all dependencies to the Admin class, this avoid to write too many lines
27
 * in the configuration files.
28
 *
29
 * @author Thomas Rabaix <[email protected]>
30
 */
31
final class AddDependencyCallsCompilerPass implements CompilerPassInterface
32
{
33
    /**
34
     * {@inheritdoc}
35
     */
36
    public function process(ContainerBuilder $container): void
37
    {
38
        // check if translator service exist
39
        if (!$container->has('translator')) {
40
            throw new \RuntimeException('The "translator" service is not yet enabled.
41
                It\'s required by SonataAdmin to display all labels properly.
42
43
                To learn how to enable the translator service please visit:
44
                http://symfony.com/doc/current/translation.html#configuration
45
             ');
46
        }
47
48
        $parameterBag = $container->getParameterBag();
49
        $groupDefaults = $admins = $classes = [];
50
51
        $pool = $container->getDefinition('sonata.admin.pool');
52
53
        foreach ($container->findTaggedServiceIds('sonata.admin') as $id => $tags) {
54
            foreach ($tags as $attributes) {
55
                $definition = $container->getDefinition($id);
56
                $parentDefinition = null;
57
58
                // Temporary fix until we can support service locators
59
                $definition->setPublic(true);
60
61
                // NEXT_MAJOR: Remove check for DefinitionDecorator instance when dropping Symfony <3.3 support
62
                if ($definition instanceof ChildDefinition ||
63
                    (!class_exists(ChildDefinition::class) && $definition instanceof DefinitionDecorator)) {
64
                    $parentDefinition = $container->getDefinition($definition->getParent());
0 ignored issues
show
Bug introduced by
The method getParent does only exist in Symfony\Component\Depend...jection\ChildDefinition, but not in Symfony\Component\Depend...ion\DefinitionDecorator.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
65
                }
66
67
                $this->replaceDefaultArguments([
68
                    0 => $id,
69
                    2 => 'SonataAdminBundle:CRUD',
70
                ], $definition, $parentDefinition);
71
                $this->applyConfigurationFromAttribute($definition, $attributes);
72
                $this->applyDefaults($container, $id, $attributes);
73
74
                $arguments = $parentDefinition ?
75
                    array_merge($parentDefinition->getArguments(), $definition->getArguments()) :
76
                    $definition->getArguments();
77
78
                $admins[] = $id;
79
80
                if (!isset($classes[$arguments[1]])) {
81
                    $classes[$arguments[1]] = [];
82
                }
83
84
                $classes[$arguments[1]][] = $id;
85
86
                $showInDashboard = (bool) (isset($attributes['show_in_dashboard']) ? $parameterBag->resolveValue($attributes['show_in_dashboard']) : true);
87
                if (!$showInDashboard) {
88
                    continue;
89
                }
90
91
                $resolvedGroupName = isset($attributes['group']) ? $parameterBag->resolveValue($attributes['group']) : 'default';
92
                $labelCatalogue = $attributes['label_catalogue'] ?? 'SonataAdminBundle';
93
                $icon = $attributes['icon'] ?? '<i class="fa fa-folder"></i>';
94
                $onTop = $attributes['on_top'] ?? false;
95
                $keepOpen = $attributes['keep_open'] ?? false;
96
97
                if (!isset($groupDefaults[$resolvedGroupName])) {
98
                    $groupDefaults[$resolvedGroupName] = [
99
                        'label' => $resolvedGroupName,
100
                        'label_catalogue' => $labelCatalogue,
101
                        'icon' => $icon,
102
                        'roles' => [],
103
                        'on_top' => false,
104
                        'keep_open' => false,
105
                    ];
106
                }
107
108
                $groupDefaults[$resolvedGroupName]['items'][] = [
109
                    'admin' => $id,
110
                    'label' => !empty($attributes['label']) ? $attributes['label'] : '',
111
                    'route' => '',
112
                    'route_params' => [],
113
                    'route_absolute' => false,
114
                ];
115
116
                if (isset($groupDefaults[$resolvedGroupName]['on_top']) && $groupDefaults[$resolvedGroupName]['on_top']
117
                    || $onTop && (count($groupDefaults[$resolvedGroupName]['items']) > 1)) {
118
                    throw new \RuntimeException('You can\'t use "on_top" option with multiple same name groups.');
119
                }
120
                $groupDefaults[$resolvedGroupName]['on_top'] = $onTop;
121
122
                $groupDefaults[$resolvedGroupName]['keep_open'] = $keepOpen;
123
            }
124
        }
125
126
        $dashboardGroupsSettings = $container->getParameter('sonata.admin.configuration.dashboard_groups');
127
        if (!empty($dashboardGroupsSettings)) {
128
            $groups = $dashboardGroupsSettings;
129
130
            foreach ($dashboardGroupsSettings as $groupName => $group) {
131
                $resolvedGroupName = $parameterBag->resolveValue($groupName);
132
                if (!isset($groupDefaults[$resolvedGroupName])) {
133
                    $groupDefaults[$resolvedGroupName] = [
134
                        'items' => [],
135
                        'label' => $resolvedGroupName,
136
                        'roles' => [],
137
                        'on_top' => false,
138
                        'keep_open' => false,
139
                    ];
140
                }
141
142
                if (empty($group['items'])) {
143
                    $groups[$resolvedGroupName]['items'] = $groupDefaults[$resolvedGroupName]['items'];
144
                }
145
146
                if (empty($group['label'])) {
147
                    $groups[$resolvedGroupName]['label'] = $groupDefaults[$resolvedGroupName]['label'];
148
                }
149
150
                if (empty($group['label_catalogue'])) {
151
                    $groups[$resolvedGroupName]['label_catalogue'] = 'SonataAdminBundle';
152
                }
153
154
                if (empty($group['icon'])) {
155
                    $groups[$resolvedGroupName]['icon'] = $groupDefaults[$resolvedGroupName]['icon'];
156
                }
157
158
                if (!empty($group['item_adds'])) {
159
                    $groups[$resolvedGroupName]['items'] = array_merge($groups[$resolvedGroupName]['items'], $group['item_adds']);
160
                }
161
162
                if (empty($group['roles'])) {
163
                    $groups[$resolvedGroupName]['roles'] = $groupDefaults[$resolvedGroupName]['roles'];
164
                }
165
166
                if (isset($groups[$resolvedGroupName]['on_top']) && !empty($group['on_top']) && $group['on_top']
167
                    && (count($groups[$resolvedGroupName]['items']) > 1)) {
168
                    throw new \RuntimeException('You can\'t use "on_top" option with multiple same name groups.');
169
                }
170
                if (empty($group['on_top'])) {
171
                    $groups[$resolvedGroupName]['on_top'] = $groupDefaults[$resolvedGroupName]['on_top'];
172
                }
173
174
                if (empty($group['keep_open'])) {
175
                    $groups[$resolvedGroupName]['keep_open'] = $groupDefaults[$resolvedGroupName]['keep_open'];
176
                }
177
            }
178
        } elseif ($container->getParameter('sonata.admin.configuration.sort_admins')) {
179
            $groups = $groupDefaults;
180
181
            $elementSort = function (&$element): void {
182
                usort(
183
                    $element['items'],
184
                    function ($a, $b) {
185
                        $a = !empty($a['label']) ? $a['label'] : $a['admin'];
186
                        $b = !empty($b['label']) ? $b['label'] : $b['admin'];
187
188
                        if ($a === $b) {
189
                            return 0;
190
                        }
191
192
                        return $a < $b ? -1 : 1;
193
                    }
194
                );
195
            };
196
197
            /*
198
             * 1) sort the groups by their index
199
             * 2) sort the elements within each group by label/admin
200
             */
201
            ksort($groups);
202
            array_walk($groups, $elementSort);
203
        } else {
204
            $groups = $groupDefaults;
205
        }
206
207
        $pool->addMethodCall('setAdminServiceIds', [$admins]);
208
        $pool->addMethodCall('setAdminGroups', [$groups]);
209
        $pool->addMethodCall('setAdminClasses', [$classes]);
210
211
        $routeLoader = $container->getDefinition('sonata.admin.route_loader');
212
        $routeLoader->replaceArgument(1, $admins);
213
    }
214
215
    /**
216
     * This method read the attribute keys and configure admin class to use the related dependency.
217
     *
218
     * @param Definition $definition
219
     * @param array      $attributes
220
     */
221
    public function applyConfigurationFromAttribute(Definition $definition, array $attributes): void
222
    {
223
        $keys = [
224
            'model_manager',
225
            'form_contractor',
226
            'show_builder',
227
            'list_builder',
228
            'datagrid_builder',
229
            'translator',
230
            'configuration_pool',
231
            'router',
232
            'validator',
233
            'security_handler',
234
            'menu_factory',
235
            'route_builder',
236
            'label_translator_strategy',
237
        ];
238
239
        foreach ($keys as $key) {
240
            $method = 'set'.Inflector::classify($key);
241
            if (!isset($attributes[$key]) || $definition->hasMethodCall($method)) {
242
                continue;
243
            }
244
245
            $definition->addMethodCall($method, [new Reference($attributes[$key])]);
246
        }
247
    }
248
249
    /**
250
     * Apply the default values required by the AdminInterface to the Admin service definition.
251
     *
252
     * @param ContainerBuilder $container
253
     * @param string           $serviceId
254
     * @param array            $attributes
255
     *
256
     * @return Definition
257
     */
258
    public function applyDefaults(ContainerBuilder $container, $serviceId, array $attributes = [])
259
    {
260
        $definition = $container->getDefinition($serviceId);
261
        $settings = $container->getParameter('sonata.admin.configuration.admin_services');
262
263
        $definition->setShared(false);
264
265
        $manager_type = $attributes['manager_type'];
266
267
        $overwriteAdminConfiguration = $settings[$serviceId] ?? [];
268
269
        $defaultAddServices = [
270
            'model_manager' => sprintf('sonata.admin.manager.%s', $manager_type),
271
            'form_contractor' => sprintf('sonata.admin.builder.%s_form', $manager_type),
272
            'show_builder' => sprintf('sonata.admin.builder.%s_show', $manager_type),
273
            'list_builder' => sprintf('sonata.admin.builder.%s_list', $manager_type),
274
            'datagrid_builder' => sprintf('sonata.admin.builder.%s_datagrid', $manager_type),
275
            'translator' => 'translator',
276
            'configuration_pool' => 'sonata.admin.pool',
277
            'route_generator' => 'sonata.admin.route.default_generator',
278
            'validator' => 'validator',
279
            'security_handler' => 'sonata.admin.security.handler',
280
            'menu_factory' => 'knp_menu.factory',
281
            'route_builder' => 'sonata.admin.route.path_info'.
282
                (('doctrine_phpcr' == $manager_type) ? '_slashes' : ''),
283
            'label_translator_strategy' => 'sonata.admin.label.strategy.native',
284
        ];
285
286
        $definition->addMethodCall('setManagerType', [$manager_type]);
287
288
        foreach ($defaultAddServices as $attr => $addServiceId) {
289
            $method = 'set'.Inflector::classify($attr);
290
291
            if (isset($overwriteAdminConfiguration[$attr]) || !$definition->hasMethodCall($method)) {
292
                $args = [new Reference($overwriteAdminConfiguration[$attr] ?? $addServiceId)];
293
                if ('translator' === $attr) {
294
                    $args[] = false;
295
                }
296
297
                $definition->addMethodCall($method, $args);
298
            }
299
        }
300
301
        if (isset($overwriteAdminConfiguration['pager_type'])) {
302
            $pagerType = $overwriteAdminConfiguration['pager_type'];
303
        } elseif (isset($attributes['pager_type'])) {
304
            $pagerType = $attributes['pager_type'];
305
        } else {
306
            $pagerType = Pager::TYPE_DEFAULT;
307
        }
308
309
        $definition->addMethodCall('setPagerType', [$pagerType]);
310
311
        if (isset($overwriteAdminConfiguration['label'])) {
312
            $label = $overwriteAdminConfiguration['label'];
313
        } elseif (isset($attributes['label'])) {
314
            $label = $attributes['label'];
315
        } else {
316
            $label = '-';
317
        }
318
319
        $definition->addMethodCall('setLabel', [$label]);
320
321
        if (isset($attributes['persist_filters'])) {
322
            $persistFilters = (bool) $attributes['persist_filters'];
323
        } else {
324
            $persistFilters = (bool) $container->getParameter('sonata.admin.configuration.filters.persist');
325
        }
326
327
        $definition->addMethodCall('setPersistFilters', [$persistFilters]);
328
329
        if (isset($overwriteAdminConfiguration['show_mosaic_button'])) {
330
            $showMosaicButton = $overwriteAdminConfiguration['show_mosaic_button'];
331
        } elseif (isset($attributes['show_mosaic_button'])) {
332
            $showMosaicButton = $attributes['show_mosaic_button'];
333
        } else {
334
            $showMosaicButton = $container->getParameter('sonata.admin.configuration.show.mosaic.button');
335
        }
336
337
        $definition->addMethodCall('showMosaicButton', [$showMosaicButton]);
338
339
        $this->fixTemplates($container, $definition, $overwriteAdminConfiguration['templates'] ?? ['view' => []]);
340
341
        if ($container->hasParameter('sonata.admin.configuration.security.information') && !$definition->hasMethodCall('setSecurityInformation')) {
342
            $definition->addMethodCall('setSecurityInformation', ['%sonata.admin.configuration.security.information%']);
343
        }
344
345
        $definition->addMethodCall('initialize');
346
347
        return $definition;
348
    }
349
350
    /**
351
     * @param ContainerBuilder $container
352
     * @param Definition       $definition
353
     * @param array            $overwrittenTemplates
354
     */
355
    public function fixTemplates(ContainerBuilder $container, Definition $definition, array $overwrittenTemplates = []): void
356
    {
357
        $definedTemplates = $container->getParameter('sonata.admin.configuration.templates');
358
359
        $methods = [];
360
        $pos = 0;
361
        foreach ($definition->getMethodCalls() as $method) {
362
            if ('setTemplates' == $method[0]) {
363
                $definedTemplates = array_merge($definedTemplates, $method[1][0]);
364
365
                continue;
366
            }
367
368
            if ('setTemplate' == $method[0]) {
369
                $definedTemplates[$method[1][0]] = $method[1][1];
370
371
                continue;
372
            }
373
374
            // set template for simple pager if it is not already overwritten
375
            if ('setPagerType' === $method[0]
376
                && $method[1][0] === Pager::TYPE_SIMPLE
377
                && (
378
                    !isset($definedTemplates['pager_results'])
379
                    || 'SonataAdminBundle:Pager:results.html.twig' === $definedTemplates['pager_results']
380
                )
381
            ) {
382
                $definedTemplates['pager_results'] = 'SonataAdminBundle:Pager:simple_pager_results.html.twig';
383
            }
384
385
            $methods[$pos] = $method;
386
            ++$pos;
387
        }
388
389
        $definition->setMethodCalls($methods);
390
391
        $definedTemplates = $overwrittenTemplates['view'] + $definedTemplates;
392
393
        if ($container->getParameter('sonata.admin.configuration.templates') !== $definedTemplates) {
394
            $definition->addMethodCall('setTemplates', [$definedTemplates]);
395
        } else {
396
            $definition->addMethodCall('setTemplates', ['%sonata.admin.configuration.templates%']);
397
        }
398
    }
399
400
    /**
401
     * Replace the empty arguments required by the Admin service definition.
402
     *
403
     * @param array           $defaultArguments
404
     * @param Definition      $definition
405
     * @param Definition|null $parentDefinition
406
     */
407
    private function replaceDefaultArguments(array $defaultArguments, Definition $definition, Definition $parentDefinition = null): void
408
    {
409
        $arguments = $definition->getArguments();
410
        $parentArguments = $parentDefinition ? $parentDefinition->getArguments() : [];
411
412
        foreach ($defaultArguments as $index => $value) {
413
            $declaredInParent = $parentDefinition && array_key_exists($index, $parentArguments);
414
415
            if (0 == strlen($declaredInParent ? $parentArguments[$index] : $arguments[$index])) {
416
                $arguments[$declaredInParent ? sprintf('index_%s', $index) : $index] = $value;
417
            }
418
        }
419
420
        $definition->setArguments($arguments);
421
    }
422
}
423