AddDependencyCallsCompilerPass   F
last analyzed

Complexity

Total Complexity 75

Size/Duplication

Total Lines 416
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 0
Metric Value
dl 0
loc 416
c 0
b 0
f 0
wmc 75
lcom 1
cbo 8
rs 2.4

6 Methods

Rating   Name   Duplication   Size   Complexity  
B fixTemplates() 0 56 9
B replaceDefaultArguments() 0 19 8
A generateSetterMethodName() 0 4 1
F process() 0 185 36
A applyConfigurationFromAttribute() 0 28 4
F applyDefaults() 0 103 17

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\Inflector\InflectorFactory;
17
use Sonata\AdminBundle\Controller\CRUDController;
18
use Sonata\AdminBundle\Datagrid\Pager;
19
use Sonata\AdminBundle\Templating\TemplateRegistry;
20
use Symfony\Component\DependencyInjection\ChildDefinition;
21
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
22
use Symfony\Component\DependencyInjection\ContainerBuilder;
23
use Symfony\Component\DependencyInjection\Definition;
24
use Symfony\Component\DependencyInjection\Reference;
25
26
/**
27
 * Add all dependencies to the Admin class, this avoid to write too many lines
28
 * in the configuration files.
29
 *
30
 * @author Thomas Rabaix <[email protected]>
31
 */
