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

AddDependencyCallsCompilerPass::fixTemplates()   B

Complexity

Conditions 9
Paths 10

Size

Total Lines 56
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 56
rs 7.1584
c 0
b 0
f 0
cc 9
eloc 36
nc 10
nop 4

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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