feature #40214 [FrameworkBundle] allow container/routing configurators to vary by env (nicolas-grekas)
This PR was merged into the 5.3-dev branch.
Discussion
----------
[FrameworkBundle] allow container/routing configurators to vary by env
| Q | A
| ------------- | ---
| Branch? | 5.x
| Bug fix? | no
| New feature? | yes
| Deprecations? | no
| Tickets | #40215
| License | MIT
| Doc PR | -
Inspired by https://github.com/symfony/webpack-encore/pull/900 and by a chat on Slack with @weaverryan
This aims at allowing conditional configuration, which would allow merging config files in one.
Using the PHP-DSL:
```php
$container
->when(env: 'prod')
->services()
->set(Foo::class)
//...
```
In Yaml:
```yaml
framework:
secret: '%env(APP_SECRET)%'
when@dev:
services:
App\FooForDev: ~
when@test:
framework:
test: true
session:
storage_factory_id: session.storage.mock_file
```
In XML (omitting namespaces):
```xml
<when env="test">
<framework test="true">
<!-- ... -->
</framework>
</when>
```
A similar syntax is also provided for routes, with support for annotations:
`@Route(env="prod")` defines a route that is enabled only on the "prod" env.
Commits
-------
108375b068
[FrameworkBundle] allow container/routing configurators to vary by env
This commit is contained in:
commit
858dca485d
@ -1011,7 +1011,10 @@ class FrameworkExtension extends Extension
|
|||||||
$container->register('routing.loader.annotation', AnnotatedRouteControllerLoader::class)
|
$container->register('routing.loader.annotation', AnnotatedRouteControllerLoader::class)
|
||||||
->setPublic(false)
|
->setPublic(false)
|
||||||
->addTag('routing.loader', ['priority' => -10])
|
->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)
|
$container->register('routing.loader.annotation.directory', AnnotationDirectoryLoader::class)
|
||||||
->setPublic(false)
|
->setPublic(false)
|
||||||
|
@ -152,7 +152,7 @@ trait MicroKernelTrait
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->configureContainer(new ContainerConfigurator($container, $kernelLoader, $instanceof, $file, $file), $loader);
|
$this->configureContainer(new ContainerConfigurator($container, $kernelLoader, $instanceof, $file, $file, $this->getEnvironment()), $loader);
|
||||||
} finally {
|
} finally {
|
||||||
$instanceof = [];
|
$instanceof = [];
|
||||||
$kernelLoader->registerAliasesForSinglyImplementedInterfaces();
|
$kernelLoader->registerAliasesForSinglyImplementedInterfaces();
|
||||||
@ -193,7 +193,7 @@ trait MicroKernelTrait
|
|||||||
return $routes->build();
|
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) {
|
foreach ($collection as $route) {
|
||||||
$controller = $route->getDefault('_controller');
|
$controller = $route->getDefault('_controller');
|
||||||
|
@ -49,36 +49,42 @@ return static function (ContainerConfigurator $container) {
|
|||||||
->set('routing.loader.xml', XmlFileLoader::class)
|
->set('routing.loader.xml', XmlFileLoader::class)
|
||||||
->args([
|
->args([
|
||||||
service('file_locator'),
|
service('file_locator'),
|
||||||
|
'%kernel.environment%',
|
||||||
])
|
])
|
||||||
->tag('routing.loader')
|
->tag('routing.loader')
|
||||||
|
|
||||||
->set('routing.loader.yml', YamlFileLoader::class)
|
->set('routing.loader.yml', YamlFileLoader::class)
|
||||||
->args([
|
->args([
|
||||||
service('file_locator'),
|
service('file_locator'),
|
||||||
|
'%kernel.environment%',
|
||||||
])
|
])
|
||||||
->tag('routing.loader')
|
->tag('routing.loader')
|
||||||
|
|
||||||
->set('routing.loader.php', PhpFileLoader::class)
|
->set('routing.loader.php', PhpFileLoader::class)
|
||||||
->args([
|
->args([
|
||||||
service('file_locator'),
|
service('file_locator'),
|
||||||
|
'%kernel.environment%',
|
||||||
])
|
])
|
||||||
->tag('routing.loader')
|
->tag('routing.loader')
|
||||||
|
|
||||||
->set('routing.loader.glob', GlobFileLoader::class)
|
->set('routing.loader.glob', GlobFileLoader::class)
|
||||||
->args([
|
->args([
|
||||||
service('file_locator'),
|
service('file_locator'),
|
||||||
|
'%kernel.environment%',
|
||||||
])
|
])
|
||||||
->tag('routing.loader')
|
->tag('routing.loader')
|
||||||
|
|
||||||
->set('routing.loader.directory', DirectoryLoader::class)
|
->set('routing.loader.directory', DirectoryLoader::class)
|
||||||
->args([
|
->args([
|
||||||
service('file_locator'),
|
service('file_locator'),
|
||||||
|
'%kernel.environment%',
|
||||||
])
|
])
|
||||||
->tag('routing.loader')
|
->tag('routing.loader')
|
||||||
|
|
||||||
->set('routing.loader.container', ContainerLoader::class)
|
->set('routing.loader.container', ContainerLoader::class)
|
||||||
->args([
|
->args([
|
||||||
tagged_locator('routing.route_loader'),
|
tagged_locator('routing.route_loader'),
|
||||||
|
'%kernel.environment%',
|
||||||
])
|
])
|
||||||
->tag('routing.loader')
|
->tag('routing.loader')
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
"php": ">=7.2.5",
|
"php": ">=7.2.5",
|
||||||
"ext-xml": "*",
|
"ext-xml": "*",
|
||||||
"symfony/cache": "^5.2",
|
"symfony/cache": "^5.2",
|
||||||
"symfony/config": "^5.0",
|
"symfony/config": "^5.3",
|
||||||
"symfony/dependency-injection": "^5.3",
|
"symfony/dependency-injection": "^5.3",
|
||||||
"symfony/deprecation-contracts": "^2.1",
|
"symfony/deprecation-contracts": "^2.1",
|
||||||
"symfony/event-dispatcher": "^5.1",
|
"symfony/event-dispatcher": "^5.1",
|
||||||
@ -30,7 +30,7 @@
|
|||||||
"symfony/polyfill-php80": "^1.15",
|
"symfony/polyfill-php80": "^1.15",
|
||||||
"symfony/filesystem": "^4.4|^5.0",
|
"symfony/filesystem": "^4.4|^5.0",
|
||||||
"symfony/finder": "^4.4|^5.0",
|
"symfony/finder": "^4.4|^5.0",
|
||||||
"symfony/routing": "^5.2"
|
"symfony/routing": "^5.3"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"doctrine/annotations": "^1.10.4",
|
"doctrine/annotations": "^1.10.4",
|
||||||
|
@ -31,9 +31,10 @@ abstract class FileLoader extends Loader
|
|||||||
|
|
||||||
private $currentDir;
|
private $currentDir;
|
||||||
|
|
||||||
public function __construct(FileLocatorInterface $locator)
|
public function __construct(FileLocatorInterface $locator, string $env = null)
|
||||||
{
|
{
|
||||||
$this->locator = $locator;
|
$this->locator = $locator;
|
||||||
|
parent::__construct($env);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -21,6 +21,12 @@ use Symfony\Component\Config\Exception\LoaderLoadException;
|
|||||||
abstract class Loader implements LoaderInterface
|
abstract class Loader implements LoaderInterface
|
||||||
{
|
{
|
||||||
protected $resolver;
|
protected $resolver;
|
||||||
|
protected $env;
|
||||||
|
|
||||||
|
public function __construct(string $env = null)
|
||||||
|
{
|
||||||
|
$this->env = $env;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
|
@ -8,6 +8,7 @@ CHANGELOG
|
|||||||
* Add `%env(not:...)%` processor to negate boolean values
|
* Add `%env(not:...)%` processor to negate boolean values
|
||||||
* Add support for loading autoconfiguration rules via the `#[Autoconfigure]` and `#[AutoconfigureTag]` attributes on PHP 8
|
* Add support for loading autoconfiguration rules via the `#[Autoconfigure]` and `#[AutoconfigureTag]` attributes on PHP 8
|
||||||
* Add autoconfigurable attributes
|
* Add autoconfigurable attributes
|
||||||
|
* Add support for per-env configuration in loaders
|
||||||
|
|
||||||
5.2.0
|
5.2.0
|
||||||
-----
|
-----
|
||||||
|
@ -25,9 +25,10 @@ class ClosureLoader extends Loader
|
|||||||
{
|
{
|
||||||
private $container;
|
private $container;
|
||||||
|
|
||||||
public function __construct(ContainerBuilder $container)
|
public function __construct(ContainerBuilder $container, string $env = null)
|
||||||
{
|
{
|
||||||
$this->container = $container;
|
$this->container = $container;
|
||||||
|
parent::__construct($env);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -35,7 +36,7 @@ class ClosureLoader extends Loader
|
|||||||
*/
|
*/
|
||||||
public function load($resource, string $type = null)
|
public function load($resource, string $type = null)
|
||||||
{
|
{
|
||||||
$resource($this->container);
|
$resource($this->container, $this->env);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,14 +35,16 @@ class ContainerConfigurator extends AbstractConfigurator
|
|||||||
private $path;
|
private $path;
|
||||||
private $file;
|
private $file;
|
||||||
private $anonymousCount = 0;
|
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->container = $container;
|
||||||
$this->loader = $loader;
|
$this->loader = $loader;
|
||||||
$this->instanceof = &$instanceof;
|
$this->instanceof = &$instanceof;
|
||||||
$this->path = $path;
|
$this->path = $path;
|
||||||
$this->file = $file;
|
$this->file = $file;
|
||||||
|
$this->env = $env;
|
||||||
}
|
}
|
||||||
|
|
||||||
final public function extension(string $namespace, array $config)
|
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 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
|
* @return static
|
||||||
*/
|
*/
|
||||||
|
@ -39,11 +39,11 @@ abstract class FileLoader extends BaseFileLoader
|
|||||||
protected $singlyImplemented = [];
|
protected $singlyImplemented = [];
|
||||||
protected $autoRegisterAliasesForSinglyImplementedInterfaces = true;
|
protected $autoRegisterAliasesForSinglyImplementedInterfaces = true;
|
||||||
|
|
||||||
public function __construct(ContainerBuilder $container, FileLocatorInterface $locator)
|
public function __construct(ContainerBuilder $container, FileLocatorInterface $locator, string $env = null)
|
||||||
{
|
{
|
||||||
$this->container = $container;
|
$this->container = $container;
|
||||||
|
|
||||||
parent::__construct($locator);
|
parent::__construct($locator, $env);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -44,6 +44,12 @@ class IniFileLoader extends FileLoader
|
|||||||
$this->container->setParameter($key, $this->phpize($value));
|
$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));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -47,7 +47,7 @@ class PhpFileLoader extends FileLoader
|
|||||||
$callback = $load($path);
|
$callback = $load($path);
|
||||||
|
|
||||||
if (\is_object($callback) && \is_callable($callback)) {
|
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 {
|
} finally {
|
||||||
$this->instanceof = [];
|
$this->instanceof = [];
|
||||||
|
@ -50,23 +50,36 @@ class XmlFileLoader extends FileLoader
|
|||||||
|
|
||||||
$this->container->fileExists($path);
|
$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
|
// anonymous services
|
||||||
$this->processAnonymousServices($xml, $path);
|
$this->processAnonymousServices($xml, $path, $root);
|
||||||
|
|
||||||
// imports
|
// imports
|
||||||
$this->parseImports($xml, $path);
|
$this->parseImports($xml, $path, $root);
|
||||||
|
|
||||||
// parameters
|
// parameters
|
||||||
$this->parseParameters($xml, $path);
|
$this->parseParameters($xml, $path, $root);
|
||||||
|
|
||||||
// extensions
|
// extensions
|
||||||
$this->loadFromExtensions($xml);
|
$this->loadFromExtensions($xml, $root);
|
||||||
|
|
||||||
// services
|
// services
|
||||||
try {
|
try {
|
||||||
$this->parseDefinitions($xml, $path, $defaults);
|
$this->parseDefinitions($xml, $path, $defaults, $root);
|
||||||
} finally {
|
} finally {
|
||||||
$this->instanceof = [];
|
$this->instanceof = [];
|
||||||
$this->registerAliasesForSinglyImplementedInterfaces();
|
$this->registerAliasesForSinglyImplementedInterfaces();
|
||||||
@ -89,19 +102,19 @@ class XmlFileLoader extends FileLoader
|
|||||||
return 'xml' === $type;
|
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));
|
$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 = new \DOMXPath($xml);
|
||||||
$xpath->registerNamespace('container', self::NS);
|
$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;
|
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 = new \DOMXPath($xml);
|
||||||
$xpath->registerNamespace('container', self::NS);
|
$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;
|
return;
|
||||||
}
|
}
|
||||||
$this->setCurrentDir(\dirname($file));
|
$this->setCurrentDir(\dirname($file));
|
||||||
|
|
||||||
$this->instanceof = [];
|
$this->instanceof = [];
|
||||||
$this->isLoadingInstanceof = true;
|
$this->isLoadingInstanceof = true;
|
||||||
$instanceof = $xpath->query('//container:services/container:instanceof');
|
$instanceof = $xpath->query('.//container:services/container:instanceof', $root);
|
||||||
foreach ($instanceof as $service) {
|
foreach ($instanceof as $service) {
|
||||||
$this->setDefinition((string) $service->getAttribute('id'), $this->parseDefinition($service, $file, new Definition()));
|
$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 = new \DOMXPath($xml);
|
||||||
$xpath->registerNamespace('container', self::NS);
|
$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();
|
return new Definition();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,7 +406,7 @@ class XmlFileLoader extends FileLoader
|
|||||||
/**
|
/**
|
||||||
* Processes anonymous services.
|
* Processes anonymous services.
|
||||||
*/
|
*/
|
||||||
private function processAnonymousServices(\DOMDocument $xml, string $file)
|
private function processAnonymousServices(\DOMDocument $xml, string $file, \DOMNode $root = null)
|
||||||
{
|
{
|
||||||
$definitions = [];
|
$definitions = [];
|
||||||
$count = 0;
|
$count = 0;
|
||||||
@ -403,7 +416,7 @@ class XmlFileLoader extends FileLoader
|
|||||||
$xpath->registerNamespace('container', self::NS);
|
$xpath->registerNamespace('container', self::NS);
|
||||||
|
|
||||||
// anonymous services as arguments/properties
|
// 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) {
|
foreach ($nodes as $node) {
|
||||||
if ($services = $this->getChildren($node, 'service')) {
|
if ($services = $this->getChildren($node, 'service')) {
|
||||||
// give it a unique name
|
// give it a unique name
|
||||||
@ -422,7 +435,7 @@ class XmlFileLoader extends FileLoader
|
|||||||
}
|
}
|
||||||
|
|
||||||
// anonymous services "in the wild"
|
// 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) {
|
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()));
|
throw new InvalidArgumentException(sprintf('Top-level services must have "id" attribute, none found in "%s" at line %d.', $file, $node->getLineNo()));
|
||||||
}
|
}
|
||||||
|
@ -129,6 +129,20 @@ class YamlFileLoader extends FileLoader
|
|||||||
return;
|
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
|
// imports
|
||||||
$this->parseImports($content, $path);
|
$this->parseImports($content, $path);
|
||||||
|
|
||||||
@ -770,7 +784,7 @@ class YamlFileLoader extends FileLoader
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach ($content as $namespace => $data) {
|
foreach ($content as $namespace => $data) {
|
||||||
if (\in_array($namespace, ['imports', 'parameters', 'services'])) {
|
if (\in_array($namespace, ['imports', 'parameters', 'services']) || 0 === strpos($namespace, 'when@')) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -907,7 +921,7 @@ class YamlFileLoader extends FileLoader
|
|||||||
private function loadFromExtensions(array $content)
|
private function loadFromExtensions(array $content)
|
||||||
{
|
{
|
||||||
foreach ($content as $namespace => $values) {
|
foreach ($content as $namespace => $values) {
|
||||||
if (\in_array($namespace, ['imports', 'parameters', 'services'])) {
|
if (\in_array($namespace, ['imports', 'parameters', 'services']) || 0 === strpos($namespace, 'when@')) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,9 +37,31 @@
|
|||||||
<xsd:element name="services" type="services" />
|
<xsd:element name="services" type="services" />
|
||||||
<xsd:group ref="foreign" />
|
<xsd:group ref="foreign" />
|
||||||
</xsd:sequence>
|
</xsd:sequence>
|
||||||
|
<xsd:sequence minOccurs="0" maxOccurs="unbounded">
|
||||||
|
<xsd:element name="when" type="when" />
|
||||||
|
</xsd:sequence>
|
||||||
</xsd:sequence>
|
</xsd:sequence>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
|
|
||||||
|
<xsd:complexType name="when">
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:group ref="foreign" />
|
||||||
|
<xsd:sequence minOccurs="0">
|
||||||
|
<xsd:element name="imports" type="imports" />
|
||||||
|
<xsd:group ref="foreign" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:sequence minOccurs="0">
|
||||||
|
<xsd:element name="parameters" type="parameters" />
|
||||||
|
<xsd:group ref="foreign" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:sequence minOccurs="0">
|
||||||
|
<xsd:element name="services" type="services" />
|
||||||
|
<xsd:group ref="foreign" />
|
||||||
|
</xsd:sequence>
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="env" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
|
||||||
<xsd:group name="foreign">
|
<xsd:group name="foreign">
|
||||||
<xsd:sequence>
|
<xsd:sequence>
|
||||||
<xsd:any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded" />
|
<xsd:any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded" />
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
|
||||||
|
|
||||||
|
use App\BarService;
|
||||||
|
|
||||||
|
return function (ContainerConfigurator $c) {
|
||||||
|
$c->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);
|
||||||
|
};
|
@ -0,0 +1,10 @@
|
|||||||
|
[parameters@some-env]
|
||||||
|
foo = 234
|
||||||
|
bar = 345
|
||||||
|
|
||||||
|
[parameters@some-other-env]
|
||||||
|
foo = 456
|
||||||
|
baz = 567
|
||||||
|
|
||||||
|
[parameters]
|
||||||
|
foo = 123
|
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||||
|
<parameters>
|
||||||
|
<parameter key="foo">123</parameter>
|
||||||
|
</parameters>
|
||||||
|
<when env="some-env">
|
||||||
|
<parameters>
|
||||||
|
<parameter key="foo">234</parameter>
|
||||||
|
<parameter key="bar">345</parameter>
|
||||||
|
</parameters>
|
||||||
|
</when>
|
||||||
|
<when env="some-other-env">
|
||||||
|
<parameters>
|
||||||
|
<parameter key="foo">456</parameter>
|
||||||
|
<parameter key="baz">567</parameter>
|
||||||
|
</parameters>
|
||||||
|
</when>
|
||||||
|
</container>
|
@ -0,0 +1,12 @@
|
|||||||
|
when@some-env:
|
||||||
|
parameters:
|
||||||
|
foo: 234
|
||||||
|
bar: 345
|
||||||
|
|
||||||
|
when@some-other-env:
|
||||||
|
parameters:
|
||||||
|
foo: 456
|
||||||
|
baz: 567
|
||||||
|
|
||||||
|
parameters:
|
||||||
|
foo: 123
|
@ -27,12 +27,13 @@ class ClosureLoaderTest extends TestCase
|
|||||||
|
|
||||||
public function testLoad()
|
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('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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,4 +120,13 @@ class IniFileLoaderTest extends TestCase
|
|||||||
$this->assertFalse($loader->supports('foo.foo'), '->supports() returns false if the resource is not loadable');
|
$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');
|
$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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,6 +138,15 @@ class PhpFileLoaderTest extends TestCase
|
|||||||
$this->assertEquals($expected, $container->get('stack_d'));
|
$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
|
* @group legacy
|
||||||
*/
|
*/
|
||||||
|
@ -1083,4 +1083,13 @@ class XmlFileLoaderTest extends TestCase
|
|||||||
$expected->label = 'Z';
|
$expected->label = 'Z';
|
||||||
$this->assertEquals($expected, $container->get('stack_d'));
|
$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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1002,4 +1002,13 @@ class YamlFileLoaderTest extends TestCase
|
|||||||
];
|
];
|
||||||
$this->assertEquals($expected, $container->get('stack_e'));
|
$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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"symfony/yaml": "^4.4|^5.0",
|
"symfony/yaml": "^4.4|^5.0",
|
||||||
"symfony/config": "^5.1",
|
"symfony/config": "^5.3",
|
||||||
"symfony/expression-language": "^4.4|^5.0"
|
"symfony/expression-language": "^4.4|^5.0"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
@ -35,7 +35,7 @@
|
|||||||
"symfony/proxy-manager-bridge": "Generate service proxies to lazy load them"
|
"symfony/proxy-manager-bridge": "Generate service proxies to lazy load them"
|
||||||
},
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
"symfony/config": "<5.1",
|
"symfony/config": "<5.3",
|
||||||
"symfony/finder": "<4.4",
|
"symfony/finder": "<4.4",
|
||||||
"symfony/proxy-manager-bridge": "<4.4",
|
"symfony/proxy-manager-bridge": "<4.4",
|
||||||
"symfony/yaml": "<4.4"
|
"symfony/yaml": "<4.4"
|
||||||
|
@ -751,15 +751,16 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
|
|||||||
*/
|
*/
|
||||||
protected function getContainerLoader(ContainerInterface $container)
|
protected function getContainerLoader(ContainerInterface $container)
|
||||||
{
|
{
|
||||||
|
$env = $this->getEnvironment();
|
||||||
$locator = new FileLocator($this);
|
$locator = new FileLocator($this);
|
||||||
$resolver = new LoaderResolver([
|
$resolver = new LoaderResolver([
|
||||||
new XmlFileLoader($container, $locator),
|
new XmlFileLoader($container, $locator, $env),
|
||||||
new YamlFileLoader($container, $locator),
|
new YamlFileLoader($container, $locator, $env),
|
||||||
new IniFileLoader($container, $locator),
|
new IniFileLoader($container, $locator, $env),
|
||||||
new PhpFileLoader($container, $locator),
|
new PhpFileLoader($container, $locator, $env),
|
||||||
new GlobFileLoader($container, $locator),
|
new GlobFileLoader($container, $locator, $env),
|
||||||
new DirectoryLoader($container, $locator),
|
new DirectoryLoader($container, $locator, $env),
|
||||||
new ClosureLoader($container),
|
new ClosureLoader($container, $env),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return new DelegatingLoader($resolver);
|
return new DelegatingLoader($resolver);
|
||||||
|
@ -34,6 +34,7 @@ class Route
|
|||||||
private $schemes = [];
|
private $schemes = [];
|
||||||
private $condition;
|
private $condition;
|
||||||
private $priority;
|
private $priority;
|
||||||
|
private $env;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array|string $data data array managed by the Doctrine Annotations library or the path
|
* @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 $locale = null,
|
||||||
string $format = null,
|
string $format = null,
|
||||||
bool $utf8 = null,
|
bool $utf8 = null,
|
||||||
bool $stateless = null
|
bool $stateless = null,
|
||||||
|
string $env = null
|
||||||
) {
|
) {
|
||||||
if (\is_string($data)) {
|
if (\is_string($data)) {
|
||||||
$data = ['path' => $data];
|
$data = ['path' => $data];
|
||||||
@ -84,6 +86,7 @@ class Route
|
|||||||
$data['format'] = $data['format'] ?? $format;
|
$data['format'] = $data['format'] ?? $format;
|
||||||
$data['utf8'] = $data['utf8'] ?? $utf8;
|
$data['utf8'] = $data['utf8'] ?? $utf8;
|
||||||
$data['stateless'] = $data['stateless'] ?? $stateless;
|
$data['stateless'] = $data['stateless'] ?? $stateless;
|
||||||
|
$data['env'] = $data['env'] ?? $env;
|
||||||
|
|
||||||
$data = array_filter($data, static function ($value): bool {
|
$data = array_filter($data, static function ($value): bool {
|
||||||
return null !== $value;
|
return null !== $value;
|
||||||
@ -241,4 +244,14 @@ class Route
|
|||||||
{
|
{
|
||||||
return $this->priority;
|
return $this->priority;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setEnv(?string $env): void
|
||||||
|
{
|
||||||
|
$this->env = $env;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEnv(): ?string
|
||||||
|
{
|
||||||
|
return $this->env;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
CHANGELOG
|
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
|
5.2.0
|
||||||
-----
|
-----
|
||||||
|
@ -73,6 +73,7 @@ use Symfony\Component\Routing\RouteCollection;
|
|||||||
abstract class AnnotationClassLoader implements LoaderInterface
|
abstract class AnnotationClassLoader implements LoaderInterface
|
||||||
{
|
{
|
||||||
protected $reader;
|
protected $reader;
|
||||||
|
protected $env;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
@ -84,9 +85,10 @@ abstract class AnnotationClassLoader implements LoaderInterface
|
|||||||
*/
|
*/
|
||||||
protected $defaultRouteIndex = 0;
|
protected $defaultRouteIndex = 0;
|
||||||
|
|
||||||
public function __construct(Reader $reader = null)
|
public function __construct(Reader $reader = null, string $env = null)
|
||||||
{
|
{
|
||||||
$this->reader = $reader;
|
$this->reader = $reader;
|
||||||
|
$this->env = $env;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -122,6 +124,10 @@ abstract class AnnotationClassLoader implements LoaderInterface
|
|||||||
$collection = new RouteCollection();
|
$collection = new RouteCollection();
|
||||||
$collection->addResource(new FileResource($class->getFileName()));
|
$collection->addResource(new FileResource($class->getFileName()));
|
||||||
|
|
||||||
|
if ($globals['env'] && $this->env !== $globals['env']) {
|
||||||
|
return $collection;
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($class->getMethods() as $method) {
|
foreach ($class->getMethods() as $method) {
|
||||||
$this->defaultRouteIndex = 0;
|
$this->defaultRouteIndex = 0;
|
||||||
foreach ($this->getAnnotations($method) as $annot) {
|
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)
|
protected function addRoute(RouteCollection $collection, object $annot, array $globals, \ReflectionClass $class, \ReflectionMethod $method)
|
||||||
{
|
{
|
||||||
|
if ($annot->getEnv() && $annot->getEnv() !== $this->env) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$name = $annot->getName();
|
$name = $annot->getName();
|
||||||
if (null === $name) {
|
if (null === $name) {
|
||||||
$name = $this->getDefaultRouteName($class, $method);
|
$name = $this->getDefaultRouteName($class, $method);
|
||||||
@ -317,6 +327,7 @@ abstract class AnnotationClassLoader implements LoaderInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
$globals['priority'] = $annot->getPriority() ?? 0;
|
$globals['priority'] = $annot->getPriority() ?? 0;
|
||||||
|
$globals['env'] = $annot->getEnv();
|
||||||
|
|
||||||
foreach ($globals['requirements'] as $placeholder => $requirement) {
|
foreach ($globals['requirements'] as $placeholder => $requirement) {
|
||||||
if (\is_int($placeholder)) {
|
if (\is_int($placeholder)) {
|
||||||
@ -342,6 +353,7 @@ abstract class AnnotationClassLoader implements LoaderInterface
|
|||||||
'condition' => '',
|
'condition' => '',
|
||||||
'name' => '',
|
'name' => '',
|
||||||
'priority' => 0,
|
'priority' => 0,
|
||||||
|
'env' => null,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ class ClosureLoader extends Loader
|
|||||||
*/
|
*/
|
||||||
public function load($closure, string $type = null)
|
public function load($closure, string $type = null)
|
||||||
{
|
{
|
||||||
return $closure();
|
return $closure($this->env);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -24,13 +24,15 @@ class RoutingConfigurator
|
|||||||
private $loader;
|
private $loader;
|
||||||
private $path;
|
private $path;
|
||||||
private $file;
|
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->collection = $collection;
|
||||||
$this->loader = $loader;
|
$this->loader = $loader;
|
||||||
$this->path = $path;
|
$this->path = $path;
|
||||||
$this->file = $file;
|
$this->file = $file;
|
||||||
|
$this->env = $env;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -58,6 +60,21 @@ class RoutingConfigurator
|
|||||||
return new CollectionConfigurator($this->collection, $name);
|
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
|
* @return static
|
||||||
*/
|
*/
|
||||||
|
@ -22,9 +22,10 @@ class ContainerLoader extends ObjectLoader
|
|||||||
{
|
{
|
||||||
private $container;
|
private $container;
|
||||||
|
|
||||||
public function __construct(ContainerInterface $container)
|
public function __construct(ContainerInterface $container, string $env = null)
|
||||||
{
|
{
|
||||||
$this->container = $container;
|
$this->container = $container;
|
||||||
|
parent::__construct($env);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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));
|
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) {
|
if (!$routeCollection instanceof RouteCollection) {
|
||||||
$type = get_debug_type($routeCollection);
|
$type = get_debug_type($routeCollection);
|
||||||
|
@ -71,7 +71,7 @@ class PhpFileLoader extends FileLoader
|
|||||||
{
|
{
|
||||||
$collection = new RouteCollection();
|
$collection = new RouteCollection();
|
||||||
|
|
||||||
$result(new RoutingConfigurator($collection, $this, $path, $file));
|
$result(new RoutingConfigurator($collection, $this, $path, $file, $this->env));
|
||||||
|
|
||||||
return $collection;
|
return $collection;
|
||||||
}
|
}
|
||||||
|
@ -88,6 +88,16 @@ class XmlFileLoader extends FileLoader
|
|||||||
case 'import':
|
case 'import':
|
||||||
$this->parseImport($collection, $node, $path, $file);
|
$this->parseImport($collection, $node, $path, $file);
|
||||||
break;
|
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:
|
default:
|
||||||
throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "route" or "import".', $node->localName, $path));
|
throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "route" or "import".', $node->localName, $path));
|
||||||
}
|
}
|
||||||
|
@ -84,6 +84,24 @@ class YamlFileLoader extends FileLoader
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach ($parsedConfig as $name => $config) {
|
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);
|
$this->validate($config, $name, $path);
|
||||||
|
|
||||||
if (isset($config['resource'])) {
|
if (isset($config['resource'])) {
|
||||||
|
@ -21,9 +21,18 @@
|
|||||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||||
<xsd:element name="import" type="import" />
|
<xsd:element name="import" type="import" />
|
||||||
<xsd:element name="route" type="route" />
|
<xsd:element name="route" type="route" />
|
||||||
|
<xsd:element name="when" type="when" />
|
||||||
</xsd:choice>
|
</xsd:choice>
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
|
|
||||||
|
<xsd:complexType name="when">
|
||||||
|
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||||
|
<xsd:element name="import" type="import" />
|
||||||
|
<xsd:element name="route" type="route" />
|
||||||
|
</xsd:choice>
|
||||||
|
<xsd:attribute name="env" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
|
||||||
<xsd:complexType name="localized-path">
|
<xsd:complexType name="localized-path">
|
||||||
<xsd:simpleContent>
|
<xsd:simpleContent>
|
||||||
<xsd:extension base="xsd:string">
|
<xsd:extension base="xsd:string">
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\Routing\Tests\Fixtures\AnnotationFixtures;
|
||||||
|
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route(env="some-env")
|
||||||
|
*/
|
||||||
|
class RouteWithEnv
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Route("/path", name="action")
|
||||||
|
*/
|
||||||
|
public function action()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/path2", name="action2", env="some-other-env")
|
||||||
|
*/
|
||||||
|
public function action2()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\Routing\Tests\Fixtures\AttributeFixtures;
|
||||||
|
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
#[Route(env: 'some-env')]
|
||||||
|
class RouteWithEnv
|
||||||
|
{
|
||||||
|
#[Route(path: '/path', name: 'action')]
|
||||||
|
public function action()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route(path: '/path2', name: 'action2', env: 'some-other-env')]
|
||||||
|
public function action2()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
18
src/Symfony/Component/Routing/Tests/Fixtures/when-env.php
Normal file
18
src/Symfony/Component/Routing/Tests/Fixtures/when-env.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\Routing\Loader\Configurator;
|
||||||
|
|
||||||
|
return function (RoutingConfigurator $routes) {
|
||||||
|
$routes
|
||||||
|
->when('some-env')
|
||||||
|
->add('a', '/a2')
|
||||||
|
->add('b', '/b');
|
||||||
|
|
||||||
|
$routes
|
||||||
|
->when('some-other-env')
|
||||||
|
->add('a', '/a3')
|
||||||
|
->add('c', '/c');
|
||||||
|
|
||||||
|
$routes
|
||||||
|
->add('a', '/a1');
|
||||||
|
};
|
17
src/Symfony/Component/Routing/Tests/Fixtures/when-env.xml
Normal file
17
src/Symfony/Component/Routing/Tests/Fixtures/when-env.xml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<routes xmlns="http://symfony.com/schema/routing"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://symfony.com/schema/routing
|
||||||
|
https://symfony.com/schema/routing/routing-1.0.xsd">
|
||||||
|
<when env="some-env">
|
||||||
|
<route id="a" path="/a2" />
|
||||||
|
<route id="b" path="/b" />
|
||||||
|
</when>
|
||||||
|
|
||||||
|
<when env="some-other-env">
|
||||||
|
<route id="a" path="/a3" />
|
||||||
|
<route id="c" path="/c" />
|
||||||
|
</when>
|
||||||
|
|
||||||
|
<route id="a" path="/a1" />
|
||||||
|
</routes>
|
@ -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}
|
@ -247,5 +247,16 @@ abstract class AnnotationClassLoaderTest extends TestCase
|
|||||||
$this->assertEquals('/prefix/path', $routes->get('action')->getPath());
|
$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;
|
abstract protected function getNamespace(): string;
|
||||||
}
|
}
|
||||||
|
@ -9,10 +9,10 @@ use Symfony\Component\Routing\Route;
|
|||||||
|
|
||||||
class AnnotationClassLoaderWithAnnotationsTest extends AnnotationClassLoaderTest
|
class AnnotationClassLoaderWithAnnotationsTest extends AnnotationClassLoaderTest
|
||||||
{
|
{
|
||||||
protected function setUp(): void
|
protected function setUp(string $env = null): void
|
||||||
{
|
{
|
||||||
$reader = new AnnotationReader();
|
$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
|
protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, object $annot): void
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -10,9 +10,9 @@ use Symfony\Component\Routing\Route;
|
|||||||
*/
|
*/
|
||||||
class AnnotationClassLoaderWithAttributesTest extends AnnotationClassLoaderTest
|
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
|
protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, object $annot): void
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -33,10 +33,12 @@ class ClosureLoaderTest extends TestCase
|
|||||||
|
|
||||||
public function testLoad()
|
public function testLoad()
|
||||||
{
|
{
|
||||||
$loader = new ClosureLoader();
|
$loader = new ClosureLoader('some-env');
|
||||||
|
|
||||||
$route = new Route('/');
|
$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 = new RouteCollection();
|
||||||
|
|
||||||
$routes->add('foo', $route);
|
$routes->add('foo', $route);
|
||||||
|
@ -20,14 +20,14 @@ class ObjectLoaderTest extends TestCase
|
|||||||
{
|
{
|
||||||
public function testLoadCallsServiceAndReturnsCollection()
|
public function testLoadCallsServiceAndReturnsCollection()
|
||||||
{
|
{
|
||||||
$loader = new TestObjectLoader();
|
$loader = new TestObjectLoader('some-env');
|
||||||
|
|
||||||
// create a basic collection that will be returned
|
// create a basic collection that will be returned
|
||||||
$collection = new RouteCollection();
|
$collection = new RouteCollection();
|
||||||
$collection->add('foo', new Route('/foo'));
|
$collection->add('foo', new Route('/foo'));
|
||||||
|
|
||||||
$loader->loaderMap = [
|
$loader->loaderMap = [
|
||||||
'my_route_provider_service' => new TestObjectLoaderRouteService($collection),
|
'my_route_provider_service' => new TestObjectLoaderRouteService($collection, 'some-env'),
|
||||||
];
|
];
|
||||||
|
|
||||||
$actualRoutes = $loader->load(
|
$actualRoutes = $loader->load(
|
||||||
@ -112,14 +112,20 @@ class TestObjectLoader extends ObjectLoader
|
|||||||
class TestObjectLoaderRouteService
|
class TestObjectLoaderRouteService
|
||||||
{
|
{
|
||||||
private $collection;
|
private $collection;
|
||||||
|
private $env;
|
||||||
|
|
||||||
public function __construct($collection)
|
public function __construct($collection, string $env = null)
|
||||||
{
|
{
|
||||||
$this->collection = $collection;
|
$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;
|
return $this->collection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -284,4 +284,14 @@ class PhpFileLoaderTest extends TestCase
|
|||||||
|
|
||||||
$this->assertEquals($expectedRoutes('php'), $routes);
|
$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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -563,4 +563,14 @@ class XmlFileLoaderTest extends TestCase
|
|||||||
|
|
||||||
$this->assertEquals($expectedRoutes('xml'), $routes);
|
$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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -435,4 +435,14 @@ class YamlFileLoaderTest extends TestCase
|
|||||||
|
|
||||||
$this->assertEquals($expectedRoutes('yml'), $routes);
|
$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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
"symfony/polyfill-php80": "^1.15"
|
"symfony/polyfill-php80": "^1.15"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"symfony/config": "^5.0",
|
"symfony/config": "^5.3",
|
||||||
"symfony/http-foundation": "^4.4|^5.0",
|
"symfony/http-foundation": "^4.4|^5.0",
|
||||||
"symfony/yaml": "^4.4|^5.0",
|
"symfony/yaml": "^4.4|^5.0",
|
||||||
"symfony/expression-language": "^4.4|^5.0",
|
"symfony/expression-language": "^4.4|^5.0",
|
||||||
@ -30,7 +30,7 @@
|
|||||||
"psr/log": "~1.0"
|
"psr/log": "~1.0"
|
||||||
},
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
"symfony/config": "<5.0",
|
"symfony/config": "<5.3",
|
||||||
"symfony/dependency-injection": "<4.4",
|
"symfony/dependency-injection": "<4.4",
|
||||||
"symfony/yaml": "<4.4"
|
"symfony/yaml": "<4.4"
|
||||||
},
|
},
|
||||||
|
Reference in New Issue
Block a user