Completed
Push — 3.x ( 10863f...4993ae )
by Grégoire
03:06
created

generateSetterMethodName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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