From 67cd22628dbe66aa93f8537447b448933a23da1f Mon Sep 17 00:00:00 2001 From: Ryan Hendrickson <8751750+rynhndrcksn@users.noreply.github.com> Date: Mon, 29 Jan 2024 18:59:10 -0800 Subject: [PATCH 01/10] Fix exception if assets dir is missing in prod --- Resources/config/asset_mapper.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Resources/config/asset_mapper.php b/Resources/config/asset_mapper.php index f41574d3b..b7ce65f03 100644 --- a/Resources/config/asset_mapper.php +++ b/Resources/config/asset_mapper.php @@ -75,6 +75,7 @@ param('kernel.project_dir'), abstract_arg('array of excluded path patterns'), abstract_arg('exclude dot files'), + param('kernel.debug'), ]) ->set('asset_mapper.public_assets_path_resolver', PublicAssetsPathResolver::class) From 1870ddfc1bcb1729db889978aec7a70c90485103 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Andr=C3=A9?= Date: Sat, 3 Feb 2024 23:16:59 +0100 Subject: [PATCH 02/10] [AssetMapper] Improve import_polyfill configuration error --- DependencyInjection/Configuration.php | 4 +++ .../DependencyInjection/ConfigurationTest.php | 36 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 2494135b6..29cb4875d 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -930,6 +930,10 @@ private function addAssetMapperSection(ArrayNodeDefinition $rootNode, callable $ ->end() ->scalarNode('importmap_polyfill') ->info('The importmap name that will be used to load the polyfill. Set to false to disable.') + ->validate() + ->ifTrue() + ->thenInvalid('Invalid "importmap_polyfill" value. Must be either an importmap name or false.') + ->end() ->defaultValue('es-module-shims') ->end() ->arrayNode('importmap_script_attributes') diff --git a/Tests/DependencyInjection/ConfigurationTest.php b/Tests/DependencyInjection/ConfigurationTest.php index 42619d07f..ca34ce109 100644 --- a/Tests/DependencyInjection/ConfigurationTest.php +++ b/Tests/DependencyInjection/ConfigurationTest.php @@ -143,6 +143,42 @@ public function testAssetMapperCanBeEnabled() $this->assertEquals($defaultConfig, $config['asset_mapper']); } + /** + * @dataProvider provideImportmapPolyfillTests + */ + public function testAssetMapperPolyfillValue(mixed $polyfillValue, bool $isValid, mixed $expected) + { + $processor = new Processor(); + $configuration = new Configuration(true); + + if (!$isValid) { + $this->expectException(InvalidConfigurationException::class); + $this->expectExceptionMessage($expected); + } + + $config = $processor->processConfiguration($configuration, [[ + 'http_method_override' => false, + 'handle_all_throwables' => true, + 'php_errors' => ['log' => true], + 'asset_mapper' => null === $polyfillValue ? [] : [ + 'importmap_polyfill' => $polyfillValue, + ], + ]]); + + if ($isValid) { + $this->assertEquals($expected, $config['asset_mapper']['importmap_polyfill']); + } + } + + public static function provideImportmapPolyfillTests() + { + yield [true, false, 'Must be either an importmap name or false.']; + yield [null, true, 'es-module-shims']; + yield ['es-module-shims', true, 'es-module-shims']; + yield ['foo', true, 'foo']; + yield [false, true, false]; + } + /** * @dataProvider provideValidAssetsPackageNameConfigurationTests */ From a85889e2319c7e169ca46cb1ed9feedfc15af709 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 5 Feb 2024 15:05:42 +0100 Subject: [PATCH 03/10] [FrameworkBundle] Fix eager-loading of env vars in ConfigBuilderCacheWarmer --- CacheWarmer/ConfigBuilderCacheWarmer.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CacheWarmer/ConfigBuilderCacheWarmer.php b/CacheWarmer/ConfigBuilderCacheWarmer.php index aabb0061e..5e6cb85a8 100644 --- a/CacheWarmer/ConfigBuilderCacheWarmer.php +++ b/CacheWarmer/ConfigBuilderCacheWarmer.php @@ -15,9 +15,12 @@ use Symfony\Component\Config\Builder\ConfigBuilderGenerator; use Symfony\Component\Config\Builder\ConfigBuilderGeneratorInterface; use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\DependencyInjection\Container; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface; use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; +use Symfony\Component\DependencyInjection\ParameterBag\ContainerBag; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; use Symfony\Component\HttpKernel\KernelInterface; @@ -71,7 +74,8 @@ private function dumpExtension(ExtensionInterface $extension, ConfigBuilderGener if ($extension instanceof ConfigurationInterface) { $configuration = $extension; } elseif ($extension instanceof ConfigurationExtensionInterface) { - $configuration = $extension->getConfiguration([], new ContainerBuilder($this->kernel->getContainer()->getParameterBag())); + $container = $this->kernel->getContainer(); + $configuration = $extension->getConfiguration([], new ContainerBuilder($container instanceof Container ? new ContainerBag($container) : new ParameterBag())); } if (!$configuration) { From d8d2152dbe71e16d393396377040c9387e47b52c Mon Sep 17 00:00:00 2001 From: Soner Sayakci Date: Mon, 5 Feb 2024 08:10:27 +0100 Subject: [PATCH 04/10] [FrameworkBundle] Prevent silenced warning by checking if /proc/mount exists --- Command/CacheClearCommand.php | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Command/CacheClearCommand.php b/Command/CacheClearCommand.php index 4be5c2c98..20df3f69e 100644 --- a/Command/CacheClearCommand.php +++ b/Command/CacheClearCommand.php @@ -49,9 +49,6 @@ public function __construct(CacheClearerInterface $cacheClearer, ?Filesystem $fi $this->filesystem = $filesystem ?? new Filesystem(); } - /** - * {@inheritdoc} - */ protected function configure() { $this @@ -71,9 +68,6 @@ protected function configure() ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { $fs = $this->filesystem; @@ -208,7 +202,7 @@ private function isNfs(string $dir): bool if (null === $mounts) { $mounts = []; - if ('/' === \DIRECTORY_SEPARATOR && $files = @file('/proc/mounts')) { + if ('/' === \DIRECTORY_SEPARATOR && is_readable('/proc/mounts') && $files = @file('/proc/mounts')) { foreach ($files as $mount) { $mount = \array_slice(explode(' ', $mount), 1, -3); if (!\in_array(array_pop($mount), ['vboxsf', 'nfs'])) { From eda7e95a1af3f42b5eb33832a0ad498ce8334b0e Mon Sep 17 00:00:00 2001 From: HypeMC Date: Tue, 6 Feb 2024 20:07:29 +0100 Subject: [PATCH 05/10] [Scheduler] Fix messenger receiver with no alias --- .../Messenger/DummyTaskWithCustomReceiver.php | 22 +++++++++++++++++++ Tests/Functional/SchedulerTest.php | 9 ++++++++ Tests/Functional/app/Scheduler/config.yml | 15 +++++++++++++ composer.json | 4 ++-- 4 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 Tests/Fixtures/Messenger/DummyTaskWithCustomReceiver.php diff --git a/Tests/Fixtures/Messenger/DummyTaskWithCustomReceiver.php b/Tests/Fixtures/Messenger/DummyTaskWithCustomReceiver.php new file mode 100644 index 000000000..10c3f37cf --- /dev/null +++ b/Tests/Fixtures/Messenger/DummyTaskWithCustomReceiver.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger; + +use Symfony\Component\Scheduler\Attribute\AsPeriodicTask; + +#[AsPeriodicTask(frequency: 5, schedule: 'custom_receiver')] +class DummyTaskWithCustomReceiver +{ + public function __invoke() + { + } +} diff --git a/Tests/Functional/SchedulerTest.php b/Tests/Functional/SchedulerTest.php index 7f737a4c4..99776e822 100644 --- a/Tests/Functional/SchedulerTest.php +++ b/Tests/Functional/SchedulerTest.php @@ -88,6 +88,15 @@ public function testAutoconfiguredScheduler() $this->assertSame([['5', 6], ['7', 8]], $calls['attributesOnMethod']); } + public function testSchedulerWithCustomTransport() + { + $container = self::getContainer(); + $container->set('clock', new MockClock('2023-10-26T08:59:59Z')); + + $this->assertTrue($container->get('receivers')->has('scheduler_custom_receiver')); + $this->assertSame($container->get('scheduler_custom_receiver'), $container->get('receivers')->get('scheduler_custom_receiver')); + } + protected static function createKernel(array $options = []): KernelInterface { return parent::createKernel(['test_case' => 'Scheduler'] + $options); diff --git a/Tests/Functional/app/Scheduler/config.yml b/Tests/Functional/app/Scheduler/config.yml index 90016381b..bd1cb6516 100644 --- a/Tests/Functional/app/Scheduler/config.yml +++ b/Tests/Functional/app/Scheduler/config.yml @@ -13,9 +13,24 @@ services: Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\DummyTask: autoconfigure: true + Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\DummyTaskWithCustomReceiver: + autoconfigure: true + clock: synthetic: true receivers: public: true alias: 'messenger.receiver_locator' + + scheduler_custom_receiver: + public: true + class: Symfony\Component\Messenger\Transport\TransportInterface + factory: [ '@messenger.transport_factory', 'createTransport' ] + arguments: + - 'schedule://custom_receiver' + - { transport_name: 'scheduler_custom_receiver' } + - !service + class: Symfony\Component\Messenger\Transport\Serialization\Serializer + tags: + - { name: 'messenger.receiver' } diff --git a/composer.json b/composer.json index afb7c2069..624167783 100644 --- a/composer.json +++ b/composer.json @@ -57,7 +57,7 @@ "symfony/notifier": "^5.4|^6.0|^7.0", "symfony/process": "^5.4|^6.0|^7.0", "symfony/rate-limiter": "^5.4|^6.0|^7.0", - "symfony/scheduler": "^6.4.3|^7.0.3", + "symfony/scheduler": "^6.4.4|^7.0.4", "symfony/security-bundle": "^5.4|^6.0|^7.0", "symfony/semaphore": "^5.4|^6.0|^7.0", "symfony/serializer": "^6.4|^7.0", @@ -93,7 +93,7 @@ "symfony/mime": "<6.4", "symfony/property-info": "<5.4", "symfony/property-access": "<5.4", - "symfony/scheduler": "<6.4.3|>=7.0.0,<7.0.3", + "symfony/scheduler": "<6.4.4|>=7.0.0,<7.0.4", "symfony/serializer": "<6.4", "symfony/security-csrf": "<5.4", "symfony/security-core": "<5.4", From c0d31ca240c0746f8ffdc867527207d2763d6985 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 7 Feb 2024 14:29:23 +0100 Subject: [PATCH 06/10] Skip Twig v3.9-dev for now --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 8479f1d88..f1b6cca7c 100644 --- a/composer.json +++ b/composer.json @@ -66,7 +66,7 @@ "symfony/property-info": "^4.4|^5.0|^6.0", "symfony/web-link": "^4.4|^5.0|^6.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "twig/twig": "^2.10|^3.0" + "twig/twig": "^2.10|~3.8.0" }, "conflict": { "doctrine/annotations": "<1.13.1", From aee07924ea20b20409100e5280666853101dd1cd Mon Sep 17 00:00:00 2001 From: Sem Schilder Date: Tue, 13 Feb 2024 13:24:51 +0100 Subject: [PATCH 07/10] [FrameworkBundle] Check if the _route attribute exists on the request --- DataCollector/RouterDataCollector.php | 2 +- .../DataCollector/RouterDataCollectorTest.php | 66 +++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 Tests/DataCollector/RouterDataCollectorTest.php diff --git a/DataCollector/RouterDataCollector.php b/DataCollector/RouterDataCollector.php index c5d0673de..c2edc8637 100644 --- a/DataCollector/RouterDataCollector.php +++ b/DataCollector/RouterDataCollector.php @@ -28,7 +28,7 @@ public function guessRoute(Request $request, $controller) $controller = $controller[0]; } - if ($controller instanceof RedirectController) { + if ($controller instanceof RedirectController && $request->attributes->has('_route')) { return $request->attributes->get('_route'); } diff --git a/Tests/DataCollector/RouterDataCollectorTest.php b/Tests/DataCollector/RouterDataCollectorTest.php new file mode 100644 index 000000000..5681bd04a --- /dev/null +++ b/Tests/DataCollector/RouterDataCollectorTest.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\DataCollector; + +use Symfony\Bundle\FrameworkBundle\Controller\RedirectController; +use Symfony\Bundle\FrameworkBundle\DataCollector\RouterDataCollector; +use Symfony\Bundle\FrameworkBundle\Tests\TestCase; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\ControllerEvent; +use Symfony\Component\HttpKernel\KernelInterface; + +class RouterDataCollectorTest extends TestCase +{ + public function testRouteRedirectControllerNoRouteAtrribute() + { + $collector = new RouterDataCollector(); + + $request = Request::create('http://test.com/foo?bar=baz'); + $response = new RedirectResponse('http://test.com/redirect'); + + $event = $this->createControllerEvent($request); + + $collector->onKernelController($event); + $collector->collect($request, $response); + + $this->assertTrue($collector->getRedirect()); + $this->assertEquals('http://test.com/redirect', $collector->getTargetUrl()); + $this->assertEquals('n/a', $collector->getTargetRoute()); + } + + public function testRouteRedirectControllerWithRouteAttribute() + { + $collector = new RouterDataCollector(); + + $request = Request::create('http://test.com/foo?bar=baz'); + $request->attributes->set('_route', 'current-route'); + + $response = new RedirectResponse('http://test.com/redirect'); + + $event = $this->createControllerEvent($request); + + $collector->onKernelController($event); + $collector->collect($request, $response); + + $this->assertTrue($collector->getRedirect()); + $this->assertEquals('http://test.com/redirect', $collector->getTargetUrl()); + $this->assertEquals('current-route', $collector->getTargetRoute()); + } + + protected function createControllerEvent(Request $request): ControllerEvent + { + $kernel = $this->createMock(KernelInterface::class); + + return new ControllerEvent($kernel, new RedirectController(), $request, null); + } +} From f30cf8ac3e2ac047d70efd0ad2aba5d20d6a38c9 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 12 Feb 2024 18:08:26 +0100 Subject: [PATCH 08/10] [TwigBridge] Fix compat with Twig v3.9 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index f1b6cca7c..c1f08b183 100644 --- a/composer.json +++ b/composer.json @@ -66,7 +66,7 @@ "symfony/property-info": "^4.4|^5.0|^6.0", "symfony/web-link": "^4.4|^5.0|^6.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "twig/twig": "^2.10|~3.8.0" + "twig/twig": "^2.10|^3.0.4" }, "conflict": { "doctrine/annotations": "<1.13.1", From 224f69093099a507cf84d8c48ceb29e8653a5896 Mon Sep 17 00:00:00 2001 From: HypeMC Date: Tue, 20 Feb 2024 15:48:43 +0100 Subject: [PATCH 09/10] [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; } } From c76d3881596860ead95f5444a5ce4414447f0067 Mon Sep 17 00:00:00 2001 From: HypeMC Date: Thu, 22 Feb 2024 23:50:59 +0100 Subject: [PATCH 10/10] [FrameworkBundle] Merge test from 5.4 --- .../ConfigBuilderCacheWarmerTest.php | 376 +++++++++++++++++- 1 file changed, 354 insertions(+), 22 deletions(-) diff --git a/Tests/CacheWarmer/ConfigBuilderCacheWarmerTest.php b/Tests/CacheWarmer/ConfigBuilderCacheWarmerTest.php index a0e8fe834..294a3cd25 100644 --- a/Tests/CacheWarmer/ConfigBuilderCacheWarmerTest.php +++ b/Tests/CacheWarmer/ConfigBuilderCacheWarmerTest.php @@ -14,15 +14,26 @@ 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\Compiler\CompilerPassInterface; 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 { - private $varDir; + private string $varDir; protected function setUp(): void { @@ -40,21 +51,110 @@ protected function tearDown(): void public function testBuildDirIsUsedAsConfigBuilderOutputDir() { - $kernel = new class($this->varDir) extends Kernel implements CompilerPassInterface { + $kernel = new TestKernel($this->varDir); + $kernel->boot(); + + self::assertDirectoryDoesNotExist($kernel->getBuildDir().'/Symfony'); + self::assertDirectoryDoesNotExist($kernel->getCacheDir().'/Symfony'); + + $warmer = new ConfigBuilderCacheWarmer($kernel); + $warmer->warmUp($kernel->getCacheDir()); + + self::assertDirectoryDoesNotExist($kernel->getBuildDir().'/Symfony'); + self::assertDirectoryDoesNotExist($kernel->getCacheDir().'/Symfony'); + + $warmer->warmUp($kernel->getCacheDir(), $kernel->getBuildDir()); + + 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'; @@ -65,37 +165,269 @@ public function getCacheDir(): string return $this->varDir.'/cache'; } - public function registerContainerConfiguration(LoaderInterface $loader): void + public function getLogDir(): string { - $loader->load(static function (ContainerBuilder $container) { - $container->loadFromExtension('framework', [ - 'annotations' => false, - 'handle_all_throwables' => true, - 'http_method_override' => false, - 'php_errors' => ['log' => true], - ]); - }); + return $this->varDir.'/log'; } - public function process(ContainerBuilder $container): void + public function getCharset(): string { - $container->removeDefinition('config_builder.warmer'); + return 'UTF-8'; } }; $kernel->boot(); - self::assertDirectoryDoesNotExist($kernel->getBuildDir().'/Symfony'); - self::assertDirectoryDoesNotExist($kernel->getCacheDir().'/Symfony'); + $warmer = new ConfigBuilderCacheWarmer($kernel); + $warmer->warmUp($kernel->getCacheDir(), $kernel->getBuildDir()); + + 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()); + $warmer->warmUp($kernel->getCacheDir(), $kernel->getBuildDir()); - self::assertDirectoryDoesNotExist($kernel->getBuildDir().'/Symfony'); - self::assertDirectoryDoesNotExist($kernel->getCacheDir().'/Symfony'); + 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(): string|false + { + 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(), $kernel->getBuildDir()); - self::assertDirectoryExists($kernel->getBuildDir().'/Symfony'); - self::assertDirectoryDoesNotExist($kernel->getCacheDir().'/Symfony'); + 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(), $kernel->getBuildDir()); + + 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 implements CompilerPassInterface +{ + 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 + { + $loader->load(static function (ContainerBuilder $container) { + $container->loadFromExtension('framework', [ + 'annotations' => false, + 'handle_all_throwables' => true, + 'http_method_override' => false, + 'php_errors' => ['log' => true], + ]); + }); + } + + public function process(ContainerBuilder $container): void + { + $container->removeDefinition('config_builder.warmer'); + } +} + +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; } }