vendor/sonata-project/admin-bundle/src/DependencyInjection/Compiler/ExtensionCompilerPass.php line 138
<?phpdeclare(strict_types=1);/** This file is part of the Sonata Project package.** (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>** For the full copyright and license information, please view the LICENSE* file that was distributed with this source code.*/namespace Sonata\AdminBundle\DependencyInjection\Compiler;use Sonata\AdminBundle\DependencyInjection\Admin\TaggedAdminInterface;use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;use Symfony\Component\DependencyInjection\ContainerBuilder;use Symfony\Component\DependencyInjection\Definition;use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;use Symfony\Component\DependencyInjection\Reference;/*** @internal** @phpstan-type ExtensionMap = array<string, array{* global: bool,* excludes: array<string, string>,* admins: array<string, string>,* implements: array<class-string, string>,* extends: array<class-string, string>,* instanceof: array<class-string, string>,* uses: array<class-string, string>,* admin_implements: array<class-string, string>,* admin_extends: array<class-string, string>,* admin_instanceof: array<class-string, string>,* admin_uses: array<class-string, string>,* priority: int,* }>* @phpstan-type FlattenExtensionMap = array{* global: array<string, array<string, array{priority: int}>>,* excludes: array<string, array<string, array{priority: int}>>,* admins: array<string, array<string, array{priority: int}>>,* implements: array<string, array<class-string, array{priority: int}>>,* extends: array<string, array<class-string, array{priority: int}>>,* instanceof: array<string, array<class-string, array{priority: int}>>,* uses: array<string, array<class-string, array{priority: int}>>,* admin_implements: array<string, array<class-string, array{priority: int}>>,* admin_extends: array<string, array<class-string, array{priority: int}>>,* admin_instanceof: array<string, array<class-string, array{priority: int}>>,* admin_uses: array<string, array<class-string, array{priority: int}>>,* }** @author Thomas Rabaix <thomas.rabaix@sonata-project.org>*/final class ExtensionCompilerPass implements CompilerPassInterface{public function process(ContainerBuilder $container): void{$universalExtensions = [];$targets = [];foreach ($container->findTaggedServiceIds('sonata.admin.extension') as $id => $tags) {$adminExtension = $container->getDefinition($id);// Trim possible parameter delimiters ("%") from the class name.$adminExtensionClass = trim($adminExtension->getClass() ?? '', '%');if (!class_exists($adminExtensionClass, false) && $container->hasParameter($adminExtensionClass)) {$adminExtensionClass = $container->getParameter($adminExtensionClass);\assert(\is_string($adminExtensionClass));}\assert(class_exists($adminExtensionClass));foreach ($tags as $attributes) {$target = false;if (isset($attributes['target'])) {$target = $attributes['target'];unset($attributes['target']);}if (isset($attributes['global'])) {if ($attributes['global']) {$attributes['global'] = $adminExtensionClass;} else {unset($attributes['global']);}}$universalExtensions[$id][] = $attributes;if (!$target || !$container->hasDefinition($target)) {continue;}$this->addExtension($targets, $target, $id, $attributes);}}/*** @phpstan-var ExtensionMap $extensionConfig*/$extensionConfig = $container->getParameter('sonata.admin.extension.map');$extensionMap = $this->flattenExtensionConfiguration($extensionConfig);foreach ($container->findTaggedServiceIds(TaggedAdminInterface::ADMIN_TAG) as $id => $tags) {$admin = $container->getDefinition($id);// Trim possible parameter delimiters ("%") from the class name.$adminClass = trim($admin->getClass() ?? '', '%');if (!class_exists($adminClass, false) && $container->hasParameter($adminClass)) {$adminClass = $container->getParameter($adminClass);\assert(\is_string($adminClass));}\assert(class_exists($adminClass));if (!isset($targets[$id])) {$targets[$id] = new \SplPriorityQueue();}// NEXT_MAJOR: Remove this line.$defaultModelClass = $admin->getArguments()[1] ?? null;foreach ($tags as $attributes) {// NEXT_MAJOR: Remove the fallback to $defaultModelClass and use null instead.$modelClass = $attributes['model_class'] ?? $defaultModelClass;if (null === $modelClass) {throw new InvalidArgumentException(sprintf('Missing tag attribute "model_class" on service "%s".', $id));}$class = $container->getParameterBag()->resolveValue($modelClass);if (!\is_string($class)) {throw new \TypeError(sprintf('Tag attribute "model_class" for service "%s" must be of type string, %s given.',$id,get_debug_type($class)));}if (!class_exists($class)) {continue;}foreach ($universalExtensions as $extension => $extensionsAttributes) {foreach ($extensionsAttributes as $extensionAttributes) {if (isset($extensionAttributes['excludes'][$id])) {continue;}foreach ($extensionAttributes as $type => $subject) {if ($this->shouldApplyExtension($type, $subject, $class, $adminClass)) {$this->addExtension($targets, $id, $extension, $extensionAttributes);break;}}}}}$extensions = $this->getExtensionsForAdmin($id, $tags, $admin, $container, $extensionMap);foreach ($extensions as $extension => $attributes) {if (!$container->has($extension)) {throw new \InvalidArgumentException(sprintf('Unable to find extension service for id %s',$extension));}$this->addExtension($targets, $id, $extension, $attributes);}}foreach ($targets as $target => $extensions) {$extensions = iterator_to_array($extensions);krsort($extensions);$admin = $container->getDefinition($target);foreach (array_values($extensions) as $extension) {$admin->addMethodCall('addExtension', [$extension]);}}}/*** @param array<string, mixed> $tags* @param array<string, array<string, array<string, array<string, mixed>>>> $extensionMap** @return array<string, array<string, mixed>>** @phpstan-param FlattenExtensionMap $extensionMap*/private function getExtensionsForAdmin(string $id, array $tags, Definition $admin, ContainerBuilder $container, array $extensionMap): array{// Trim possible parameter delimiters ("%") from the class name.$adminClass = trim($admin->getClass() ?? '', '%');if (!class_exists($adminClass, false) && $container->hasParameter($adminClass)) {$adminClass = $container->getParameter($adminClass);\assert(\is_string($adminClass));}\assert(class_exists($adminClass));$extensions = [];$excludes = $extensionMap['excludes'];unset($extensionMap['excludes']);foreach ($extensionMap as $type => $subjects) {foreach ($subjects as $subject => $extensionList) {if ('admins' === $type) {if ($id === $subject) {$extensions = array_merge($extensions, $extensionList);}continue;}// NEXT_MAJOR: Remove this line.$defaultModelClass = $admin->getArguments()[1] ?? null;foreach ($tags as $attributes) {// NEXT_MAJOR: Remove the fallback to $defaultModelClass and use null instead.$modelClass = $attributes['model_class'] ?? $defaultModelClass;if (null === $modelClass) {throw new InvalidArgumentException(sprintf('Missing tag attribute "model_class" on service "%s".', $id));}$class = $container->getParameterBag()->resolveValue($modelClass);if (!\is_string($class)) {throw new \TypeError(sprintf('Tag attribute "model_class" for service "%s" must be of type string, %s given.',$id,get_debug_type($class)));}if (!class_exists($class)) {continue;}if ($this->shouldApplyExtension($type, $subject, $class, $adminClass)) {$extensions = array_merge($extensions, $extensionList);}}}}if (isset($excludes[$id])) {$extensions = array_diff_key($extensions, $excludes[$id]);}return $extensions;}/*** @param array<string, array<string, array<string, string>|int|bool>> $config** @return array<string, array<string, array<string, array<string, int>>>> an array with the following structure** @phpstan-param ExtensionMap $config* @phpstan-return FlattenExtensionMap*/private function flattenExtensionConfiguration(array $config): array{/** @phpstan-var FlattenExtensionMap $extensionMap */$extensionMap = ['global' => [],'excludes' => [],'admins' => [],'implements' => [],'extends' => [],'instanceof' => [],'uses' => [],'admin_implements' => [],'admin_extends' => [],'admin_instanceof' => [],'admin_uses' => [],];foreach ($config as $extension => $options) {if (true === $options['global']) {$options['global'] = [$extension];} else {$options['global'] = [];}/*** @phpstan-var array{* global: array<string, string>,* excludes: array<string, string>,* admins: array<string, string>,* implements: array<class-string, string>,* extends: array<class-string, string>,* instanceof: array<class-string, string>,* uses: array<class-string, string>,* admin_implements: array<class-string, string>,* admin_extends: array<class-string, string>,* admin_instanceof: array<class-string, string>,* admin_uses: array<class-string, string>,* } $optionsMap*/$optionsMap = array_intersect_key($options, $extensionMap);foreach ($extensionMap as $key => &$value) {foreach ($optionsMap[$key] as $source) {$value[$source][$extension]['priority'] = $options['priority'];}}}return $extensionMap;}/*** @param \ReflectionClass<object> $class*/private function hasTrait(\ReflectionClass $class, string $traitName): bool{if (\in_array($traitName, $class->getTraitNames(), true)) {return true;}$parentClass = $class->getParentClass();if (false === $parentClass) {return false;}return $this->hasTrait($parentClass, $traitName);}/*** @phpstan-param class-string $class* @phpstan-param class-string $adminClass*/private function shouldApplyExtension(string $type, mixed $subject, string $class, string $adminClass): bool{$classReflection = new \ReflectionClass($class);$adminClassReflection = new \ReflectionClass($adminClass);switch ($type) {case 'global':return true;case 'instanceof':if (!\is_string($subject) || !class_exists($subject)) {return false;}$subjectReflection = new \ReflectionClass($subject);return $classReflection->isSubclassOf($subject) || $subjectReflection->getName() === $classReflection->getName();case 'implements':return \is_string($subject) && interface_exists($subject) && $classReflection->implementsInterface($subject);case 'extends':return \is_string($subject) && class_exists($subject) && $classReflection->isSubclassOf($subject);case 'uses':return \is_string($subject) && trait_exists($subject) && $this->hasTrait($classReflection, $subject);case 'admin_instanceof':if (!\is_string($subject) || !class_exists($subject)) {return false;}$subjectReflection = new \ReflectionClass($subject);return $adminClassReflection->isSubclassOf($subject) || $subjectReflection->getName() === $adminClassReflection->getName();case 'admin_implements':return \is_string($subject) && interface_exists($subject) && $adminClassReflection->implementsInterface($subject);case 'admin_extends':return \is_string($subject) && class_exists($subject) && $adminClassReflection->isSubclassOf($subject);case 'admin_uses':return \is_string($subject) && trait_exists($subject) && $this->hasTrait($adminClassReflection, $subject);default:return false;}}/*** Add extension configuration to the targets array.** @param array<string, \SplPriorityQueue<int, Reference>> $targets* @param array<string, mixed> $attributes*/private function addExtension(array &$targets,string $target,string $extension,array $attributes): void {if (!isset($targets[$target])) {/** @phpstan-var \SplPriorityQueue<int, Reference> $queue */$queue = new \SplPriorityQueue();$targets[$target] = $queue;}$priority = $attributes['priority'] ?? 0;$targets[$target]->insert(new Reference($extension), $priority);}}