diff --git a/docs/reference/configuration.rst b/docs/reference/configuration.rst
index b6e5ebacf9..b3a2f7f178 100644
--- a/docs/reference/configuration.rst
+++ b/docs/reference/configuration.rst
@@ -235,6 +235,7 @@ Full Configuration Options
# Prototype
id:
+ global: false
admins: []
excludes: []
implements: []
diff --git a/docs/reference/extensions.rst b/docs/reference/extensions.rst
index a58a89e326..bc48a90b7e 100644
--- a/docs/reference/extensions.rst
+++ b/docs/reference/extensions.rst
@@ -80,6 +80,9 @@ Using ``config/packages/sonata_admin.yaml`` file has some advantages, it allows
extra options you can use to wire your extensions in a more dynamic way. This means you can change the behavior of all
admins that manage a class of a specific type.
+global:
+ adds the extension to all admins.
+
admins:
specify one or more admin service ids to which the Extension should be added
@@ -115,6 +118,7 @@ priority:
sonata_admin:
extensions:
app.publish.extension:
+ global: true
admins:
- app.admin.article
implements:
diff --git a/psalm-baseline.xml b/psalm-baseline.xml
index 7fea81f437..537d313679 100644
--- a/psalm-baseline.xml
+++ b/psalm-baseline.xml
@@ -34,6 +34,14 @@
new ArrayCollection(\array_slice($this->results, 0, $this->getMaxPerPage()))
+
+
+ $extensionMap
+
+
+ array<string, array<string, array<string, array<string, array<string, string>>>>>
+
+
buildSecurityInformation
diff --git a/src/DependencyInjection/Compiler/ExtensionCompilerPass.php b/src/DependencyInjection/Compiler/ExtensionCompilerPass.php
index e6d5a62223..09fe97e9ed 100644
--- a/src/DependencyInjection/Compiler/ExtensionCompilerPass.php
+++ b/src/DependencyInjection/Compiler/ExtensionCompilerPass.php
@@ -177,11 +177,12 @@ protected function getManagedClass(Definition $admin, ContainerBuilder $containe
}
/**
- * @param array>> $config
+ * @param array|bool>> $config
*
* @return array>>>> an array with the following structure.
*
* [
+ * 'global' => ['' => ['' => ['priority' => ]]],
* 'excludes' => ['' => ['' => ['priority' => ]]],
* 'admins' => ['' => ['' => ['priority' => ]]],
* 'implements' => ['' => ['' => ['priority' => ]]],
@@ -193,6 +194,7 @@ protected function getManagedClass(Definition $admin, ContainerBuilder $containe
protected function flattenExtensionConfiguration(array $config)
{
$extensionMap = [
+ 'global' => [],
'excludes' => [],
'admins' => [],
'implements' => [],
@@ -202,6 +204,12 @@ protected function flattenExtensionConfiguration(array $config)
];
foreach ($config as $extension => $options) {
+ if (true === $options['global']) {
+ $options['global'] = [$extension];
+ } else {
+ $options['global'] = [];
+ }
+
$optionsMap = array_intersect_key($options, $extensionMap);
foreach ($optionsMap as $key => $value) {
@@ -244,6 +252,8 @@ private function isSubtypeOf(string $type, string $subject, string $class): bool
$classReflection = new \ReflectionClass($class);
switch ($type) {
+ case 'global':
+ return true;
case 'instanceof':
$subjectReflection = new \ReflectionClass($subject);
diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php
index cbb3a0fca8..1b56858c19 100644
--- a/src/DependencyInjection/Configuration.php
+++ b/src/DependencyInjection/Configuration.php
@@ -516,7 +516,7 @@ public function getConfigTreeBuilder()
->arrayNode('extensions')
->useAttributeAsKey('id')
- ->defaultValue(['admins' => [], 'excludes' => [], 'implements' => [], 'extends' => [], 'instanceof' => [], 'uses' => []])
+ ->defaultValue([])
->prototype('array')
->fixXmlConfig('admin')
->fixXmlConfig('exclude')
@@ -524,6 +524,7 @@ public function getConfigTreeBuilder()
->fixXmlConfig('extend')
->fixXmlConfig('use')
->children()
+ ->booleanNode('global')->defaultValue(false)->end()
->arrayNode('admins')
->prototype('scalar')->end()
->end()
diff --git a/tests/DependencyInjection/Compiler/ExtensionCompilerPassTest.php b/tests/DependencyInjection/Compiler/ExtensionCompilerPassTest.php
index e465f34a63..c00a4ca935 100644
--- a/tests/DependencyInjection/Compiler/ExtensionCompilerPassTest.php
+++ b/tests/DependencyInjection/Compiler/ExtensionCompilerPassTest.php
@@ -75,12 +75,7 @@ public function testAdminExtensionLoad(): void
$this->assertTrue($container->hasParameter(sprintf('%s.extension.map', $this->root)));
$this->assertIsArray($extensionMap = $container->getParameter(sprintf('%s.extension.map', $this->root)));
- $this->assertArrayHasKey('admins', $extensionMap);
- $this->assertArrayHasKey('excludes', $extensionMap);
- $this->assertArrayHasKey('implements', $extensionMap);
- $this->assertArrayHasKey('extends', $extensionMap);
- $this->assertArrayHasKey('instanceof', $extensionMap);
- $this->assertArrayHasKey('uses', $extensionMap);
+ $this->assertSame([], $extensionMap);
}
/**
@@ -106,6 +101,7 @@ public function testFlattenEmptyExtensionConfiguration(): void
$this->assertArrayHasKey('instanceof', $extensionMap);
$this->assertArrayHasKey('uses', $extensionMap);
+ $this->assertEmpty($extensionMap['global']);
$this->assertEmpty($extensionMap['admins']);
$this->assertEmpty($extensionMap['excludes']);
$this->assertEmpty($extensionMap['implements']);
@@ -245,6 +241,7 @@ public function testProcess(): void
$extensionsPass->process($container);
$container->compile();
+ $this->assertTrue($container->hasDefinition('sonata_extension_global'));
$this->assertTrue($container->hasDefinition('sonata_extension_publish'));
$this->assertTrue($container->hasDefinition('sonata_extension_history'));
$this->assertTrue($container->hasDefinition('sonata_extension_order'));
@@ -255,6 +252,7 @@ public function testProcess(): void
$this->assertTrue($container->hasDefinition('sonata_article_admin'));
$this->assertTrue($container->hasDefinition('sonata_news_admin'));
+ $globalExtension = $container->get('sonata_extension_global');
$securityExtension = $container->get('sonata_extension_security');
$publishExtension = $container->get('sonata_extension_publish');
$historyExtension = $container->get('sonata_extension_history');
@@ -263,28 +261,31 @@ public function testProcess(): void
$def = $container->get('sonata_post_admin');
$extensions = $def->getExtensions();
- $this->assertCount(4, $extensions);
+ $this->assertCount(5, $extensions);
$this->assertSame($historyExtension, $extensions[0]);
$this->assertSame($publishExtension, $extensions[2]);
$this->assertSame($securityExtension, $extensions[3]);
+ $this->assertSame($globalExtension, $extensions[4]);
$def = $container->get('sonata_article_admin');
$extensions = $def->getExtensions();
- $this->assertCount(5, $extensions);
+ $this->assertCount(6, $extensions);
$this->assertSame($filterExtension, $extensions[0]);
$this->assertSame($securityExtension, $extensions[1]);
$this->assertSame($publishExtension, $extensions[2]);
$this->assertSame($orderExtension, $extensions[4]);
+ $this->assertSame($globalExtension, $extensions[5]);
$def = $container->get('sonata_news_admin');
$extensions = $def->getExtensions();
- $this->assertCount(5, $extensions);
+ $this->assertCount(6, $extensions);
$this->assertSame($historyExtension, $extensions[0]);
- $this->assertSame($securityExtension, $extensions[1]);
- $this->assertSame($filterExtension, $extensions[2]);
+ $this->assertSame($securityExtension, $extensions[2]);
+ $this->assertSame($filterExtension, $extensions[3]);
$this->assertSame($orderExtension, $extensions[4]);
+ $this->assertSame($globalExtension, $extensions[5]);
}
/**
@@ -308,13 +309,14 @@ public function testProcessThrowsExceptionIfTraitsAreNotAvailable(): void
$container->compile();
}
- /**
- * @return array
- */
- protected function getConfig()
+ protected function getConfig(): array
{
$config = [
'extensions' => [
+ 'sonata_extension_global' => [
+ 'global' => true,
+ 'priority' => -255,
+ ],
'sonata_extension_publish' => [
'admins' => ['sonata_post_admin'],
'implements' => [Publishable::class],
@@ -418,6 +420,10 @@ private function getContainer(): ContainerBuilder
// Add admin extension definition's
$extensionClass = \get_class($this->createMock(AdminExtensionInterface::class));
+ $container
+ ->register('sonata_extension_global')
+ ->setPublic(true)
+ ->setClass($extensionClass);
$container
->register('sonata_extension_publish')
->setPublic(true)