32
final class AddDependencyCallsCompilerPass implements CompilerPassInterface
33
{
34
    public function process(ContainerBuilder $container): void
35
    {
36
        // check if translator service exist
37
        if (!$container->has('translator')) {
38
            throw new \RuntimeException('The "translator" service is not yet enabled.
39
                It\'s required by SonataAdmin to display all labels properly.
40
                To learn how to enable the translator service please visit:
41
                http://symfony.com/doc/current/translation.html#configuration
42
             ');
43
        }
44
45
        $parameterBag = $container->getParameterBag();
46
        $groupDefaults = $admins = $classes = [];
47
48
        $pool = $container->getDefinition('sonata.admin.pool');
49
50
        $defaultValues = [
51
            'group' => $container->getParameter('sonata.admin.configuration.default_group'),
52
            'label_catalogue' => $container->getParameter('sonata.admin.configuration.default_label_catalogue'),
53
            'icon' => $container->getParameter('sonata.admin.configuration.default_icon'),
54
        ];
55
56
        foreach ($container->findTaggedServiceIds('sonata.admin') as $id => $tags) {
57
            foreach ($tags as $attributes) {
58
                $definition = $container->getDefinition($id);
59
                $parentDefinition = null;
60
61
                // Temporary fix until we can support service locators
62
                $definition->setPublic(true);
63
64
                if ($definition instanceof ChildDefinition) {
65
                    $parentDefinition = $container->getDefinition($definition->getParent());
66
                }
67
68
                $this->replaceDefaultArguments([
69
                    0 => $id,
70
                    2 => CRUDController::class,
71
                ], $definition, $parentDefinition);
72
                $this->applyConfigurationFromAttribute($definition, $attributes);
73
                $this->applyDefaults($container, $id, $attributes);
74
75
                $arguments = $parentDefinition ?
76
                    array_merge($parentDefinition->getArguments(), $definition->getArguments()) :
77
                    $definition->getArguments();
78
79
                $admins[] = $id;
80
81
                if (!isset($classes[$arguments[1]])) {
82
                    $classes[$arguments[1]] = [];
83
                }
84
85
                $classes[$arguments[1]][] = $id;
86
87
                $showInDashboard = (bool) (isset($attributes['show_in_dashboard']) ? $parameterBag->resolveValue($attributes['show_in_dashboard']) : true);
88
                if (!$showInDashboard) {
89
                    continue;
90
                }
91
92
                $resolvedGroupName = isset($attributes['group']) ?
93
                    $parameterBag->resolveValue($attributes['group']) :
94
                    $defaultValues['group'];
95
                $labelCatalogue = $attributes['label_catalogue'] ?? $defaultValues['label_catalogue'];
96
                $icon = $attributes['icon'] ?? $defaultValues['icon'];
97
                $onTop = $attributes['on_top'] ?? false;
98
                $keepOpen = $attributes['keep_open'] ?? false;
99
100
                if (!isset($groupDefaults[$resolvedGroupName])) {
101
                    $groupDefaults[$resolvedGroupName] = [
102
                        'label' => $resolvedGroupName,
103
                        'label_catalogue' => $labelCatalogue,
104
                        'icon' => $icon,
105
                        'roles' => [],
106
                        'on_top' => false,
107
                        'keep_open' => false,
108
                    ];
109
                }
110
111
                $groupDefaults[$resolvedGroupName]['items'][] = [
112
                    'admin' => $id,
113
                    'label' => !empty($attributes['label']) ? $attributes['label'] : '',
114
                    'route' => '',
115
                    'route_params' => [],
116
                    'route_absolute' => false,
117
                ];
118
119
                if (isset($groupDefaults[$resolvedGroupName]['on_top']) && $groupDefaults[$resolvedGroupName]['on_top']
120
                    || $onTop && (\count($groupDefaults[$resolvedGroupName]['items']) > 1)) {
121
                    throw new \RuntimeException('You can\'t use "on_top" option with multiple same name groups.');
122
                }
123
                $groupDefaults[$resolvedGroupName]['on_top'] = $onTop;
124
125
                $groupDefaults[$resolvedGroupName]['keep_open'] = $keepOpen;
126
            }
127
        }
128
129
        $dashboardGroupsSettings = $container->getParameter('sonata.admin.configuration.dashboard_groups');
130
        if (!empty($dashboardGroupsSettings)) {
131
            $groups = $dashboardGroupsSettings;
132
133
            foreach ($dashboardGroupsSettings as $groupName => $group) {
134
                $resolvedGroupName = $parameterBag->resolveValue($groupName);
135
                if (!isset($groupDefaults[$resolvedGroupName])) {
136
                    $groupDefaults[$resolvedGroupName] = [
137
                        'items' => [],
138
                        'label' => $resolvedGroupName,
139
                        'label_catalogue' => $defaultValues['label_catalogue'],
140
                        'icon' => $defaultValues['icon'],
141
                        'roles' => [],
142
                        'on_top' => false,
143
                        'keep_open' => false,
144
                    ];
145
                }
146
147
                if (empty($group['items'])) {
148
                    $groups[$resolvedGroupName]['items'] = $groupDefaults[$resolvedGroupName]['items'];
149
                }
150
151
                if (empty($group['label'])) {
152
                    $groups[$resolvedGroupName]['label'] = $groupDefaults[$resolvedGroupName]['label'];
153
                }
154
155
                if (empty($group['label_catalogue'])) {
156
                    $groups[$resolvedGroupName]['label_catalogue'] = $groupDefaults[$resolvedGroupName]['label_catalogue'];
157
                }
158
159
                if (empty($group['icon'])) {
160
                    $groups[$resolvedGroupName]['icon'] = $groupDefaults[$resolvedGroupName]['icon'];
161
                }
162
163
                if (!empty($group['item_adds'])) {
164
                    $groups[$resolvedGroupName]['items'] = array_merge($groups[$resolvedGroupName]['items'], $group['item_adds']);
165
                }
166
167
                if (empty($group['roles'])) {
168
                    $groups[$resolvedGroupName]['roles'] = $groupDefaults[$resolvedGroupName]['roles'];
169
                }
170
171
                if (isset($groups[$resolvedGroupName]['on_top']) && !empty($group['on_top']) && $group['on_top']
172
                    && (\count($groups[$resolvedGroupName]['items']) > 1)) {
173
                    throw new \RuntimeException('You can\'t use "on_top" option with multiple same name groups.');
174
                }
175
                if (empty($group['on_top'])) {
176
                    $groups[$resolvedGroupName]['on_top'] = $groupDefaults[$resolvedGroupName]['on_top'];
177
                }
178
179
                if (empty($group['keep_open'])) {
180
                    $groups[$resolvedGroupName]['keep_open'] = $groupDefaults[$resolvedGroupName]['keep_open'];
181
                }
182
            }
183
        } elseif ($container->getParameter('sonata.admin.configuration.sort_admins')) {
184
            $groups = $groupDefaults;
185
186
            $elementSort = static function (&$element): void {
187
                usort(
188
                    $element['items'],
189
                    static function ($a, $b) {
190
                        $a = !empty($a['label']) ? $a['label'] : $a['admin'];
191
                        $b = !empty($b['label']) ? $b['label'] : $b['admin'];
192
193
                        if ($a === $b) {
194
                            return 0;
195
                        }
196
197
                        return $a < $b ? -1 : 1;
198
                    }
199
                );
200
            };
201
202
            /*
203
             * 1) sort the groups by their index
204
             * 2) sort the elements within each group by label/admin
205
             */
206
            ksort($groups);
207
            array_walk($groups, $elementSort);
208
        } else {
209
            $groups = $groupDefaults;
210
        }
211
212
        $pool->addMethodCall('setAdminServiceIds', [$admins]);
213
        $pool->addMethodCall('setAdminGroups', [$groups]);
214
        $pool->addMethodCall('setAdminClasses', [$classes]);
215
216
        $routeLoader = $container->getDefinition('sonata.admin.route_loader');
217
        $routeLoader->replaceArgument(1, $admins);
218
    }
219
220
    /**
221
     * This method read the attribute keys and configure admin class to use the related dependency.
222
     */
223
    public function applyConfigurationFromAttribute(Definition $definition, array $attributes): void
224
    {
225
        $keys = [
226
            'model_manager',
227
            'form_contractor',
228
            'show_builder',
229
            'list_builder',
230
            'datagrid_builder',
231
            'translator',
232
            'configuration_pool',
233
            'router',
234
            'validator',
235
            'security_handler',
236
            'menu_factory',
237
            'route_builder',
238
            'label_translator_strategy',
239
        ];
240
241
        foreach ($keys as $key) {
242
            $method = $this->generateSetterMethodName($key);
243
244
            if (!isset($attributes[$key]) || $definition->hasMethodCall($method)) {
245
                continue;
246
            }
247
248
            $definition->addMethodCall($method, [new Reference($attributes[$key])]);
249
        }
250
    }
251
252
    /**
253
     * Apply the default values required by the AdminInterface to the Admin service definition.
254
     *
255
     * @param string $serviceId
256
     *
257
     * @return Definition
258
     */
259
    public function applyDefaults(ContainerBuilder $container, $serviceId, array $attributes = [])
260
    {
261
        $definition = $container->getDefinition($serviceId);
262
        $settings = $container->getParameter('sonata.admin.configuration.admin_services');
263
264
        $definition->setShared(false);
265
266
        $managerType = $attributes['manager_type'];
267
268
        $overwriteAdminConfiguration = $settings[$serviceId] ?? [];
269
270
        $defaultAddServices = [
271
            'model_manager' => sprintf('sonata.admin.manager.%s', $managerType),
272
            'form_contractor' => sprintf('sonata.admin.builder.%s_form', $managerType),
273
            'show_builder' => sprintf('sonata.admin.builder.%s_show', $managerType),
274
            'list_builder' => sprintf('sonata.admin.builder.%s_list', $managerType),
275
            'datagrid_builder' => sprintf('sonata.admin.builder.%s_datagrid', $managerType),
276
            'translator' => 'translator',
277
            'configuration_pool' => 'sonata.admin.pool',
278
            'route_generator' => 'sonata.admin.route.default_generator',
279
            'validator' => 'validator',
280
            'security_handler' => 'sonata.admin.security.handler',
281
            'menu_factory' => 'knp_menu.factory',
282
            'route_builder' => 'sonata.admin.route.path_info'.
283
                (('doctrine_phpcr' === $managerType) ? '_slashes' : ''),
284
            'label_translator_strategy' => 'sonata.admin.label.strategy.native',
285
        ];
286
287
        $definition->addMethodCall('setManagerType', [$managerType]);
288
289
        foreach ($defaultAddServices as $attr => $addServiceId) {
290
            $method = $this->generateSetterMethodName($attr);
291
292
            if (isset($overwriteAdminConfiguration[$attr]) || !$definition->hasMethodCall($method)) {
293
                $args = [new Reference($overwriteAdminConfiguration[$attr] ?? $addServiceId)];
294
                if ('translator' === $attr) {
295
                    $args[] = false;
296
                }
297
298
                $definition->addMethodCall($method, $args);
299
            }
300
        }
301
302
        if (isset($overwriteAdminConfiguration['pager_type'])) {
303
            $pagerType = $overwriteAdminConfiguration['pager_type'];
304
        } elseif (isset($attributes['pager_type'])) {
305
            $pagerType = $attributes['pager_type'];
306
        } else {
307
            $pagerType = Pager::TYPE_DEFAULT;
308
        }
309
310
        $definition->addMethodCall('setPagerType', [$pagerType]);
311
312
        if (isset($overwriteAdminConfiguration['label'])) {
313
            $label = $overwriteAdminConfiguration['label'];
314
        } elseif (isset($attributes['label'])) {
315
            $label = $attributes['label'];
316
        } else {
317
            $label = '-';
318
        }
319
320
        $definition->addMethodCall('setLabel', [$label]);
321
322
        $persistFilters = $container->getParameter('sonata.admin.configuration.filters.persist');
323
        // override default configuration with admin config if set
324
        if (isset($attributes['persist_filters'])) {
325
            $persistFilters = $attributes['persist_filters'];
326
        }
327
        $filtersPersister = $container->getParameter('sonata.admin.configuration.filters.persister');
328
        // override default configuration with admin config if set
329
        if (isset($attributes['filter_persister'])) {
330
            $filtersPersister = $attributes['filter_persister'];
331
        }
332
        // configure filters persistence, if configured to
333
        if ($persistFilters) {
334
            $definition->addMethodCall('setFilterPersister', [new Reference($filtersPersister)]);
335
        }
336
337
        if (isset($overwriteAdminConfiguration['show_mosaic_button'])) {
338
            $showMosaicButton = $overwriteAdminConfiguration['show_mosaic_button'];
339
        } elseif (isset($attributes['show_mosaic_button'])) {
340
            $showMosaicButton = $attributes['show_mosaic_button'];
341
        } else {
342
            $showMosaicButton = $container->getParameter('sonata.admin.configuration.show.mosaic.button');
343
        }
344
345
        $definition->addMethodCall('showMosaicButton', [$showMosaicButton]);
346
347
        $this->fixTemplates(
348
            $serviceId,
349
            $container,
350
            $definition,
351
            $overwriteAdminConfiguration['templates'] ?? ['view' => []]
352
        );
353
354
        if ($container->hasParameter('sonata.admin.configuration.security.information') && !$definition->hasMethodCall('setSecurityInformation')) {
355
            $definition->addMethodCall('setSecurityInformation', ['%sonata.admin.configuration.security.information%']);
356
        }
357
358
        $definition->addMethodCall('initialize');
359
360
        return $definition;
361
    }
362
363
    public function fixTemplates(
364
        string $serviceId,
365
        ContainerBuilder $container,
366
        Definition $definition,
367
        array $overwrittenTemplates = []
368
    ): void {
369
        $definedTemplates = $container->getParameter('sonata.admin.configuration.templates');
370
371
        $methods = [];
372
        $pos = 0;
373
        foreach ($definition->getMethodCalls() as $method) {
374
            if ('setTemplates' === $method[0]) {
375
                $definedTemplates = array_merge($definedTemplates, $method[1][0]);
376
377
                continue;
378
            }
379
380
            if ('setTemplate' === $method[0]) {
381
                $definedTemplates[$method[1][0]] = $method[1][1];
382
383
                continue;
384
            }
385
386
            // set template for simple pager if it is not already overwritten
387
            if ('setPagerType' === $method[0]
388
                && Pager::TYPE_SIMPLE === $method[1][0]
389
                && (
390
                    !isset($definedTemplates['pager_results'])
391
                    || '@SonataAdmin/Pager/results.html.twig' === $definedTemplates['pager_results']
392
                )
393
            ) {
394
                $definedTemplates['pager_results'] = '@SonataAdmin/Pager/simple_pager_results.html.twig';
395
            }
396
397
            $methods[$pos] = $method;
398
            ++$pos;
399
        }
400
401
        $definition->setMethodCalls($methods);
402
403
        $definedTemplates = $overwrittenTemplates['view'] + $definedTemplates;
404
405
        $templateRegistryId = sprintf('%s.template_registry', $serviceId);
406
        $templateRegistryDefinition = $container
407
            ->register($templateRegistryId, TemplateRegistry::class)
408
            ->addTag('sonata.admin.template_registry')
409
            ->setPublic(true); // Temporary fix until we can support service locators
410
411
        if ($container->getParameter('sonata.admin.configuration.templates') !== $definedTemplates) {
412
            $templateRegistryDefinition->addArgument($definedTemplates);
413
        } else {
414
            $templateRegistryDefinition->addArgument('%sonata.admin.configuration.templates%');
415
        }
416
417
        $definition->addMethodCall('setTemplateRegistry', [new Reference($templateRegistryId)]);
418
    }
419
420
    /**
421
     * Replace the empty arguments required by the Admin service definition.
422
     */
423
    private function replaceDefaultArguments(
424
        array $defaultArguments,
425
        Definition $definition,
426
        ?Definition $parentDefinition = null
427
    ): void {
428
        $arguments = $definition->getArguments();
429
        $parentArguments = $parentDefinition ? $parentDefinition->getArguments() : [];
430
431
        foreach ($defaultArguments as $index => $value) {
432
            $declaredInParent = $parentDefinition && \array_key_exists($index, $parentArguments);
433
            $argumentValue = $declaredInParent ? $parentArguments[$index] : $arguments[$index];
434
435
            if (null === $argumentValue || 0 === \strlen($argumentValue)) {
436
                $arguments[$declaredInParent ? sprintf('index_%s', $index) : $index] = $value;
437
            }
438
        }
439
440
        $definition->setArguments($arguments);
441
    }
442
443
    private function generateSetterMethodName(string $key): string
444
    {
445
        return 'set'.InflectorFactory::create()->build()->classify($key);
446
    }
447
}
448