vendor/sonata-project/admin-bundle/src/DependencyInjection/Compiler/ExtensionCompilerPass.php line 142

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 Sonata\AdminBundle\DependencyInjection\Admin\TaggedAdminInterface;
  13. use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
  14. use Symfony\Component\DependencyInjection\ContainerBuilder;
  15. use Symfony\Component\DependencyInjection\Definition;
  16. use Symfony\Component\DependencyInjection\Reference;
  17. /**
  18.  * @final since sonata-project/admin-bundle 3.52
  19.  *
  20.  * @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
  21.  */
  22. class ExtensionCompilerPass implements CompilerPassInterface
  23. {
  24.     public function process(ContainerBuilder $container)
  25.     {
  26.         $universalExtensions = [];
  27.         $targets = [];
  28.         foreach ($container->findTaggedServiceIds('sonata.admin.extension') as $id => $tags) {
  29.             foreach ($tags as $attributes) {
  30.                 $target false;
  31.                 if (isset($attributes['target'])) {
  32.                     $target $attributes['target'];
  33.                 }
  34.                 if (isset($attributes['global']) && $attributes['global']) {
  35.                     $universalExtensions[$id] = $attributes;
  36.                 }
  37.                 if (!$target || !$container->hasDefinition($target)) {
  38.                     continue;
  39.                 }
  40.                 $this->addExtension($targets$target$id$attributes);
  41.             }
  42.         }
  43.         $extensionConfig $container->getParameter('sonata.admin.extension.map');
  44.         $extensionMap $this->flattenExtensionConfiguration($extensionConfig);
  45.         foreach ($container->findTaggedServiceIds(TaggedAdminInterface::ADMIN_TAG) as $id => $attributes) {
  46.             $admin $container->getDefinition($id);
  47.             if (!isset($targets[$id])) {
  48.                 $targets[$id] = new \SplPriorityQueue();
  49.             }
  50.             foreach ($universalExtensions as $extension => $extensionAttributes) {
  51.                 $this->addExtension($targets$id$extension$extensionAttributes);
  52.             }
  53.             $extensions $this->getExtensionsForAdmin($id$admin$container$extensionMap);
  54.             foreach ($extensions as $extension => $attributes) {
  55.                 if (!$container->has($extension)) {
  56.                     throw new \InvalidArgumentException(sprintf(
  57.                         'Unable to find extension service for id %s',
  58.                         $extension
  59.                     ));
  60.                 }
  61.                 $this->addExtension($targets$id$extension$attributes);
  62.             }
  63.         }
  64.         foreach ($targets as $target => $extensions) {
  65.             $extensions iterator_to_array($extensions);
  66.             krsort($extensions);
  67.             $admin $container->getDefinition($target);
  68.             foreach (array_values($extensions) as $extension) {
  69.                 $admin->addMethodCall('addExtension', [$extension]);
  70.             }
  71.         }
  72.     }
  73.     /**
  74.      * @param string                                                                           $id
  75.      * @param array<string, array<string, array<string, array<string, array<string, mixed>>>>> $extensionMap
  76.      *
  77.      * @return array
  78.      */
  79.     protected function getExtensionsForAdmin($idDefinition $adminContainerBuilder $container, array $extensionMap)
  80.     {
  81.         $extensions = [];
  82.         $excludes $extensionMap['excludes'];
  83.         unset($extensionMap['excludes']);
  84.         foreach ($extensionMap as $type => $subjects) {
  85.             foreach ($subjects as $subject => $extensionList) {
  86.                 if ('admins' === $type) {
  87.                     if ($id === $subject) {
  88.                         $extensions array_merge($extensions$extensionList);
  89.                     }
  90.                     continue;
  91.                 }
  92.                 $class $this->getManagedClass($admin$container);
  93.                 if (null === $class || !class_exists($class)) {
  94.                     continue;
  95.                 }
  96.                 if ($this->isSubtypeOf($type$subject$class)) {
  97.                     $extensions array_merge($extensions$extensionList);
  98.                 }
  99.             }
  100.         }
  101.         if (isset($excludes[$id])) {
  102.             $extensions array_diff_key($extensions$excludes[$id]);
  103.         }
  104.         return $extensions;
  105.     }
  106.     /**
  107.      * Resolves the class argument of the admin to an actual class (in case of %parameter%).
  108.      *
  109.      * @return string|null
  110.      *
  111.      * @phpstan-return class-string|null
  112.      */
  113.     protected function getManagedClass(Definition $adminContainerBuilder $container)
  114.     {
  115.         $argument $admin->getArgument(1);
  116.         $class $container->getParameterBag()->resolveValue($argument);
  117.         if (null === $class) {
  118.             // NEXT_MAJOR: Throw exception
  119. //            throw new \DomainException(sprintf('The admin "%s" does not have a valid manager.', $admin->getClass()));
  120.             @trigger_error(
  121.                 sprintf('The admin "%s" does not have a valid manager.'$admin->getClass()),
  122.                 \E_USER_DEPRECATED
  123.             );
  124.         }
  125.         if (!\is_string($class)) {
  126.             // NEXT_MAJOR: Throw exception
  127. //            throw new \TypeError(sprintf(
  128. //                'Argument "%s" for admin class "%s" must be of type string, %s given.',
  129. //                $argument,
  130. //                $admin->getClass(),
  131. //                \is_object($class) ? \get_class($class) : \gettype($class)
  132. //            ));
  133.             @trigger_error(
  134.                 sprintf(
  135.                     'Argument "%s" for admin class "%s" must be of type string, %s given.',
  136.                     $argument,
  137.                     $admin->getClass(),
  138.                     \is_object($class) ? \get_class($class) : \gettype($class)
  139.                 ),
  140.                 \E_USER_DEPRECATED
  141.             );
  142.         }
  143.         return $class;
  144.     }
  145.     /**
  146.      * @param array<string, array<string, array<string, string>|bool>> $config
  147.      *
  148.      * @return array<string, array<string, array<string, array<string, array<string, string>>>>> an array with the following structure.
  149.      *
  150.      * [
  151.      *     'global'     => ['<admin_id>'  => ['<extension_id>' => ['priority' => <int>]]],
  152.      *     'excludes'   => ['<admin_id>'  => ['<extension_id>' => ['priority' => <int>]]],
  153.      *     'admins'     => ['<admin_id>'  => ['<extension_id>' => ['priority' => <int>]]],
  154.      *     'implements' => ['<interface>' => ['<extension_id>' => ['priority' => <int>]]],
  155.      *     'extends'    => ['<class>'     => ['<extension_id>' => ['priority' => <int>]]],
  156.      *     'instanceof' => ['<class>'     => ['<extension_id>' => ['priority' => <int>]]],
  157.      *     'uses'       => ['<trait>'     => ['<extension_id>' => ['priority' => <int>]]],
  158.      * ]
  159.      */
  160.     protected function flattenExtensionConfiguration(array $config)
  161.     {
  162.         $extensionMap = [
  163.             'global' => [],
  164.             'excludes' => [],
  165.             'admins' => [],
  166.             'implements' => [],
  167.             'extends' => [],
  168.             'instanceof' => [],
  169.             'uses' => [],
  170.         ];
  171.         foreach ($config as $extension => $options) {
  172.             if (true === $options['global']) {
  173.                 $options['global'] = [$extension];
  174.             } else {
  175.                 $options['global'] = [];
  176.             }
  177.             $optionsMap array_intersect_key($options$extensionMap);
  178.             foreach ($optionsMap as $key => $value) {
  179.                 foreach ($value as $source) {
  180.                     if (!isset($extensionMap[$key][$source])) {
  181.                         $extensionMap[$key][$source] = [];
  182.                     }
  183.                     $extensionMap[$key][$source][$extension]['priority'] = $options['priority'];
  184.                 }
  185.             }
  186.         }
  187.         return $extensionMap;
  188.     }
  189.     /**
  190.      * @return bool
  191.      *
  192.      * @phpstan-param class-string $traitName
  193.      */
  194.     protected function hasTrait(\ReflectionClass $class$traitName)
  195.     {
  196.         if (\in_array($traitName$class->getTraitNames(), true)) {
  197.             return true;
  198.         }
  199.         if (!$parentClass $class->getParentClass()) {
  200.             return false;
  201.         }
  202.         return $this->hasTrait($parentClass$traitName);
  203.     }
  204.     /**
  205.      * @phpstan-param class-string $class
  206.      * @phpstan-param class-string $subject
  207.      */
  208.     private function isSubtypeOf(string $typestring $subjectstring $class): bool
  209.     {
  210.         $classReflection = new \ReflectionClass($class);
  211.         switch ($type) {
  212.             case 'global':
  213.                 return true;
  214.             case 'instanceof':
  215.                 $subjectReflection = new \ReflectionClass($subject);
  216.                 return $classReflection->isSubclassOf($subject) || $subjectReflection->getName() === $classReflection->getName();
  217.             case 'implements':
  218.                 return $classReflection->implementsInterface($subject);
  219.             case 'extends':
  220.                 return $classReflection->isSubclassOf($subject);
  221.             case 'uses':
  222.                 return $this->hasTrait($classReflection$subject);
  223.         }
  224.         return false;
  225.     }
  226.     /**
  227.      * Add extension configuration to the targets array.
  228.      */
  229.     private function addExtension(
  230.         array &$targets,
  231.         string $target,
  232.         string $extension,
  233.         array $attributes
  234.     ): void {
  235.         if (!isset($targets[$target])) {
  236.             $targets[$target] = new \SplPriorityQueue();
  237.         }
  238.         $priority $attributes['priority'] ?? 0;
  239.         $targets[$target]->insert(new Reference($extension), $priority);
  240.     }
  241. }