vendor/sonata-project/admin-bundle/src/DependencyInjection/Compiler/AddDependencyCallsCompilerPass.php line 357

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4.  * This file is part of the Sonata Project package.
  5.  *
  6.  * (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
  7.  *
  8.  * For the full copyright and license information, please view the LICENSE
  9.  * file that was distributed with this source code.
  10.  */
  11. namespace Sonata\AdminBundle\DependencyInjection\Compiler;
  12. use Doctrine\Inflector\InflectorFactory;
  13. use Sonata\AdminBundle\Admin\Pool;
  14. use Sonata\AdminBundle\Datagrid\Pager;
  15. use Sonata\AdminBundle\DependencyInjection\Admin\TaggedAdminInterface;
  16. use Sonata\AdminBundle\Templating\MutableTemplateRegistry;
  17. use Symfony\Component\DependencyInjection\ChildDefinition;
  18. use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
  19. use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
  20. use Symfony\Component\DependencyInjection\ContainerBuilder;
  21. use Symfony\Component\DependencyInjection\Definition;
  22. use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
  23. use Symfony\Component\DependencyInjection\Reference;
  24. /**
  25.  * Add all dependencies to the Admin class, this avoids writing too many lines
  26.  * in the configuration files.
  27.  *
  28.  * @internal
  29.  *
  30.  * @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
  31.  */
  32. final class AddDependencyCallsCompilerPass implements CompilerPassInterface
  33. {
  34.     public function process(ContainerBuilder $container): void
  35.     {
  36.         if (!$container->has('sonata.admin.pool')) {
  37.             return;
  38.         }
  39.         // check if translator service exist
  40.         if (!$container->has('translator')) {
  41.             throw new \RuntimeException('The "translator" service is not yet enabled.
  42.                 It\'s required by SonataAdmin to display all labels properly.
  43.                 To learn how to enable the translator service please visit:
  44.                 http://symfony.com/doc/current/translation.html#configuration
  45.              ');
  46.         }
  47.         $parameterBag $container->getParameterBag();
  48.         $groupDefaults $admins $adminServices $classes = [];
  49.         $pool $container->getDefinition('sonata.admin.pool');
  50.         $defaultController $container->getParameter('sonata.admin.configuration.default_controller');
  51.         \assert(\is_string($defaultController));
  52.         $defaultGroup $container->getParameter('sonata.admin.configuration.default_group');
  53.         \assert(\is_string($defaultGroup));
  54.         // NEXT_MAJOR: Remove this variable.
  55.         $defaultLabelCatalogue $container->getParameter('sonata.admin.configuration.default_label_catalogue');
  56.         \assert(\is_string($defaultLabelCatalogue));
  57.         // NEXT_MAJOR: Remove the fallback.
  58.         $defaultTranslationDomain $container->getParameter('sonata.admin.configuration.default_translation_domain') ?? $defaultLabelCatalogue;
  59.         \assert(\is_string($defaultTranslationDomain));
  60.         $defaultIcon $container->getParameter('sonata.admin.configuration.default_icon');
  61.         \assert(\is_string($defaultIcon));
  62.         $defaultValues = [
  63.             'group' => $defaultGroup,
  64.             'translation_domain' => $defaultTranslationDomain,
  65.             'label_catalogue' => $defaultLabelCatalogue// NEXT_MAJOR: Remove this line.
  66.             'icon' => $defaultIcon,
  67.         ];
  68.         foreach ($container->findTaggedServiceIds(TaggedAdminInterface::ADMIN_TAG) as $id => $tags) {
  69.             $adminServices[$id] = new Reference($id);
  70.             foreach ($tags as $attributes) {
  71.                 $definition $container->getDefinition($id);
  72.                 $parentDefinition null;
  73.                 if ($definition instanceof ChildDefinition) {
  74.                     $parentDefinition $container->getDefinition($definition->getParent());
  75.                 }
  76.                 // NEXT_MAJOR: Remove the following code.
  77.                 if (!isset($attributes['model_class'])) {
  78.                     // Since the model_class attribute will be mandatory we're assuming that
  79.                     // - if it's used the new syntax is used, so we don't need to replace the arguments
  80.                     // - if it's not used, the old syntax is used, so we still need to
  81.                     $this->replaceDefaultArguments([
  82.                         => $id,
  83.                         => $defaultController,
  84.                     ], $definition$parentDefinition);
  85.                 }
  86.                 $definition->setMethodCalls(array_merge(
  87.                     $this->getDefaultMethodCalls($container$id$attributes),
  88.                     $definition->getMethodCalls()
  89.                 ));
  90.                 $this->fixTemplates($id$container$definition);
  91.                 $arguments null !== $parentDefinition ?
  92.                     array_merge($parentDefinition->getArguments(), $definition->getArguments()) :
  93.                     $definition->getArguments();
  94.                 $admins[] = $id;
  95.                 // NEXT_MAJOR: Remove the fallback to $arguments[1].
  96.                 $modelClass $attributes['model_class'] ?? $arguments[1];
  97.                 if (!isset($classes[$modelClass])) {
  98.                     $classes[$modelClass] = [];
  99.                 }
  100.                 $default = (bool) (isset($attributes['default']) ? $parameterBag->resolveValue($attributes['default']) : false);
  101.                 if ($default) {
  102.                     if (isset($classes[$modelClass][Pool::DEFAULT_ADMIN_KEY])) {
  103.                         throw new \RuntimeException(sprintf(
  104.                             'The class %s has two admins %s and %s with the "default" attribute set to true. Only one is allowed.',
  105.                             $modelClass,
  106.                             $classes[$modelClass][Pool::DEFAULT_ADMIN_KEY],
  107.                             $id
  108.                         ));
  109.                     }
  110.                     $classes[$modelClass][Pool::DEFAULT_ADMIN_KEY] = $id;
  111.                 } else {
  112.                     $classes[$modelClass][] = $id;
  113.                 }
  114.                 $showInDashboard = (bool) (isset($attributes['show_in_dashboard']) ? $parameterBag->resolveValue($attributes['show_in_dashboard']) : true);
  115.                 if (!$showInDashboard) {
  116.                     continue;
  117.                 }
  118.                 $resolvedGroupName = isset($attributes['group']) ?
  119.                     $parameterBag->resolveValue($attributes['group']) :
  120.                     $defaultValues['group'];
  121.                 \assert(\is_string($resolvedGroupName));
  122.                 // NEXT_MAJOR: Remove this deprecation and the $labelCatalogue variable.
  123.                 if (isset($attributes['label_catalogue'])) {
  124.                     @trigger_error(
  125.                         'The "label_catalogue" attribute is deprecated'
  126.                         .' since sonata-project/admin-bundle 4.9 and will throw an error in 5.0.',
  127.                         \E_USER_DEPRECATED
  128.                     );
  129.                 }
  130.                 $labelCatalogue $attributes['label_catalogue'] ?? $defaultValues['label_catalogue'];
  131.                 // NEXT_MAJOR: Remove the `label_catalogue` fallback.
  132.                 $groupTranslationDomain $attributes['translation_domain'] ?? $attributes['label_catalogue'] ?? $defaultValues['translation_domain'];
  133.                 $icon $attributes['icon'] ?? $defaultValues['icon'];
  134.                 $onTop $attributes['on_top'] ?? false;
  135.                 $keepOpen $attributes['keep_open'] ?? false;
  136.                 if (!isset($groupDefaults[$resolvedGroupName])) {
  137.                     $groupDefaults[$resolvedGroupName] = [
  138.                         'label' => $resolvedGroupName,
  139.                         'translation_domain' => $groupTranslationDomain,
  140.                         'label_catalogue' => $labelCatalogue// NEXT_MAJOR: Remove this line.
  141.                         'icon' => $icon,
  142.                         'items' => [],
  143.                         'roles' => [],
  144.                         'on_top' => false,
  145.                         'keep_open' => false,
  146.                     ];
  147.                 }
  148.                 $groupDefaults[$resolvedGroupName]['items'][] = [
  149.                     'admin' => $id,
  150.                     'label' => $attributes['label'] ?? ''// NEXT_MAJOR: Remove this line.
  151.                     'route' => ''// NEXT_MAJOR: Remove this line.
  152.                     'route_params' => [],
  153.                     'route_absolute' => false,
  154.                 ];
  155.                 if (isset($groupDefaults[$resolvedGroupName]['on_top']) && $groupDefaults[$resolvedGroupName]['on_top']
  156.                     || $onTop && (\count($groupDefaults[$resolvedGroupName]['items']) > 1)) {
  157.                     throw new \RuntimeException('You can\'t use "on_top" option with multiple same name groups.');
  158.                 }
  159.                 $groupDefaults[$resolvedGroupName]['on_top'] = $onTop;
  160.                 $groupDefaults[$resolvedGroupName]['keep_open'] = $keepOpen;
  161.             }
  162.         }
  163.         $dashboardGroupsSettings $container->getParameter('sonata.admin.configuration.dashboard_groups');
  164.         \assert(\is_array($dashboardGroupsSettings));
  165.         $sortAdmins $container->getParameter('sonata.admin.configuration.sort_admins');
  166.         \assert(\is_bool($sortAdmins));
  167.         if ([] !== $dashboardGroupsSettings) {
  168.             $groups $dashboardGroupsSettings;
  169.             foreach ($dashboardGroupsSettings as $groupName => $group) {
  170.                 $resolvedGroupName $parameterBag->resolveValue($groupName);
  171.                 \assert(\is_string($resolvedGroupName));
  172.                 if (!isset($groupDefaults[$resolvedGroupName])) {
  173.                     $groupDefaults[$resolvedGroupName] = [
  174.                         'items' => [],
  175.                         'label' => $resolvedGroupName,
  176.                         'translation_domain' => $defaultValues['translation_domain'],
  177.                         'label_catalogue' => $defaultValues['label_catalogue'], // NEXT_MAJOR: Remove this line.
  178.                         'icon' => $defaultValues['icon'],
  179.                         'roles' => [],
  180.                         'on_top' => false,
  181.                         'keep_open' => false,
  182.                     ];
  183.                 }
  184.                 if (!isset($group['items']) || [] === $group['items']) {
  185.                     $groups[$resolvedGroupName]['items'] = $groupDefaults[$resolvedGroupName]['items'];
  186.                 }
  187.                 if (!isset($group['label']) || '' === $group['label']) {
  188.                     $groups[$resolvedGroupName]['label'] = $groupDefaults[$resolvedGroupName]['label'];
  189.                 }
  190.                 if (!isset($group['translation_domain']) || '' === $group['translation_domain']) {
  191.                     $groups[$resolvedGroupName]['translation_domain'] = $groupDefaults[$resolvedGroupName]['translation_domain'];
  192.                 }
  193.                 // NEXT_MAJOR: Remove the whole if/else.
  194.                 if (!isset($group['label_catalogue']) || '' === $group['label_catalogue']) {
  195.                     $groups[$resolvedGroupName]['label_catalogue'] = $groupDefaults[$resolvedGroupName]['label_catalogue'];
  196.                 } elseif (!isset($group['translation_domain']) || '' === $group['translation_domain']) {
  197.                     // BC-layer if label_catalogue is provided.
  198.                     $groups[$resolvedGroupName]['translation_domain'] = $group['label_catalogue'];
  199.                 }
  200.                 if (!isset($group['icon']) || '' === $group['icon']) {
  201.                     $groups[$resolvedGroupName]['icon'] = $groupDefaults[$resolvedGroupName]['icon'];
  202.                 }
  203.                 if (!isset($group['roles']) || [] === $group['roles']) {
  204.                     $groups[$resolvedGroupName]['roles'] = $groupDefaults[$resolvedGroupName]['roles'];
  205.                 }
  206.                 if (
  207.                     isset($groups[$resolvedGroupName]['on_top'])
  208.                     && ($group['on_top'] ?? false)
  209.                     && \count($groups[$resolvedGroupName]['items']) > 1
  210.                 ) {
  211.                     throw new \RuntimeException('You can\'t use "on_top" option with multiple same name groups.');
  212.                 }
  213.                 if (!isset($group['on_top'])) {
  214.                     $groups[$resolvedGroupName]['on_top'] = $groupDefaults[$resolvedGroupName]['on_top'];
  215.                 }
  216.                 if (!isset($group['keep_open'])) {
  217.                     $groups[$resolvedGroupName]['keep_open'] = $groupDefaults[$resolvedGroupName]['keep_open'];
  218.                 }
  219.             }
  220.         } elseif ($sortAdmins) {
  221.             $groups $groupDefaults;
  222.             $elementSort = static function (array &$element): void {
  223.                 usort(
  224.                     $element['items'],
  225.                     static function (array $a, array $b): int {
  226.                         $labelA = isset($a['label']) && '' !== $a['label'] ? $a['label'] : $a['admin'];
  227.                         $labelB = isset($b['label']) && '' !== $b['label'] ? $b['label'] : $b['admin'];
  228.                         return $labelA <=> $labelB;
  229.                     }
  230.                 );
  231.             };
  232.             /*
  233.              * 1) sort the groups by their index
  234.              * 2) sort the elements within each group by label/admin
  235.              */
  236.             ksort($groups);
  237.             array_walk($groups$elementSort);
  238.         } else {
  239.             $groups $groupDefaults;
  240.         }
  241.         $pool->replaceArgument(0ServiceLocatorTagPass::register($container$adminServices));
  242.         $pool->replaceArgument(1$admins);
  243.         $pool->replaceArgument(2$groups);
  244.         $pool->replaceArgument(3$classes);
  245.     }
  246.     /**
  247.      * Apply the default values required by the AdminInterface to the Admin service definition.
  248.      *
  249.      * @param array<string, mixed> $attributes
  250.      *
  251.      * @return array<array{string, array<mixed>}>
  252.      */
  253.     private function getDefaultMethodCalls(ContainerBuilder $containerstring $serviceId, array $attributes = []): array
  254.     {
  255.         $definition $container->getDefinition($serviceId);
  256.         $methodCalls = [];
  257.         $definition->setShared(false);
  258.         $managerType $attributes['manager_type'] ?? null;
  259.         if (!\is_string($managerType)) {
  260.             throw new InvalidArgumentException(sprintf('Missing tag information "manager_type" on service "%s".'$serviceId));
  261.         }
  262.         $overwriteAdminConfiguration $container->getParameter('sonata.admin.configuration.default_admin_services');
  263.         \assert(\is_array($overwriteAdminConfiguration));
  264.         $defaultAddServices = [
  265.             'model_manager' => sprintf('sonata.admin.manager.%s'$managerType),
  266.             'data_source' => sprintf('sonata.admin.data_source.%s'$managerType),
  267.             'field_description_factory' => sprintf('sonata.admin.field_description_factory.%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.             'security_handler' => 'sonata.admin.security.handler',
  276.             'menu_factory' => 'knp_menu.factory',
  277.             'route_builder' => 'sonata.admin.route.path_info',
  278.             'label_translator_strategy' => 'sonata.admin.label.strategy.native',
  279.         ];
  280.         $methodCalls[] = ['setManagerType', [$managerType]];
  281.         foreach ($defaultAddServices as $attr => $addServiceId) {
  282.             $method $this->generateSetterMethodName($attr);
  283.             if ($definition->hasMethodCall($method)) {
  284.                 continue;
  285.             }
  286.             $args = [new Reference($attributes[$attr] ?? $overwriteAdminConfiguration[$attr] ?? $addServiceId)];
  287.             if ('translator' === $attr) {
  288.                 $args[] = false;
  289.             }
  290.             $methodCalls[] = [$method$args];
  291.         }
  292.         $defaultController $container->getParameter('sonata.admin.configuration.default_controller');
  293.         \assert(\is_string($defaultController));
  294.         $modelClass $attributes['model_class'] ?? null;
  295.         if (null === $modelClass) {
  296.             @trigger_error(
  297.                 'Not setting the "model_class" attribute is deprecated'
  298.                 .' since sonata-project/admin-bundle 4.8 and will throw an error in 5.0.',
  299.                 \E_USER_DEPRECATED
  300.             );
  301.         // NEXT_MAJOR: Uncomment the exception instead of the deprecation.
  302.             // throw new InvalidArgumentException(sprintf('Missing tag information "model_class" on service "%s".', $serviceId));
  303.         } else {
  304.             $methodCalls[] = ['setModelClass', [$modelClass]];
  305.             $controller $attributes['controller'] ?? $defaultController;
  306.             $methodCalls[] = ['setBaseControllerName', [$controller]];
  307.             $code $attributes['code'] ?? $serviceId;
  308.             $methodCalls[] = ['setCode', [$code]];
  309.         }
  310.         $pagerType $overwriteAdminConfiguration['pager_type'] ?? $attributes['pager_type'] ?? Pager::TYPE_DEFAULT;
  311.         $methodCalls[] = ['setPagerType', [$pagerType]];
  312.         $label $attributes['label'] ?? null;
  313.         $methodCalls[] = ['setLabel', [$label]];
  314.         // NEXT_MAJOR: Remove the fallback.
  315.         $defaultTranslationDomain $container->getParameter('sonata.admin.configuration.default_translation_domain') ?? 'messages';
  316.         \assert(\is_string($defaultTranslationDomain));
  317.         $translationDomain $attributes['translation_domain'] ?? $defaultTranslationDomain;
  318.         $methodCalls[] = ['setTranslationDomain', [$translationDomain]];
  319.         $persistFilters $attributes['persist_filters']
  320.             ?? $container->getParameter('sonata.admin.configuration.filters.persist');
  321.         \assert(\is_bool($persistFilters));
  322.         $filtersPersister $attributes['filter_persister']
  323.             ?? $container->getParameter('sonata.admin.configuration.filters.persister');
  324.         \assert(\is_string($filtersPersister));
  325.         // configure filters persistence, if configured to
  326.         if ($persistFilters) {
  327.             $methodCalls[] = ['setFilterPersister', [new Reference($filtersPersister)]];
  328.         }
  329.         $showMosaicButton $attributes['show_mosaic_button']
  330.             ?? $container->getParameter('sonata.admin.configuration.show.mosaic.button');
  331.         \assert(\is_bool($showMosaicButton));
  332.         $listModes TaggedAdminInterface::DEFAULT_LIST_MODES;
  333.         if (!$showMosaicButton) {
  334.             unset($listModes['mosaic']);
  335.         }
  336.         $methodCalls[] = ['setListModes', [$listModes]];
  337.         if ($container->hasParameter('sonata.admin.configuration.security.information') && !$definition->hasMethodCall('setSecurityInformation')) {
  338.             $methodCalls[] = ['setSecurityInformation', ['%sonata.admin.configuration.security.information%']];
  339.         }
  340.         $defaultTemplates $container->getParameter('sonata.admin.configuration.templates');
  341.         \assert(\is_array($defaultTemplates));
  342.         if (!$definition->hasMethodCall('setFormTheme')) {
  343.             $formTheme $defaultTemplates['form_theme'] ?? [];
  344.             $methodCalls[] = ['setFormTheme', [$formTheme]];
  345.         }
  346.         if (!$definition->hasMethodCall('setFilterTheme')) {
  347.             $filterTheme $defaultTemplates['filter_theme'] ?? [];
  348.             $methodCalls[] = ['setFilterTheme', [$filterTheme]];
  349.         }
  350.         return $methodCalls;
  351.     }
  352.     private function fixTemplates(
  353.         string $serviceId,
  354.         ContainerBuilder $container,
  355.         Definition $definition
  356.     ): void {
  357.         $definedTemplates $container->getParameter('sonata.admin.configuration.templates');
  358.         \assert(\is_array($definedTemplates));
  359.         $methods = [];
  360.         $pos 0;
  361.         foreach ($definition->getMethodCalls() as [$method$args]) {
  362.             if ('setTemplates' === $method) {
  363.                 $definedTemplates array_merge($definedTemplates$args[0]);
  364.                 continue;
  365.             }
  366.             if ('setTemplate' === $method) {
  367.                 $definedTemplates[$args[0]] = $args[1];
  368.                 continue;
  369.             }
  370.             // set template for simple pager if it is not already overwritten
  371.             if ('setPagerType' === $method
  372.                 && Pager::TYPE_SIMPLE === $args[0]
  373.                 && (
  374.                     !isset($definedTemplates['pager_results'])
  375.                     || '@SonataAdmin/Pager/results.html.twig' === $definedTemplates['pager_results']
  376.                 )
  377.             ) {
  378.                 $definedTemplates['pager_results'] = '@SonataAdmin/Pager/simple_pager_results.html.twig';
  379.             }
  380.             $methods[$pos] = [$method$args];
  381.             ++$pos;
  382.         }
  383.         $definition->setMethodCalls($methods);
  384.         $templateRegistryId sprintf('%s.template_registry'$serviceId);
  385.         $templateRegistryDefinition $container
  386.             ->register($templateRegistryIdMutableTemplateRegistry::class)
  387.             ->addTag('sonata.admin.template_registry')
  388.             ->setPublic(true); // Temporary fix until we can support service locators
  389.         if ($container->getParameter('sonata.admin.configuration.templates') !== $definedTemplates) {
  390.             $templateRegistryDefinition->addArgument($definedTemplates);
  391.         } else {
  392.             $templateRegistryDefinition->addArgument('%sonata.admin.configuration.templates%');
  393.         }
  394.         $definition->addMethodCall('setTemplateRegistry', [new Reference($templateRegistryId)]);
  395.     }
  396.     /**
  397.      * NEXT_MAJOR: Remove this method.
  398.      *
  399.      * Replace the empty arguments required by the Admin service definition.
  400.      *
  401.      * @param string[] $defaultArguments
  402.      */
  403.     private function replaceDefaultArguments(
  404.         array $defaultArguments,
  405.         Definition $definition,
  406.         ?Definition $parentDefinition null
  407.     ): void {
  408.         $arguments $definition->getArguments();
  409.         $parentArguments null !== $parentDefinition $parentDefinition->getArguments() : [];
  410.         foreach ($defaultArguments as $index => $value) {
  411.             $declaredInParent null !== $parentDefinition && \array_key_exists($index$parentArguments);
  412.             $argumentValue $declaredInParent $parentArguments[$index] : $arguments[$index] ?? null;
  413.             if (null === $argumentValue || '' === $argumentValue) {
  414.                 $arguments[$declaredInParent sprintf('index_%s'$index) : $index] = $value;
  415.             }
  416.         }
  417.         $definition->setArguments($arguments);
  418.     }
  419.     private function generateSetterMethodName(string $key): string
  420.     {
  421.         return 'set'.InflectorFactory::create()->build()->classify($key);
  422.     }
  423. }