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)