feature #40782 [DependencyInjection] Add #[When(env: 'foo')] to skip autoregistering a class when the env doesn't match (nicolas-grekas)

This PR was merged into the 5.3-dev branch.

Discussion
----------

[DependencyInjection] Add `#[When(env: 'foo')]` to skip autoregistering a class when the env doesn't match

| Q             | A
| ------------- | ---
| Branch?       | 5.x
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Tickets       | -
| License       | MIT
| Doc PR        | -

This is a follow up of #40214, in order to conditionally auto-register classes.

By adding a `#[When(env: prod)]` annotation on a class, one can tell that a class should be skipped when the current env doesn't match the one declared in the attribute.

This saves from writing similar conditional configuration by using the per-env `services_prod.yaml` convention (+corresponding exclusion from `services.yaml`), or some logic in the Kernel.

Commits
-------

59c75bad7b [DI] add `#[When(env: 'foo')]` to skip autoregistering a class when the env doesn't match
This commit is contained in:
Nicolas Grekas 2021-04-17 19:07:09 +02:00
commit 4cac9cf829
7 changed files with 83 additions and 3 deletions

View File

@ -0,0 +1,26 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\DependencyInjection\Attribute;
/**
* An attribute to tell under which environement this class should be registered as a service.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
class When
{
public function __construct(
public string $env,
) {
}
}

View File

@ -13,6 +13,7 @@ CHANGELOG
* Add support for per-env configuration in XML and Yaml loaders
* Add `ContainerBuilder::willBeAvailable()` to help with conditional configuration
* Add support an integer return value for default_index_method
* Add `#[When(env: 'foo')]` to skip autoregistering a class when the env doesn't match
* Add `env()` and `EnvConfigurator` in the PHP-DSL
* Add support for `ConfigBuilder` in the `PhpFileLoader`
* Add `ContainerConfigurator::env()` to get the current environment

View File

@ -17,6 +17,7 @@ use Symfony\Component\Config\FileLocatorInterface;
use Symfony\Component\Config\Loader\FileLoader as BaseFileLoader;
use Symfony\Component\Config\Loader\Loader;
use Symfony\Component\Config\Resource\GlobResource;
use Symfony\Component\DependencyInjection\Attribute\When;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\RegisterAutoconfigureAttributesPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
@ -98,11 +99,26 @@ abstract class FileLoader extends BaseFileLoader
}
$autoconfigureAttributes = new RegisterAutoconfigureAttributesPass();
$classes = $this->findClasses($namespace, $resource, (array) $exclude, $autoconfigureAttributes->accept($prototype) ? $autoconfigureAttributes : null);
$autoconfigureAttributes = $autoconfigureAttributes->accept($prototype) ? $autoconfigureAttributes : null;
$classes = $this->findClasses($namespace, $resource, (array) $exclude, $autoconfigureAttributes);
// prepare for deep cloning
$serializedPrototype = serialize($prototype);
foreach ($classes as $class => $errorMessage) {
if ($autoconfigureAttributes && $this->env) {
$r = $this->container->getReflectionClass($class);
$attribute = null;
foreach ($r->getAttributes(When::class) as $attribute) {
if ($this->env === $attribute->newInstance()->env) {
$attribute = null;
break;
}
}
if (null !== $attribute) {
continue;
}
}
if (interface_exists($class, false)) {
$this->interfaces[] = $class;
} else {

View File

@ -50,7 +50,13 @@ class XmlFileLoader extends FileLoader
$this->container->fileExists($path);
$this->loadXml($xml, $path);
$env = $this->env;
$this->env = null;
try {
$this->loadXml($xml, $path);
} finally {
$this->env = $env;
}
if ($this->env) {
$xpath = new \DOMXPath($xml);

View File

@ -129,7 +129,13 @@ class YamlFileLoader extends FileLoader
return;
}
$this->loadContent($content, $path);
$env = $this->env;
$this->env = null;
try {
$this->loadContent($content, $path);
} finally {
$this->env = $env;
}
// per-env configuration
if ($this->env && isset($content['when@'.$this->env])) {

View File

@ -2,6 +2,10 @@
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype;
use Symfony\Component\DependencyInjection\Attribute\When;
#[When(env: 'prod')]
#[When(env: 'dev')]
class Foo implements FooInterface, Sub\BarInterface
{
public function __construct($bar = null)

View File

@ -243,6 +243,27 @@ class FileLoaderTest extends TestCase
'yaml/*'
);
}
/**
* @requires PHP 8
*
* @testWith ["prod", true]
* ["dev", true]
* ["bar", false]
* [null, true]
*/
public function testRegisterClassesWithWhenEnv(?string $env, bool $expected)
{
$container = new ContainerBuilder();
$loader = new TestFileLoader($container, new FileLocator(self::$fixturesPath.'/Fixtures'), $env);
$loader->registerClasses(
(new Definition())->setAutoconfigured(true),
'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\\',
'Prototype/{Foo.php}'
);
$this->assertSame($expected, $container->has(Foo::class));
}
}
class TestFileLoader extends FileLoader