From 224f69093099a507cf84d8c48ceb29e8653a5896 Mon Sep 17 00:00:00 2001 From: HypeMC Date: Tue, 20 Feb 2024 15:48:43 +0100 Subject: [PATCH] [FrameworkBundle] Fix config builder with extensions extended in `build()` --- CacheWarmer/ConfigBuilderCacheWarmer.php | 24 +- .../ConfigBuilderCacheWarmerTest.php | 345 +++++++++++++++++- 2 files changed, 359 insertions(+), 10 deletions(-) diff --git a/CacheWarmer/ConfigBuilderCacheWarmer.php b/CacheWarmer/ConfigBuilderCacheWarmer.php index 5e6cb85a8..18472263c 100644 --- a/CacheWarmer/ConfigBuilderCacheWarmer.php +++ b/CacheWarmer/ConfigBuilderCacheWarmer.php @@ -22,6 +22,7 @@ use Symfony\Component\DependencyInjection\ParameterBag\ContainerBag; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; +use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\HttpKernel\KernelInterface; /** @@ -49,12 +50,27 @@ public function warmUp(string $cacheDir) { $generator = new ConfigBuilderGenerator($this->kernel->getBuildDir()); - foreach ($this->kernel->getBundles() as $bundle) { - $extension = $bundle->getContainerExtension(); - if (null === $extension) { - continue; + if ($this->kernel instanceof Kernel) { + /** @var ContainerBuilder $container */ + $container = \Closure::bind(function (Kernel $kernel) { + $containerBuilder = $kernel->getContainerBuilder(); + $kernel->prepareContainer($containerBuilder); + + return $containerBuilder; + }, null, $this->kernel)($this->kernel); + + $extensions = $container->getExtensions(); + } else { + $extensions = []; + foreach ($this->kernel->getBundles() as $bundle) { + $extension = $bundle->getContainerExtension(); + if (null !== $extension) { + $extensions[] = $extension; + } } + } + foreach ($extensions as $extension) { try { $this->dumpExtension($extension, $generator); } catch (\Exception $e) { diff --git a/Tests/CacheWarmer/ConfigBuilderCacheWarmerTest.php b/Tests/CacheWarmer/ConfigBuilderCacheWarmerTest.php index c64e5d3b4..85975c621 100644 --- a/Tests/CacheWarmer/ConfigBuilderCacheWarmerTest.php +++ b/Tests/CacheWarmer/ConfigBuilderCacheWarmerTest.php @@ -14,9 +14,21 @@ use Symfony\Bundle\FrameworkBundle\CacheWarmer\ConfigBuilderCacheWarmer; use Symfony\Bundle\FrameworkBundle\FrameworkBundle; use Symfony\Bundle\FrameworkBundle\Tests\TestCase; +use Symfony\Component\Config\Definition\Builder\NodeDefinition; +use Symfony\Component\Config\Definition\Builder\TreeBuilder; +use Symfony\Component\Config\Definition\ConfigurationInterface; use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Extension\Extension; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Bundle\Bundle; +use Symfony\Component\HttpKernel\Bundle\BundleInterface; use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\HttpKernel\KernelInterface; class ConfigBuilderCacheWarmerTest extends TestCase { @@ -38,21 +50,102 @@ protected function tearDown(): void public function testBuildDirIsUsedAsConfigBuilderOutputDir() { - $kernel = new class($this->varDir) extends Kernel { + $kernel = new TestKernel($this->varDir); + $kernel->boot(); + + $warmer = new ConfigBuilderCacheWarmer($kernel); + $warmer->warmUp($kernel->getCacheDir()); + + self::assertDirectoryExists($kernel->getBuildDir().'/Symfony'); + self::assertDirectoryDoesNotExist($kernel->getCacheDir().'/Symfony'); + } + + public function testWithCustomKernelImplementation() + { + $kernel = new class($this->varDir) implements KernelInterface { private $varDir; public function __construct(string $varDir) { - parent::__construct('test', false); - $this->varDir = $varDir; } + public function handle(Request $request, int $type = self::MAIN_REQUEST, bool $catch = true): Response + { + return new Response(); + } + public function registerBundles(): iterable { yield new FrameworkBundle(); } + public function registerContainerConfiguration(LoaderInterface $loader): void + { + } + + public function boot(): void + { + } + + public function shutdown(): void + { + } + + public function getBundles(): array + { + $bundles = []; + foreach ($this->registerBundles() as $bundle) { + $bundles[$bundle->getName()] = $bundle; + } + + return $bundles; + } + + public function getBundle(string $name): BundleInterface + { + foreach ($this->getBundles() as $bundleName => $bundle) { + if ($bundleName === $name) { + return $bundle; + } + } + + throw new \InvalidArgumentException(); + } + + public function locateResource(string $name): string + { + return __DIR__; + } + + public function getEnvironment(): string + { + return 'test'; + } + + public function isDebug(): bool + { + return false; + } + + public function getProjectDir(): string + { + return __DIR__; + } + + public function getContainer(): ContainerInterface + { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', $this->isDebug()); + + return $container; + } + + public function getStartTime(): float + { + return -\INF; + } + public function getBuildDir(): string { return $this->varDir.'/build'; @@ -63,8 +156,14 @@ public function getCacheDir(): string return $this->varDir.'/cache'; } - public function registerContainerConfiguration(LoaderInterface $loader) + public function getLogDir(): string { + return $this->varDir.'/cache'; + } + + public function getCharset(): string + { + return 'UTF-8'; } }; $kernel->boot(); @@ -72,7 +171,241 @@ public function registerContainerConfiguration(LoaderInterface $loader) $warmer = new ConfigBuilderCacheWarmer($kernel); $warmer->warmUp($kernel->getCacheDir()); - self::assertDirectoryExists($kernel->getBuildDir().'/Symfony'); - self::assertDirectoryDoesNotExist($kernel->getCacheDir().'/Symfony'); + self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/FrameworkConfig.php'); + } + + public function testExtensionAddedInKernel() + { + $kernel = new class($this->varDir) extends TestKernel { + protected function build(ContainerBuilder $container): void + { + $container->registerExtension(new class() extends Extension implements ConfigurationInterface { + public function load(array $configs, ContainerBuilder $container): void + { + } + + public function getConfigTreeBuilder(): TreeBuilder + { + $treeBuilder = new TreeBuilder('app'); + $rootNode = $treeBuilder->getRootNode(); + + $rootNode + ->children() + ->scalarNode('provider')->end() + ->end() + ; + + return $treeBuilder; + } + + public function getAlias(): string + { + return 'app'; + } + }); + } + }; + $kernel->boot(); + + $warmer = new ConfigBuilderCacheWarmer($kernel); + $warmer->warmUp($kernel->getCacheDir()); + + self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/FrameworkConfig.php'); + self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/AppConfig.php'); + } + + public function testKernelAsExtension() + { + $kernel = new class($this->varDir) extends TestKernel implements ExtensionInterface, ConfigurationInterface { + public function load(array $configs, ContainerBuilder $container): void + { + } + + public function getXsdValidationBasePath() + { + return false; + } + + public function getNamespace(): string + { + return 'http://www.example.com/schema/acme'; + } + + public function getAlias(): string + { + return 'kernel'; + } + + public function getConfigTreeBuilder(): TreeBuilder + { + $treeBuilder = new TreeBuilder('kernel'); + $rootNode = $treeBuilder->getRootNode(); + + $rootNode + ->children() + ->scalarNode('provider')->end() + ->end() + ; + + return $treeBuilder; + } + }; + $kernel->boot(); + + $warmer = new ConfigBuilderCacheWarmer($kernel); + $warmer->warmUp($kernel->getCacheDir()); + + self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/FrameworkConfig.php'); + self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/KernelConfig.php'); + } + + public function testExtensionsExtendedInBuildMethods() + { + $kernel = new class($this->varDir) extends TestKernel { + protected function build(ContainerBuilder $container): void + { + /** @var TestSecurityExtension $extension */ + $extension = $container->getExtension('test_security'); + $extension->addAuthenticatorFactory(new class() implements TestAuthenticatorFactoryInterface { + public function getKey(): string + { + return 'token'; + } + + public function addConfiguration(NodeDefinition $node): void + { + } + }); + } + + public function registerBundles(): iterable + { + yield from parent::registerBundles(); + + yield new class() extends Bundle { + public function getContainerExtension(): ExtensionInterface + { + return new TestSecurityExtension(); + } + }; + + yield new class() extends Bundle { + public function build(ContainerBuilder $container): void + { + /** @var TestSecurityExtension $extension */ + $extension = $container->getExtension('test_security'); + $extension->addAuthenticatorFactory(new class() implements TestAuthenticatorFactoryInterface { + public function getKey(): string + { + return 'form-login'; + } + + public function addConfiguration(NodeDefinition $node): void + { + $node + ->children() + ->scalarNode('provider')->end() + ->end() + ; + } + }); + } + }; + } + }; + $kernel->boot(); + + $warmer = new ConfigBuilderCacheWarmer($kernel); + $warmer->warmUp($kernel->getCacheDir()); + + self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/FrameworkConfig.php'); + self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/SecurityConfig.php'); + self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/Security/FirewallConfig.php'); + self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/Security/FirewallConfig/FormLoginConfig.php'); + self::assertFileExists($kernel->getBuildDir().'/Symfony/Config/Security/FirewallConfig/TokenConfig.php'); + } +} + +class TestKernel extends Kernel +{ + private $varDir; + + public function __construct(string $varDir) + { + parent::__construct('test', false); + + $this->varDir = $varDir; + } + + public function registerBundles(): iterable + { + yield new FrameworkBundle(); + } + + public function getBuildDir(): string + { + return $this->varDir.'/build'; + } + + public function getCacheDir(): string + { + return $this->varDir.'/cache'; + } + + public function registerContainerConfiguration(LoaderInterface $loader): void + { + } +} + +interface TestAuthenticatorFactoryInterface +{ + public function getKey(): string; + + public function addConfiguration(NodeDefinition $builder): void; +} + +class TestSecurityExtension extends Extension implements ConfigurationInterface +{ + /** @var TestAuthenticatorFactoryInterface[] */ + private $factories; + + public function load(array $configs, ContainerBuilder $container): void + { + } + + public function getConfiguration(array $config, ContainerBuilder $container): ConfigurationInterface + { + return $this; + } + + public function addAuthenticatorFactory(TestAuthenticatorFactoryInterface $factory): void + { + $this->factories[] = $factory; + } + + public function getConfigTreeBuilder(): TreeBuilder + { + $treeBuilder = new TreeBuilder('security'); + $rootNode = $treeBuilder->getRootNode(); + + $firewallNodeBuilder = $rootNode + ->fixXmlConfig('firewall') + ->children() + ->arrayNode('firewalls') + ->isRequired() + ->requiresAtLeastOneElement() + ->useAttributeAsKey('name') + ->prototype('array') + ->children() + ; + + foreach ($this->factories as $factory) { + $name = str_replace('-', '_', $factory->getKey()); + $factoryNode = $firewallNodeBuilder->arrayNode($name); + + $factory->addConfiguration($factoryNode); + } + + return $treeBuilder; } }