Skip to content

[FrameworkBundle] allow container/routing configurators to vary by env #40214

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1011,7 +1011,10 @@ private function registerRouterConfiguration(array $config, ContainerBuilder $co
$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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ public function registerContainerConfiguration(LoaderInterface $loader)
};

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();
Expand Down Expand Up @@ -193,7 +193,7 @@ public function loadRoutes(LoaderInterface $loader)
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');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,36 +49,42 @@
->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')

Expand Down
4 changes: 2 additions & 2 deletions src/Symfony/Bundle/FrameworkBundle/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
3 changes: 2 additions & 1 deletion src/Symfony/Component/Config/Loader/FileLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/**
Expand Down
6 changes: 6 additions & 0 deletions src/Symfony/Component/Config/Loader/Loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@
abstract class Loader implements LoaderInterface
{
protected $resolver;
protected $env;

public function __construct(string $env = null)
{
$this->env = $env;
}

/**
* {@inheritdoc}
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Component/DependencyInjection/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
-----
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,18 @@ 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);
}

/**
* {@inheritdoc}
*/
public function load($resource, string $type = null)
{
$resource($this->container);
$resource($this->container, $this->env);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -71,6 +73,23 @@ final public function services(): ServicesConfigurator
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
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ public function load($resource, string $type = null)
$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));
}
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public function load($resource, string $type = null)
$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 = [];
Expand Down
49 changes: 31 additions & 18 deletions src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,23 +50,36 @@ public function load($resource, string $type = null)

$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();
Expand All @@ -89,19 +102,19 @@ public function supports($resource, string $type = null)
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;
}

Expand All @@ -112,19 +125,19 @@ private function parseImports(\DOMDocument $xml, string $file)
}
}

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()));
}
Expand Down Expand Up @@ -170,12 +183,12 @@ private function parseDefinitions(\DOMDocument $xml, string $file, Definition $d
}
}

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();
}

Expand Down Expand Up @@ -393,7 +406,7 @@ private function parseFileToDOM(string $file): \DOMDocument
/**
* Processes anonymous services.
*/
private function processAnonymousServices(\DOMDocument $xml, string $file)
private function processAnonymousServices(\DOMDocument $xml, string $file, \DOMNode $root = null)
{
$definitions = [];
$count = 0;
Expand All @@ -403,7 +416,7 @@ private function processAnonymousServices(\DOMDocument $xml, string $file)
$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
Expand All @@ -422,7 +435,7 @@ private function processAnonymousServices(\DOMDocument $xml, string $file)
}

// 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()));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,20 @@ public function load($resource, string $type = null)
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);

Expand Down Expand Up @@ -770,7 +784,7 @@ private function validate($content, string $file): ?array
}

foreach ($content as $namespace => $data) {
if (\in_array($namespace, ['imports', 'parameters', 'services'])) {
if (\in_array($namespace, ['imports', 'parameters', 'services']) || 0 === strpos($namespace, 'when@')) {
continue;
}

Expand Down Expand Up @@ -907,7 +921,7 @@ private function resolveServices($value, string $file, bool $isParameter = false
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;
}

Expand Down
Loading