diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index ac4bd6da0b..43d5d081a8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -1011,7 +1011,10 @@ class FrameworkExtension extends Extension $container->register('routing.loader.annotation', AnnotatedRouteControllerLoader::class) ->setPublic(false) ->addTag('routing.loader', ['priority' => -10]) - ->addArgument(new Reference('annotation_reader', ContainerInterface::NULL_ON_INVALID_REFERENCE)); + ->setArguments([ + new Reference('annotation_reader', ContainerInterface::NULL_ON_INVALID_REFERENCE), + '%kernel.environment%', + ]); $container->register('routing.loader.annotation.directory', AnnotationDirectoryLoader::class) ->setPublic(false) diff --git a/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php b/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php index 52587cc7c7..c77c452d99 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php +++ b/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php @@ -152,7 +152,7 @@ trait MicroKernelTrait }; try { - $this->configureContainer(new ContainerConfigurator($container, $kernelLoader, $instanceof, $file, $file), $loader); + $this->configureContainer(new ContainerConfigurator($container, $kernelLoader, $instanceof, $file, $file, $this->getEnvironment()), $loader); } finally { $instanceof = []; $kernelLoader->registerAliasesForSinglyImplementedInterfaces(); @@ -193,7 +193,7 @@ trait MicroKernelTrait return $routes->build(); } - $this->configureRoutes(new RoutingConfigurator($collection, $kernelLoader, $file, $file)); + $this->configureRoutes(new RoutingConfigurator($collection, $kernelLoader, $file, $file, $this->getEnvironment())); foreach ($collection as $route) { $controller = $route->getDefault('_controller'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.php index bd44427bf6..09e340ff8a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing.php @@ -49,36 +49,42 @@ return static function (ContainerConfigurator $container) { ->set('routing.loader.xml', XmlFileLoader::class) ->args([ service('file_locator'), + '%kernel.environment%', ]) ->tag('routing.loader') ->set('routing.loader.yml', YamlFileLoader::class) ->args([ service('file_locator'), + '%kernel.environment%', ]) ->tag('routing.loader') ->set('routing.loader.php', PhpFileLoader::class) ->args([ service('file_locator'), + '%kernel.environment%', ]) ->tag('routing.loader') ->set('routing.loader.glob', GlobFileLoader::class) ->args([ service('file_locator'), + '%kernel.environment%', ]) ->tag('routing.loader') ->set('routing.loader.directory', DirectoryLoader::class) ->args([ service('file_locator'), + '%kernel.environment%', ]) ->tag('routing.loader') ->set('routing.loader.container', ContainerLoader::class) ->args([ tagged_locator('routing.route_loader'), + '%kernel.environment%', ]) ->tag('routing.loader') diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 0f66c585ed..5cada9c672 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -19,7 +19,7 @@ "php": ">=7.2.5", "ext-xml": "*", "symfony/cache": "^5.2", - "symfony/config": "^5.0", + "symfony/config": "^5.3", "symfony/dependency-injection": "^5.3", "symfony/deprecation-contracts": "^2.1", "symfony/event-dispatcher": "^5.1", @@ -30,7 +30,7 @@ "symfony/polyfill-php80": "^1.15", "symfony/filesystem": "^4.4|^5.0", "symfony/finder": "^4.4|^5.0", - "symfony/routing": "^5.2" + "symfony/routing": "^5.3" }, "require-dev": { "doctrine/annotations": "^1.10.4", diff --git a/src/Symfony/Component/Config/Loader/FileLoader.php b/src/Symfony/Component/Config/Loader/FileLoader.php index 0b01df49f3..4c2a43b14a 100644 --- a/src/Symfony/Component/Config/Loader/FileLoader.php +++ b/src/Symfony/Component/Config/Loader/FileLoader.php @@ -31,9 +31,10 @@ abstract class FileLoader extends Loader private $currentDir; - public function __construct(FileLocatorInterface $locator) + public function __construct(FileLocatorInterface $locator, string $env = null) { $this->locator = $locator; + parent::__construct($env); } /** diff --git a/src/Symfony/Component/Config/Loader/Loader.php b/src/Symfony/Component/Config/Loader/Loader.php index 62cae685e2..92b894f7d8 100644 --- a/src/Symfony/Component/Config/Loader/Loader.php +++ b/src/Symfony/Component/Config/Loader/Loader.php @@ -21,6 +21,12 @@ use Symfony\Component\Config\Exception\LoaderLoadException; abstract class Loader implements LoaderInterface { protected $resolver; + protected $env; + + public function __construct(string $env = null) + { + $this->env = $env; + } /** * {@inheritdoc} diff --git a/src/Symfony/Component/DependencyInjection/CHANGELOG.md b/src/Symfony/Component/DependencyInjection/CHANGELOG.md index a6c60db0e2..c08e7cb75d 100644 --- a/src/Symfony/Component/DependencyInjection/CHANGELOG.md +++ b/src/Symfony/Component/DependencyInjection/CHANGELOG.md @@ -8,6 +8,7 @@ CHANGELOG * Add `%env(not:...)%` processor to negate boolean values * Add support for loading autoconfiguration rules via the `#[Autoconfigure]` and `#[AutoconfigureTag]` attributes on PHP 8 * Add autoconfigurable attributes + * Add support for per-env configuration in loaders 5.2.0 ----- diff --git a/src/Symfony/Component/DependencyInjection/Loader/ClosureLoader.php b/src/Symfony/Component/DependencyInjection/Loader/ClosureLoader.php index 57aa83d6a0..a8337d4e66 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/ClosureLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/ClosureLoader.php @@ -25,9 +25,10 @@ class ClosureLoader extends Loader { private $container; - public function __construct(ContainerBuilder $container) + public function __construct(ContainerBuilder $container, string $env = null) { $this->container = $container; + parent::__construct($env); } /** @@ -35,7 +36,7 @@ class ClosureLoader extends Loader */ public function load($resource, string $type = null) { - $resource($this->container); + $resource($this->container, $this->env); } /** diff --git a/src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php b/src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php index 7c4377f7aa..5fc8f5b6c8 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php +++ b/src/Symfony/Component/DependencyInjection/Loader/Configurator/ContainerConfigurator.php @@ -35,14 +35,16 @@ class ContainerConfigurator extends AbstractConfigurator private $path; private $file; private $anonymousCount = 0; + private $env; - public function __construct(ContainerBuilder $container, PhpFileLoader $loader, array &$instanceof, string $path, string $file) + public function __construct(ContainerBuilder $container, PhpFileLoader $loader, array &$instanceof, string $path, string $file, string $env = null) { $this->container = $container; $this->loader = $loader; $this->instanceof = &$instanceof; $this->path = $path; $this->file = $file; + $this->env = $env; } final public function extension(string $namespace, array $config) @@ -71,6 +73,23 @@ class ContainerConfigurator extends AbstractConfigurator return new ServicesConfigurator($this->container, $this->loader, $this->instanceof, $this->path, $this->anonymousCount); } + /** + * @return static + */ + final public function when(string $env): self + { + if ($env === $this->env) { + return clone $this; + } + + $instanceof = $this->instanceof; + $clone = clone $this; + $clone->container = new ContainerBuilder(clone $this->container->getParameterBag()); + $clone->instanceof = &$instanceof; + + return $clone; + } + /** * @return static */ diff --git a/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php index b0d3e952e9..75606c5abf 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/FileLoader.php @@ -39,11 +39,11 @@ abstract class FileLoader extends BaseFileLoader protected $singlyImplemented = []; protected $autoRegisterAliasesForSinglyImplementedInterfaces = true; - public function __construct(ContainerBuilder $container, FileLocatorInterface $locator) + public function __construct(ContainerBuilder $container, FileLocatorInterface $locator, string $env = null) { $this->container = $container; - parent::__construct($locator); + parent::__construct($locator, $env); } /** diff --git a/src/Symfony/Component/DependencyInjection/Loader/IniFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/IniFileLoader.php index f313cbcae4..fbf313878b 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/IniFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/IniFileLoader.php @@ -44,6 +44,12 @@ class IniFileLoader extends FileLoader $this->container->setParameter($key, $this->phpize($value)); } } + + if ($this->env && \is_array($result['parameters@'.$this->env] ?? null)) { + foreach ($result['parameters@'.$this->env] as $key => $value) { + $this->container->setParameter($key, $this->phpize($value)); + } + } } /** diff --git a/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php index 5c7b628eab..130d7cfba7 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/PhpFileLoader.php @@ -47,7 +47,7 @@ class PhpFileLoader extends FileLoader $callback = $load($path); if (\is_object($callback) && \is_callable($callback)) { - $callback(new ContainerConfigurator($this->container, $this, $this->instanceof, $path, $resource), $this->container, $this); + $callback(new ContainerConfigurator($this->container, $this, $this->instanceof, $path, $resource, $this->env), $this->container, $this); } } finally { $this->instanceof = []; diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php index a5b42f8835..b694b7791c 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php @@ -50,23 +50,36 @@ class XmlFileLoader extends FileLoader $this->container->fileExists($path); - $defaults = $this->getServiceDefaults($xml, $path); + $this->loadXml($xml, $path); + + if ($this->env) { + $xpath = new \DOMXPath($xml); + $xpath->registerNamespace('container', self::NS); + foreach ($xpath->query(sprintf('//container:when[@env="%s"]', $this->env)) ?: [] as $root) { + $this->loadXml($xml, $path, $root); + } + } + } + + private function loadXml(\DOMDocument $xml, string $path, \DOMNode $root = null): void + { + $defaults = $this->getServiceDefaults($xml, $path, $root); // anonymous services - $this->processAnonymousServices($xml, $path); + $this->processAnonymousServices($xml, $path, $root); // imports - $this->parseImports($xml, $path); + $this->parseImports($xml, $path, $root); // parameters - $this->parseParameters($xml, $path); + $this->parseParameters($xml, $path, $root); // extensions - $this->loadFromExtensions($xml); + $this->loadFromExtensions($xml, $root); // services try { - $this->parseDefinitions($xml, $path, $defaults); + $this->parseDefinitions($xml, $path, $defaults, $root); } finally { $this->instanceof = []; $this->registerAliasesForSinglyImplementedInterfaces(); @@ -89,19 +102,19 @@ class XmlFileLoader extends FileLoader return 'xml' === $type; } - private function parseParameters(\DOMDocument $xml, string $file) + private function parseParameters(\DOMDocument $xml, string $file, \DOMNode $root = null) { - if ($parameters = $this->getChildren($xml->documentElement, 'parameters')) { + if ($parameters = $this->getChildren($root ?? $xml->documentElement, 'parameters')) { $this->container->getParameterBag()->add($this->getArgumentsAsPhp($parameters[0], 'parameter', $file)); } } - private function parseImports(\DOMDocument $xml, string $file) + private function parseImports(\DOMDocument $xml, string $file, \DOMNode $root = null) { $xpath = new \DOMXPath($xml); $xpath->registerNamespace('container', self::NS); - if (false === $imports = $xpath->query('//container:imports/container:import')) { + if (false === $imports = $xpath->query('.//container:imports/container:import', $root)) { return; } @@ -112,19 +125,19 @@ class XmlFileLoader extends FileLoader } } - private function parseDefinitions(\DOMDocument $xml, string $file, Definition $defaults) + private function parseDefinitions(\DOMDocument $xml, string $file, Definition $defaults, \DOMNode $root = null) { $xpath = new \DOMXPath($xml); $xpath->registerNamespace('container', self::NS); - if (false === $services = $xpath->query('//container:services/container:service|//container:services/container:prototype|//container:services/container:stack')) { + if (false === $services = $xpath->query('.//container:services/container:service|.//container:services/container:prototype|.//container:services/container:stack', $root)) { return; } $this->setCurrentDir(\dirname($file)); $this->instanceof = []; $this->isLoadingInstanceof = true; - $instanceof = $xpath->query('//container:services/container:instanceof'); + $instanceof = $xpath->query('.//container:services/container:instanceof', $root); foreach ($instanceof as $service) { $this->setDefinition((string) $service->getAttribute('id'), $this->parseDefinition($service, $file, new Definition())); } @@ -170,12 +183,12 @@ class XmlFileLoader extends FileLoader } } - private function getServiceDefaults(\DOMDocument $xml, string $file): Definition + private function getServiceDefaults(\DOMDocument $xml, string $file, \DOMNode $root = null): Definition { $xpath = new \DOMXPath($xml); $xpath->registerNamespace('container', self::NS); - if (null === $defaultsNode = $xpath->query('//container:services/container:defaults')->item(0)) { + if (null === $defaultsNode = $xpath->query('.//container:services/container:defaults', $root)->item(0)) { return new Definition(); } @@ -393,7 +406,7 @@ class XmlFileLoader extends FileLoader /** * Processes anonymous services. */ - private function processAnonymousServices(\DOMDocument $xml, string $file) + private function processAnonymousServices(\DOMDocument $xml, string $file, \DOMNode $root = null) { $definitions = []; $count = 0; @@ -403,7 +416,7 @@ class XmlFileLoader extends FileLoader $xpath->registerNamespace('container', self::NS); // anonymous services as arguments/properties - if (false !== $nodes = $xpath->query('//container:argument[@type="service"][not(@id)]|//container:property[@type="service"][not(@id)]|//container:bind[not(@id)]|//container:factory[not(@service)]|//container:configurator[not(@service)]')) { + if (false !== $nodes = $xpath->query('.//container:argument[@type="service"][not(@id)]|.//container:property[@type="service"][not(@id)]|.//container:bind[not(@id)]|.//container:factory[not(@service)]|.//container:configurator[not(@service)]', $root)) { foreach ($nodes as $node) { if ($services = $this->getChildren($node, 'service')) { // give it a unique name @@ -422,7 +435,7 @@ class XmlFileLoader extends FileLoader } // anonymous services "in the wild" - if (false !== $nodes = $xpath->query('//container:services/container:service[not(@id)]')) { + if (false !== $nodes = $xpath->query('.//container:services/container:service[not(@id)]', $root)) { foreach ($nodes as $node) { throw new InvalidArgumentException(sprintf('Top-level services must have "id" attribute, none found in "%s" at line %d.', $file, $node->getLineNo())); } diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index 22f029cca8..530f88d4b5 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php @@ -129,6 +129,20 @@ class YamlFileLoader extends FileLoader return; } + $this->loadContent($content, $path); + + // per-env configuration + if ($this->env && isset($content['when@'.$this->env])) { + if (!\is_array($content['when@'.$this->env])) { + throw new InvalidArgumentException(sprintf('The "when@%s" key should contain an array in "%s". Check your YAML syntax.', $this->env, $path)); + } + + $this->loadContent($content['when@'.$this->env], $path); + } + } + + private function loadContent($content, $path) + { // imports $this->parseImports($content, $path); @@ -770,7 +784,7 @@ class YamlFileLoader extends FileLoader } foreach ($content as $namespace => $data) { - if (\in_array($namespace, ['imports', 'parameters', 'services'])) { + if (\in_array($namespace, ['imports', 'parameters', 'services']) || 0 === strpos($namespace, 'when@')) { continue; } @@ -907,7 +921,7 @@ class YamlFileLoader extends FileLoader private function loadFromExtensions(array $content) { foreach ($content as $namespace => $values) { - if (\in_array($namespace, ['imports', 'parameters', 'services'])) { + if (\in_array($namespace, ['imports', 'parameters', 'services']) || 0 === strpos($namespace, 'when@')) { continue; } diff --git a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd index 03988686ba..f295f7e6f7 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd +++ b/src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd @@ -37,9 +37,31 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/when-env.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/when-env.php new file mode 100644 index 0000000000..5170261ad8 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/config/when-env.php @@ -0,0 +1,20 @@ +parameters() + ->set('foo', 123); + + $c->when('some-env') + ->parameters() + ->set('foo', 234) + ->set('bar', 345); + + $c->when('some-other-env') + ->parameters() + ->set('foo', 456) + ->set('baz', 567); +}; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/ini/when-env.ini b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/ini/when-env.ini new file mode 100644 index 0000000000..053fe86f86 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/ini/when-env.ini @@ -0,0 +1,10 @@ +[parameters@some-env] + foo = 234 + bar = 345 + +[parameters@some-other-env] + foo = 456 + baz = 567 + +[parameters] + foo = 123 diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/when-env.xml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/when-env.xml new file mode 100644 index 0000000000..1d12221a2f --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/when-env.xml @@ -0,0 +1,18 @@ + + + + 123 + + + + 234 + 345 + + + + + 456 + 567 + + + diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/when-env.yaml b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/when-env.yaml new file mode 100644 index 0000000000..22590d9b80 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/when-env.yaml @@ -0,0 +1,12 @@ +when@some-env: + parameters: + foo: 234 + bar: 345 + +when@some-other-env: + parameters: + foo: 456 + baz: 567 + +parameters: + foo: 123 diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/ClosureLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/ClosureLoaderTest.php index 125e09b6cf..a42ecf5a81 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/ClosureLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/ClosureLoaderTest.php @@ -27,12 +27,13 @@ class ClosureLoaderTest extends TestCase public function testLoad() { - $loader = new ClosureLoader($container = new ContainerBuilder()); + $loader = new ClosureLoader($container = new ContainerBuilder(), 'some-env'); - $loader->load(function ($container) { + $loader->load(function ($container, $env) { $container->setParameter('foo', 'foo'); + $container->setParameter('env', $env); }); - $this->assertEquals('foo', $container->getParameter('foo'), '->load() loads a \Closure resource'); + $this->assertSame(['foo' => 'foo', 'env' => 'some-env'], $container->getParameterBag()->all()); } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/IniFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/IniFileLoaderTest.php index b4cf503237..07f23f3137 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/IniFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/IniFileLoaderTest.php @@ -120,4 +120,13 @@ class IniFileLoaderTest extends TestCase $this->assertFalse($loader->supports('foo.foo'), '->supports() returns false if the resource is not loadable'); $this->assertTrue($loader->supports('with_wrong_ext.yml', 'ini'), '->supports() returns true if the resource with forced type is loadable'); } + + public function testWhenEnv() + { + $container = new ContainerBuilder(); + $loader = new IniFileLoader($container, new FileLocator(realpath(__DIR__.'/../Fixtures/').'/ini'), 'some-env'); + $loader->load('when-env.ini'); + + $this->assertSame(['foo' => 234, 'bar' => 345], $container->getParameterBag()->all()); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/PhpFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/PhpFileLoaderTest.php index bf53669cac..50f3c58dd8 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/PhpFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/PhpFileLoaderTest.php @@ -138,6 +138,15 @@ class PhpFileLoaderTest extends TestCase $this->assertEquals($expected, $container->get('stack_d')); } + public function testWhenEnv() + { + $container = new ContainerBuilder(); + $loader = new PhpFileLoader($container, new FileLocator(realpath(__DIR__.'/../Fixtures').'/config'), 'some-env'); + $loader->load('when-env.php'); + + $this->assertSame(['foo' => 234, 'bar' => 345], $container->getParameterBag()->all()); + } + /** * @group legacy */ diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php index 8714905f71..1e446d0ada 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/XmlFileLoaderTest.php @@ -1083,4 +1083,13 @@ class XmlFileLoaderTest extends TestCase $expected->label = 'Z'; $this->assertEquals($expected, $container->get('stack_d')); } + + public function testWhenEnv() + { + $container = new ContainerBuilder(); + $loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml'), 'some-env'); + $loader->load('when-env.xml'); + + $this->assertSame(['foo' => 234, 'bar' => 345], $container->getParameterBag()->all()); + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php index 508ecb8cb8..5c2b29559e 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php @@ -1002,4 +1002,13 @@ class YamlFileLoaderTest extends TestCase ]; $this->assertEquals($expected, $container->get('stack_e')); } + + public function testWhenEnv() + { + $container = new ContainerBuilder(); + $loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml'), 'some-env'); + $loader->load('when-env.yaml'); + + $this->assertSame(['foo' => 234, 'bar' => 345], $container->getParameterBag()->all()); + } } diff --git a/src/Symfony/Component/DependencyInjection/composer.json b/src/Symfony/Component/DependencyInjection/composer.json index c5752e46d0..efec2a3bc3 100644 --- a/src/Symfony/Component/DependencyInjection/composer.json +++ b/src/Symfony/Component/DependencyInjection/composer.json @@ -24,7 +24,7 @@ }, "require-dev": { "symfony/yaml": "^4.4|^5.0", - "symfony/config": "^5.1", + "symfony/config": "^5.3", "symfony/expression-language": "^4.4|^5.0" }, "suggest": { @@ -35,7 +35,7 @@ "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them" }, "conflict": { - "symfony/config": "<5.1", + "symfony/config": "<5.3", "symfony/finder": "<4.4", "symfony/proxy-manager-bridge": "<4.4", "symfony/yaml": "<4.4" diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 5ecad3b9a9..ad6a7e1ec0 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -751,15 +751,16 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl */ protected function getContainerLoader(ContainerInterface $container) { + $env = $this->getEnvironment(); $locator = new FileLocator($this); $resolver = new LoaderResolver([ - new XmlFileLoader($container, $locator), - new YamlFileLoader($container, $locator), - new IniFileLoader($container, $locator), - new PhpFileLoader($container, $locator), - new GlobFileLoader($container, $locator), - new DirectoryLoader($container, $locator), - new ClosureLoader($container), + new XmlFileLoader($container, $locator, $env), + new YamlFileLoader($container, $locator, $env), + new IniFileLoader($container, $locator, $env), + new PhpFileLoader($container, $locator, $env), + new GlobFileLoader($container, $locator, $env), + new DirectoryLoader($container, $locator, $env), + new ClosureLoader($container, $env), ]); return new DelegatingLoader($resolver); diff --git a/src/Symfony/Component/Routing/Annotation/Route.php b/src/Symfony/Component/Routing/Annotation/Route.php index f51b74c387..418887da26 100644 --- a/src/Symfony/Component/Routing/Annotation/Route.php +++ b/src/Symfony/Component/Routing/Annotation/Route.php @@ -34,6 +34,7 @@ class Route private $schemes = []; private $condition; private $priority; + private $env; /** * @param array|string $data data array managed by the Doctrine Annotations library or the path @@ -59,7 +60,8 @@ class Route string $locale = null, string $format = null, bool $utf8 = null, - bool $stateless = null + bool $stateless = null, + string $env = null ) { if (\is_string($data)) { $data = ['path' => $data]; @@ -84,6 +86,7 @@ class Route $data['format'] = $data['format'] ?? $format; $data['utf8'] = $data['utf8'] ?? $utf8; $data['stateless'] = $data['stateless'] ?? $stateless; + $data['env'] = $data['env'] ?? $env; $data = array_filter($data, static function ($value): bool { return null !== $value; @@ -241,4 +244,14 @@ class Route { return $this->priority; } + + public function setEnv(?string $env): void + { + $this->env = $env; + } + + public function getEnv(): ?string + { + return $this->env; + } } diff --git a/src/Symfony/Component/Routing/CHANGELOG.md b/src/Symfony/Component/Routing/CHANGELOG.md index 840df13441..b664e03aea 100644 --- a/src/Symfony/Component/Routing/CHANGELOG.md +++ b/src/Symfony/Component/Routing/CHANGELOG.md @@ -1,10 +1,11 @@ CHANGELOG ========= -5.3.0 ------ +5.3 +--- - * already encoded slashes are not decoded nor double-encoded anymore when generating URLs + * Already encoded slashes are not decoded nor double-encoded anymore when generating URLs + * Add support for per-env configuration in loaders 5.2.0 ----- diff --git a/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php b/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php index bf8fe2af36..0b362ad1e0 100644 --- a/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php +++ b/src/Symfony/Component/Routing/Loader/AnnotationClassLoader.php @@ -73,6 +73,7 @@ use Symfony\Component\Routing\RouteCollection; abstract class AnnotationClassLoader implements LoaderInterface { protected $reader; + protected $env; /** * @var string @@ -84,9 +85,10 @@ abstract class AnnotationClassLoader implements LoaderInterface */ protected $defaultRouteIndex = 0; - public function __construct(Reader $reader = null) + public function __construct(Reader $reader = null, string $env = null) { $this->reader = $reader; + $this->env = $env; } /** @@ -122,6 +124,10 @@ abstract class AnnotationClassLoader implements LoaderInterface $collection = new RouteCollection(); $collection->addResource(new FileResource($class->getFileName())); + if ($globals['env'] && $this->env !== $globals['env']) { + return $collection; + } + foreach ($class->getMethods() as $method) { $this->defaultRouteIndex = 0; foreach ($this->getAnnotations($method) as $annot) { @@ -144,6 +150,10 @@ abstract class AnnotationClassLoader implements LoaderInterface */ protected function addRoute(RouteCollection $collection, object $annot, array $globals, \ReflectionClass $class, \ReflectionMethod $method) { + if ($annot->getEnv() && $annot->getEnv() !== $this->env) { + return; + } + $name = $annot->getName(); if (null === $name) { $name = $this->getDefaultRouteName($class, $method); @@ -317,6 +327,7 @@ abstract class AnnotationClassLoader implements LoaderInterface } $globals['priority'] = $annot->getPriority() ?? 0; + $globals['env'] = $annot->getEnv(); foreach ($globals['requirements'] as $placeholder => $requirement) { if (\is_int($placeholder)) { @@ -342,6 +353,7 @@ abstract class AnnotationClassLoader implements LoaderInterface 'condition' => '', 'name' => '', 'priority' => 0, + 'env' => null, ]; } diff --git a/src/Symfony/Component/Routing/Loader/ClosureLoader.php b/src/Symfony/Component/Routing/Loader/ClosureLoader.php index cea5f9c194..2407307482 100644 --- a/src/Symfony/Component/Routing/Loader/ClosureLoader.php +++ b/src/Symfony/Component/Routing/Loader/ClosureLoader.php @@ -33,7 +33,7 @@ class ClosureLoader extends Loader */ public function load($closure, string $type = null) { - return $closure(); + return $closure($this->env); } /** diff --git a/src/Symfony/Component/Routing/Loader/Configurator/RoutingConfigurator.php b/src/Symfony/Component/Routing/Loader/Configurator/RoutingConfigurator.php index e5086e2441..70d68dfc3c 100644 --- a/src/Symfony/Component/Routing/Loader/Configurator/RoutingConfigurator.php +++ b/src/Symfony/Component/Routing/Loader/Configurator/RoutingConfigurator.php @@ -24,13 +24,15 @@ class RoutingConfigurator private $loader; private $path; private $file; + private $env; - public function __construct(RouteCollection $collection, PhpFileLoader $loader, string $path, string $file) + public function __construct(RouteCollection $collection, PhpFileLoader $loader, string $path, string $file, string $env = null) { $this->collection = $collection; $this->loader = $loader; $this->path = $path; $this->file = $file; + $this->env = $env; } /** @@ -58,6 +60,21 @@ class RoutingConfigurator return new CollectionConfigurator($this->collection, $name); } + /** + * @return static + */ + final public function when(string $env): self + { + if ($env === $this->env) { + return clone $this; + } + + $clone = clone $this; + $clone->collection = new RouteCollection(); + + return $clone; + } + /** * @return static */ diff --git a/src/Symfony/Component/Routing/Loader/ContainerLoader.php b/src/Symfony/Component/Routing/Loader/ContainerLoader.php index 92bf2a096b..8128b74213 100644 --- a/src/Symfony/Component/Routing/Loader/ContainerLoader.php +++ b/src/Symfony/Component/Routing/Loader/ContainerLoader.php @@ -22,9 +22,10 @@ class ContainerLoader extends ObjectLoader { private $container; - public function __construct(ContainerInterface $container) + public function __construct(ContainerInterface $container, string $env = null) { $this->container = $container; + parent::__construct($env); } /** diff --git a/src/Symfony/Component/Routing/Loader/ObjectLoader.php b/src/Symfony/Component/Routing/Loader/ObjectLoader.php index d6ec1a7277..062453908c 100644 --- a/src/Symfony/Component/Routing/Loader/ObjectLoader.php +++ b/src/Symfony/Component/Routing/Loader/ObjectLoader.php @@ -59,7 +59,7 @@ abstract class ObjectLoader extends Loader throw new \BadMethodCallException(sprintf('Method "%s" not found on "%s" when importing routing resource "%s".', $method, get_debug_type($loaderObject), $resource)); } - $routeCollection = $loaderObject->$method($this); + $routeCollection = $loaderObject->$method($this, $this->env); if (!$routeCollection instanceof RouteCollection) { $type = get_debug_type($routeCollection); diff --git a/src/Symfony/Component/Routing/Loader/PhpFileLoader.php b/src/Symfony/Component/Routing/Loader/PhpFileLoader.php index e000c5a0eb..2418b0d322 100644 --- a/src/Symfony/Component/Routing/Loader/PhpFileLoader.php +++ b/src/Symfony/Component/Routing/Loader/PhpFileLoader.php @@ -71,7 +71,7 @@ class PhpFileLoader extends FileLoader { $collection = new RouteCollection(); - $result(new RoutingConfigurator($collection, $this, $path, $file)); + $result(new RoutingConfigurator($collection, $this, $path, $file, $this->env)); return $collection; } diff --git a/src/Symfony/Component/Routing/Loader/XmlFileLoader.php b/src/Symfony/Component/Routing/Loader/XmlFileLoader.php index 9951625be9..4f38ce95cc 100644 --- a/src/Symfony/Component/Routing/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/Routing/Loader/XmlFileLoader.php @@ -88,6 +88,16 @@ class XmlFileLoader extends FileLoader case 'import': $this->parseImport($collection, $node, $path, $file); break; + case 'when': + if (!$this->env || $node->getAttribute('env') !== $this->env) { + break; + } + foreach ($node->childNodes as $node) { + if ($node instanceof \DOMElement) { + $this->parseNode($collection, $node, $path, $file); + } + } + break; default: throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "route" or "import".', $node->localName, $path)); } diff --git a/src/Symfony/Component/Routing/Loader/YamlFileLoader.php b/src/Symfony/Component/Routing/Loader/YamlFileLoader.php index b236e4b54e..1ec8a810a2 100644 --- a/src/Symfony/Component/Routing/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/Routing/Loader/YamlFileLoader.php @@ -84,6 +84,24 @@ class YamlFileLoader extends FileLoader } foreach ($parsedConfig as $name => $config) { + if (0 === strpos($name, 'when@')) { + if (!$this->env || 'when@'.$this->env !== $name) { + continue; + } + + foreach ($config as $name => $config) { + $this->validate($config, $name.'" when "@'.$this->env, $path); + + if (isset($config['resource'])) { + $this->parseImport($collection, $config, $path, $file); + } else { + $this->parseRoute($collection, $name, $config, $path); + } + } + + continue; + } + $this->validate($config, $name, $path); if (isset($config['resource'])) { diff --git a/src/Symfony/Component/Routing/Loader/schema/routing/routing-1.0.xsd b/src/Symfony/Component/Routing/Loader/schema/routing/routing-1.0.xsd index 846d126724..e03114791e 100644 --- a/src/Symfony/Component/Routing/Loader/schema/routing/routing-1.0.xsd +++ b/src/Symfony/Component/Routing/Loader/schema/routing/routing-1.0.xsd @@ -21,9 +21,18 @@ + + + + + + + + + diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/AnnotationFixtures/RouteWithEnv.php b/src/Symfony/Component/Routing/Tests/Fixtures/AnnotationFixtures/RouteWithEnv.php new file mode 100644 index 0000000000..dcc94e7a17 --- /dev/null +++ b/src/Symfony/Component/Routing/Tests/Fixtures/AnnotationFixtures/RouteWithEnv.php @@ -0,0 +1,25 @@ +when('some-env') + ->add('a', '/a2') + ->add('b', '/b'); + + $routes + ->when('some-other-env') + ->add('a', '/a3') + ->add('c', '/c'); + + $routes + ->add('a', '/a1'); +}; diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/when-env.xml b/src/Symfony/Component/Routing/Tests/Fixtures/when-env.xml new file mode 100644 index 0000000000..50d1fd8b39 --- /dev/null +++ b/src/Symfony/Component/Routing/Tests/Fixtures/when-env.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/when-env.yml b/src/Symfony/Component/Routing/Tests/Fixtures/when-env.yml new file mode 100644 index 0000000000..0f97ab22f2 --- /dev/null +++ b/src/Symfony/Component/Routing/Tests/Fixtures/when-env.yml @@ -0,0 +1,9 @@ +when@some-env: + a: {path: /a2} + b: {path: /b} + +when@some-other-env: + a: {path: /a3} + c: {path: /c} + +a: {path: /a1} diff --git a/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderTest.php index 3b82499c4f..a4f6e3e3a6 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderTest.php @@ -247,5 +247,16 @@ abstract class AnnotationClassLoaderTest extends TestCase $this->assertEquals('/prefix/path', $routes->get('action')->getPath()); } + public function testWhenEnv() + { + $routes = $this->loader->load($this->getNamespace().'\RouteWithEnv'); + $this->assertCount(0, $routes); + + $this->setUp('some-env'); + $routes = $this->loader->load($this->getNamespace().'\RouteWithEnv'); + $this->assertCount(1, $routes); + $this->assertSame('/path', $routes->get('action')->getPath()); + } + abstract protected function getNamespace(): string; } diff --git a/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderWithAnnotationsTest.php b/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderWithAnnotationsTest.php index ef9ca39e82..d2fe627ef9 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderWithAnnotationsTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderWithAnnotationsTest.php @@ -9,10 +9,10 @@ use Symfony\Component\Routing\Route; class AnnotationClassLoaderWithAnnotationsTest extends AnnotationClassLoaderTest { - protected function setUp(): void + protected function setUp(string $env = null): void { $reader = new AnnotationReader(); - $this->loader = new class($reader) extends AnnotationClassLoader { + $this->loader = new class($reader, $env) extends AnnotationClassLoader { protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, object $annot): void { } diff --git a/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderWithAttributesTest.php b/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderWithAttributesTest.php index 1545253e56..ea2a5c573b 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderWithAttributesTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/AnnotationClassLoaderWithAttributesTest.php @@ -10,9 +10,9 @@ use Symfony\Component\Routing\Route; */ class AnnotationClassLoaderWithAttributesTest extends AnnotationClassLoaderTest { - protected function setUp(): void + protected function setUp(string $env = null): void { - $this->loader = new class() extends AnnotationClassLoader { + $this->loader = new class(null, $env) extends AnnotationClassLoader { protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, object $annot): void { } diff --git a/src/Symfony/Component/Routing/Tests/Loader/ClosureLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/ClosureLoaderTest.php index 5d963f86fb..da8ad090dd 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/ClosureLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/ClosureLoaderTest.php @@ -33,10 +33,12 @@ class ClosureLoaderTest extends TestCase public function testLoad() { - $loader = new ClosureLoader(); + $loader = new ClosureLoader('some-env'); $route = new Route('/'); - $routes = $loader->load(function () use ($route) { + $routes = $loader->load(function (string $env = null) use ($route) { + $this->assertSame('some-env', $env); + $routes = new RouteCollection(); $routes->add('foo', $route); diff --git a/src/Symfony/Component/Routing/Tests/Loader/ObjectLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/ObjectLoaderTest.php index 50e75b5fd4..54d3643b1f 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/ObjectLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/ObjectLoaderTest.php @@ -20,14 +20,14 @@ class ObjectLoaderTest extends TestCase { public function testLoadCallsServiceAndReturnsCollection() { - $loader = new TestObjectLoader(); + $loader = new TestObjectLoader('some-env'); // create a basic collection that will be returned $collection = new RouteCollection(); $collection->add('foo', new Route('/foo')); $loader->loaderMap = [ - 'my_route_provider_service' => new TestObjectLoaderRouteService($collection), + 'my_route_provider_service' => new TestObjectLoaderRouteService($collection, 'some-env'), ]; $actualRoutes = $loader->load( @@ -112,14 +112,20 @@ class TestObjectLoader extends ObjectLoader class TestObjectLoaderRouteService { private $collection; + private $env; - public function __construct($collection) + public function __construct($collection, string $env = null) { $this->collection = $collection; + $this->env = $env; } - public function loadRoutes() + public function loadRoutes(TestObjectLoader $loader, string $env = null) { + if ($this->env !== $env) { + throw new \InvalidArgumentException(sprintf('Expected env "%s", "%s" given.', $this->env, $env)); + } + return $this->collection; } } diff --git a/src/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php index 7ed941c711..fd3fca4154 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/PhpFileLoaderTest.php @@ -284,4 +284,14 @@ class PhpFileLoaderTest extends TestCase $this->assertEquals($expectedRoutes('php'), $routes); } + + public function testWhenEnv() + { + $loader = new PhpFileLoader(new FileLocator([__DIR__.'/../Fixtures']), 'some-env'); + $routes = $loader->load('when-env.php'); + + $this->assertSame(['b', 'a'], array_keys($routes->all())); + $this->assertSame('/b', $routes->get('b')->getPath()); + $this->assertSame('/a1', $routes->get('a')->getPath()); + } } diff --git a/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php index 9ab49fc131..8518129a5b 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/XmlFileLoaderTest.php @@ -563,4 +563,14 @@ class XmlFileLoaderTest extends TestCase $this->assertEquals($expectedRoutes('xml'), $routes); } + + public function testWhenEnv() + { + $loader = new XmlFileLoader(new FileLocator([__DIR__.'/../Fixtures']), 'some-env'); + $routes = $loader->load('when-env.xml'); + + $this->assertSame(['b', 'a'], array_keys($routes->all())); + $this->assertSame('/b', $routes->get('b')->getPath()); + $this->assertSame('/a1', $routes->get('a')->getPath()); + } } diff --git a/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php index 56915c5ce5..09e3f07951 100644 --- a/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php +++ b/src/Symfony/Component/Routing/Tests/Loader/YamlFileLoaderTest.php @@ -435,4 +435,14 @@ class YamlFileLoaderTest extends TestCase $this->assertEquals($expectedRoutes('yml'), $routes); } + + public function testWhenEnv() + { + $loader = new YamlFileLoader(new FileLocator([__DIR__.'/../Fixtures']), 'some-env'); + $routes = $loader->load('when-env.yml'); + + $this->assertSame(['b', 'a'], array_keys($routes->all())); + $this->assertSame('/b', $routes->get('b')->getPath()); + $this->assertSame('/a1', $routes->get('a')->getPath()); + } } diff --git a/src/Symfony/Component/Routing/composer.json b/src/Symfony/Component/Routing/composer.json index 164dbb2bfd..11da29d649 100644 --- a/src/Symfony/Component/Routing/composer.json +++ b/src/Symfony/Component/Routing/composer.json @@ -21,7 +21,7 @@ "symfony/polyfill-php80": "^1.15" }, "require-dev": { - "symfony/config": "^5.0", + "symfony/config": "^5.3", "symfony/http-foundation": "^4.4|^5.0", "symfony/yaml": "^4.4|^5.0", "symfony/expression-language": "^4.4|^5.0", @@ -30,7 +30,7 @@ "psr/log": "~1.0" }, "conflict": { - "symfony/config": "<5.0", + "symfony/config": "<5.3", "symfony/dependency-injection": "<4.4", "symfony/yaml": "<4.4" },