feature #22234 [DI] Introducing autoconfigure: automatic _instanceof configuration (weaverryan)

This PR was squashed before being merged into the 3.3-dev branch (closes #22234).

Discussion
----------

[DI] Introducing autoconfigure: automatic _instanceof configuration

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes (mostly, a continuation of a new feature)
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | n/a
| License       | MIT
| Doc PR        | https://github.com/symfony/symfony-docs/issues/7538

This is a proposal to allow the user to opt into some automatic `_instanceof` config. Suppose I want to auto-tag all of my voters and event subscribers

```yml
# current
services:
    _defaults:
        autowire: true

    _instanceof:
        Symfony\Component\Security\Core\Authorization\Voter\VoterInterface:
            tags: [security.voter]

        Symfony\Component\EventDispatcher\EventSubscriberInterface:
            tags: [kernel.event_subscriber]

    # services using the above tags
    AppBundle\Security\PostVoter: ~
    AppBundle\EventListener\CheckRequirementsSubscriber: ~
```

If I'm registering a service with a class that implements `VoterInterface`, when would I ever *not* want that to be tagged with `security.voter`? Here's the proposed code:

```yml
# proposed
services:
    _defaults:
        autowire: true
        autoconfigure: true

    # services using the auto_configure_instanceof functionality
    AppBundle\Security\PostVoter: ~
    AppBundle\EventListener\CheckRequirementsSubscriber: ~
```

The user must opt into this and it only applies locally to this configuration file. It works because each enabled bundle would have the opportunity to add one or more "automatic instanceof" definitions - e.g. SecurityBundle would add the `security.voter` instanceof config, FrameworkBundle would add the `kernel.event_subscriber` instanceof config, etc.

For another example, you can check out the proposed changes to `symfony-demo` - symfony/symfony-demo#483 - the `_instanceof` section is pretty heavy: 81694ac21e/app/config/services.yml (L20)

Thanks!

Commits
-------

18627bf9f6 [DI] Introducing autoconfigure: automatic _instanceof configuration
This commit is contained in:
Fabien Potencier 2017-04-20 11:20:32 -06:00
commit f730ffae49
57 changed files with 410 additions and 25 deletions

View File

@ -222,6 +222,7 @@ class JsonDescriptor extends Descriptor
'shared' => $definition->isShared(),
'abstract' => $definition->isAbstract(),
'autowire' => $definition->isAutowired(),
'autoconfigure' => $definition->isAutoconfigured(),
);
foreach ($definition->getAutowiringTypes(false) as $autowiringType) {

View File

@ -183,6 +183,7 @@ class MarkdownDescriptor extends Descriptor
."\n".'- Shared: '.($definition->isShared() ? 'yes' : 'no')
."\n".'- Abstract: '.($definition->isAbstract() ? 'yes' : 'no')
."\n".'- Autowired: '.($definition->isAutowired() ? 'yes' : 'no')
."\n".'- Autoconfigured: '.($definition->isAutoconfigured() ? 'yes' : 'no')
;
foreach ($definition->getAutowiringTypes(false) as $autowiringType) {

View File

@ -372,6 +372,7 @@ class XmlDescriptor extends Descriptor
$serviceXML->setAttribute('shared', $definition->isShared() ? 'true' : 'false');
$serviceXML->setAttribute('abstract', $definition->isAbstract() ? 'true' : 'false');
$serviceXML->setAttribute('autowired', $definition->isAutowired() ? 'true' : 'false');
$serviceXML->setAttribute('autoconfigured', $definition->isAutoconfigured() ? 'true' : 'false');
$serviceXML->setAttribute('file', $definition->getFile());
$calls = $definition->getMethodCalls();

View File

@ -13,30 +13,49 @@ namespace Symfony\Bundle\FrameworkBundle\DependencyInjection;
use Doctrine\Common\Annotations\Reader;
use Symfony\Bridge\Monolog\Processor\DebugProcessor;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\Config\Resource\DirectoryResource;
use Symfony\Component\Config\ResourceCheckerInterface;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Form\FormTypeGuesserInterface;
use Symfony\Component\Form\FormTypeInterface;
use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\PropertyAccess\PropertyAccessor;
use Symfony\Component\Serializer\Encoder\YamlEncoder;
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
use Symfony\Component\Serializer\Encoder\CsvEncoder;
use Symfony\Component\Serializer\Encoder\EncoderInterface;
use Symfony\Component\Serializer\Encoder\YamlEncoder;
use Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory;
use Symfony\Component\Serializer\Normalizer\DataUriNormalizer;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
use Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Validator\ConstraintValidatorInterface;
use Symfony\Component\Validator\ObjectInitializerInterface;
use Symfony\Component\WebLink\HttpHeaderSerializer;
use Symfony\Component\Workflow;
@ -225,6 +244,45 @@ class FrameworkExtension extends Extension
'Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller',
));
$container->registerForAutoconfiguration(Command::class)
->addTag('console.command');
$container->registerForAutoconfiguration(ResourceCheckerInterface::class)
->addTag('config_cache.resource_checker');
$container->registerForAutoconfiguration(ServiceSubscriberInterface::class)
->addTag('container.service_subscriber');
$container->registerForAutoconfiguration(AbstractController::class)
->addTag('controller.service_arguments');
$container->registerForAutoconfiguration(Controller::class)
->addTag('controller.service_arguments');
$container->registerForAutoconfiguration(DataCollectorInterface::class)
->addTag('data_collector');
$container->registerForAutoconfiguration(FormTypeInterface::class)
->addTag('form.type');
$container->registerForAutoconfiguration(FormTypeGuesserInterface::class)
->addTag('form.type_guesser');
$container->registerForAutoconfiguration(CacheClearerInterface::class)
->addTag('kernel.cache_clearer');
$container->registerForAutoconfiguration(CacheWarmerInterface::class)
->addTag('kernel.cache_warmer');
$container->registerForAutoconfiguration(EventSubscriberInterface::class)
->addTag('kernel.event_subscriber');
$container->registerForAutoconfiguration(PropertyListExtractorInterface::class)
->addTag('property_info.list_extractor');
$container->registerForAutoconfiguration(PropertyTypeExtractorInterface::class)
->addTag('property_info.type_extractor');
$container->registerForAutoconfiguration(PropertyDescriptionExtractorInterface::class)
->addTag('property_info.description_extractor');
$container->registerForAutoconfiguration(PropertyAccessExtractorInterface::class)
->addTag('property_info.access_extractor');
$container->registerForAutoconfiguration(EncoderInterface::class)
->addTag('serializer.encoder');
$container->registerForAutoconfiguration(NormalizerInterface::class)
->addTag('serializer.normalizer');
$container->registerForAutoconfiguration(ConstraintValidatorInterface::class)
->addTag('validator.constraint_validator');
$container->registerForAutoconfiguration(ObjectInitializerInterface::class)
->addTag('validator.initializer');
if (PHP_VERSION_ID < 70000) {
$this->addClassesToCompile(array(
'Symfony\\Component\\Config\\ConfigCache',

View File

@ -11,6 +11,7 @@
"shared": true,
"abstract": true,
"autowire": false,
"autoconfigure": false,
"file": null,
"factory_class": "Full\\Qualified\\FactoryClass",
"factory_method": "get",

View File

@ -12,5 +12,6 @@
- Shared: yes
- Abstract: yes
- Autowired: no
- Autoconfigured: no
- Factory Class: `Full\Qualified\FactoryClass`
- Factory Method: `get`

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<alias id="alias_1" service="service_1" public="true"/>
<definition id="service_1" class="Full\Qualified\Class1" public="true" synthetic="false" lazy="true" shared="true" abstract="true" autowired="false" file="">
<definition id="service_1" class="Full\Qualified\Class1" public="true" synthetic="false" lazy="true" shared="true" abstract="true" autowired="false" autoconfigured="false" file="">
<factory class="Full\Qualified\FactoryClass" method="get"/>
</definition>

View File

@ -11,6 +11,7 @@
"shared": true,
"abstract": false,
"autowire": false,
"autoconfigure": false,
"file": "\/path\/to\/file",
"factory_service": "factory.service",
"factory_method": "get",

View File

@ -12,6 +12,7 @@
- Shared: yes
- Abstract: no
- Autowired: no
- Autoconfigured: no
- File: `/path/to/file`
- Factory Service: `factory.service`
- Factory Method: `get`

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<alias id="alias_2" service="service_2" public="false"/>
<definition id="service_2" class="Full\Qualified\Class2" public="false" synthetic="true" lazy="false" shared="true" abstract="false" autowired="false" file="/path/to/file">
<definition id="service_2" class="Full\Qualified\Class2" public="false" synthetic="true" lazy="false" shared="true" abstract="false" autowired="false" autoconfigured="false" file="/path/to/file">
<factory service="factory.service" method="get"/>
<calls>
<call method="setMailer"/>

View File

@ -8,6 +8,7 @@
"shared": true,
"abstract": true,
"autowire": false,
"autoconfigure": false,
"arguments": [
{
"type": "service",
@ -22,6 +23,7 @@
"shared": true,
"abstract": false,
"autowire": false,
"autoconfigure": false,
"arguments": [
"arg1",
"arg2"
@ -43,6 +45,7 @@
"shared": true,
"abstract": false,
"autowire": false,
"autoconfigure": false,
"arguments": [],
"file": null,
"tags": []

View File

@ -13,6 +13,7 @@ Definitions
- Shared: yes
- Abstract: yes
- Autowired: no
- Autoconfigured: no
- Arguments: yes
- Factory Class: `Full\Qualified\FactoryClass`
- Factory Method: `get`

View File

@ -2,12 +2,12 @@
<container>
<alias id="alias_1" service="service_1" public="true"/>
<alias id="alias_2" service="service_2" public="false"/>
<definition id="definition_1" class="Full\Qualified\Class1" public="true" synthetic="false" lazy="true" shared="true" abstract="true" autowired="false" file="">
<definition id="definition_1" class="Full\Qualified\Class1" public="true" synthetic="false" lazy="true" shared="true" abstract="true" autowired="false" autoconfigured="false" file="">
<factory class="Full\Qualified\FactoryClass" method="get"/>
<argument type="service" id="definition2"/>
<argument>%parameter%</argument>
<argument>
<definition class="inline_service" public="true" synthetic="false" lazy="false" shared="true" abstract="false" autowired="false" file="">
<definition class="inline_service" public="true" synthetic="false" lazy="false" shared="true" abstract="false" autowired="false" autoconfigured="false" file="">
<argument>arg1</argument>
<argument>arg2</argument>
</definition>
@ -16,7 +16,7 @@
<argument>foo</argument>
<argument type="service" id="definition2"/>
<argument>
<definition class="inline_service" public="true" synthetic="false" lazy="false" shared="true" abstract="false" autowired="false" file=""/>
<definition class="inline_service" public="true" synthetic="false" lazy="false" shared="true" abstract="false" autowired="false" autoconfigured="false" file=""/>
</argument>
</argument>
<argument type="iterator">

View File

@ -8,6 +8,7 @@
"shared": true,
"abstract": true,
"autowire": false,
"autoconfigure": false,
"file": null,
"factory_class": "Full\\Qualified\\FactoryClass",
"factory_method": "get",

View File

@ -13,6 +13,7 @@ Definitions
- Shared: yes
- Abstract: yes
- Autowired: no
- Autoconfigured: no
- Factory Class: `Full\Qualified\FactoryClass`
- Factory Method: `get`

View File

@ -2,7 +2,7 @@
<container>
<alias id="alias_1" service="service_1" public="true"/>
<alias id="alias_2" service="service_2" public="false"/>
<definition id="definition_1" class="Full\Qualified\Class1" public="true" synthetic="false" lazy="true" shared="true" abstract="true" autowired="false" file="">
<definition id="definition_1" class="Full\Qualified\Class1" public="true" synthetic="false" lazy="true" shared="true" abstract="true" autowired="false" autoconfigured="false" file="">
<factory class="Full\Qualified\FactoryClass" method="get"/>
</definition>
<service id="service_container" class="Symfony\Component\DependencyInjection\ContainerBuilder"/>

View File

@ -8,6 +8,7 @@
"shared": true,
"abstract": true,
"autowire": false,
"autoconfigure": false,
"file": null,
"factory_class": "Full\\Qualified\\FactoryClass",
"factory_method": "get",
@ -21,6 +22,7 @@
"shared": true,
"abstract": false,
"autowire": false,
"autoconfigure": false,
"file": "\/path\/to\/file",
"factory_service": "factory.service",
"factory_method": "get",

View File

@ -13,6 +13,7 @@ Definitions
- Shared: yes
- Abstract: yes
- Autowired: no
- Autoconfigured: no
- Factory Class: `Full\Qualified\FactoryClass`
- Factory Method: `get`
@ -25,6 +26,7 @@ Definitions
- Shared: yes
- Abstract: no
- Autowired: no
- Autoconfigured: no
- File: `/path/to/file`
- Factory Service: `factory.service`
- Factory Method: `get`

View File

@ -2,10 +2,10 @@
<container>
<alias id="alias_1" service="service_1" public="true"/>
<alias id="alias_2" service="service_2" public="false"/>
<definition id="definition_1" class="Full\Qualified\Class1" public="true" synthetic="false" lazy="true" shared="true" abstract="true" autowired="false" file="">
<definition id="definition_1" class="Full\Qualified\Class1" public="true" synthetic="false" lazy="true" shared="true" abstract="true" autowired="false" autoconfigured="false" file="">
<factory class="Full\Qualified\FactoryClass" method="get"/>
</definition>
<definition id="definition_2" class="Full\Qualified\Class2" public="false" synthetic="true" lazy="false" shared="true" abstract="false" autowired="false" file="/path/to/file">
<definition id="definition_2" class="Full\Qualified\Class2" public="false" synthetic="true" lazy="false" shared="true" abstract="false" autowired="false" autoconfigured="false" file="/path/to/file">
<factory service="factory.service" method="get"/>
<calls>
<call method="setMailer"/>

View File

@ -8,6 +8,7 @@
"shared": true,
"abstract": false,
"autowire": false,
"autoconfigure": false,
"file": "\/path\/to\/file",
"factory_service": "factory.service",
"factory_method": "get",

View File

@ -13,6 +13,7 @@ Definitions
- Shared: yes
- Abstract: no
- Autowired: no
- Autoconfigured: no
- File: `/path/to/file`
- Factory Service: `factory.service`
- Factory Method: `get`

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<container>
<definition id="definition_2" class="Full\Qualified\Class2" public="false" synthetic="true" lazy="false" shared="true" abstract="false" autowired="false" file="/path/to/file">
<definition id="definition_2" class="Full\Qualified\Class2" public="false" synthetic="true" lazy="false" shared="true" abstract="false" autowired="false" autoconfigured="false" file="/path/to/file">
<factory service="factory.service" method="get"/>
<calls>
<call method="setMailer"/>

View File

@ -8,6 +8,7 @@
"shared": true,
"abstract": false,
"autowire": false,
"autoconfigure": false,
"file": "\/path\/to\/file",
"factory_service": "factory.service",
"factory_method": "get",
@ -25,6 +26,7 @@
"shared": true,
"abstract": false,
"autowire": false,
"autoconfigure": false,
"file": "\/path\/to\/file",
"factory_service": "factory.service",
"factory_method": "get",

View File

@ -13,6 +13,7 @@ tag1
- Shared: yes
- Abstract: no
- Autowired: no
- Autoconfigured: no
- File: `/path/to/file`
- Factory Service: `factory.service`
- Factory Method: `get`
@ -31,6 +32,7 @@ tag2
- Shared: yes
- Abstract: no
- Autowired: no
- Autoconfigured: no
- File: `/path/to/file`
- Factory Service: `factory.service`
- Factory Method: `get`

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<container>
<tag name="tag1">
<definition id="definition_2" class="Full\Qualified\Class2" public="false" synthetic="true" lazy="false" shared="true" abstract="false" autowired="false" file="/path/to/file">
<definition id="definition_2" class="Full\Qualified\Class2" public="false" synthetic="true" lazy="false" shared="true" abstract="false" autowired="false" autoconfigured="false" file="/path/to/file">
<factory service="factory.service" method="get"/>
<calls>
<call method="setMailer"/>
@ -9,7 +9,7 @@
</definition>
</tag>
<tag name="tag2">
<definition id="definition_2" class="Full\Qualified\Class2" public="false" synthetic="true" lazy="false" shared="true" abstract="false" autowired="false" file="/path/to/file">
<definition id="definition_2" class="Full\Qualified\Class2" public="false" synthetic="true" lazy="false" shared="true" abstract="false" autowired="false" autoconfigured="false" file="/path/to/file">
<factory service="factory.service" method="get"/>
<calls>
<call method="setMailer"/>

View File

@ -6,6 +6,7 @@
"shared": true,
"abstract": true,
"autowire": false,
"autoconfigure": false,
"file": null,
"factory_class": "Full\\Qualified\\FactoryClass",
"factory_method": "get",

View File

@ -5,5 +5,6 @@
- Shared: yes
- Abstract: yes
- Autowired: no
- Autoconfigured: no
- Factory Class: `Full\Qualified\FactoryClass`
- Factory Method: `get`

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<definition class="Full\Qualified\Class1" public="true" synthetic="false" lazy="true" shared="true" abstract="true" autowired="false" file="">
<definition class="Full\Qualified\Class1" public="true" synthetic="false" lazy="true" shared="true" abstract="true" autowired="false" autoconfigured="false" file="">
<factory class="Full\Qualified\FactoryClass" method="get"/>
</definition>

View File

@ -6,6 +6,7 @@
"shared": true,
"abstract": false,
"autowire": false,
"autoconfigure": false,
"file": "\/path\/to\/file",
"factory_service": "factory.service",
"factory_method": "get",

View File

@ -5,6 +5,7 @@
- Shared: yes
- Abstract: no
- Autowired: no
- Autoconfigured: no
- File: `/path/to/file`
- Factory Service: `factory.service`
- Factory Method: `get`

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<definition class="Full\Qualified\Class2" public="false" synthetic="true" lazy="false" shared="true" abstract="false" autowired="false" file="/path/to/file">
<definition class="Full\Qualified\Class2" public="false" synthetic="true" lazy="false" shared="true" abstract="false" autowired="false" autoconfigured="false" file="/path/to/file">
<factory service="factory.service" method="get"/>
<calls>
<call method="setMailer"/>

View File

@ -6,6 +6,7 @@
"shared": true,
"abstract": true,
"autowire": false,
"autoconfigure": false,
"arguments": [
{
"type": "service",
@ -20,6 +21,7 @@
"shared": true,
"abstract": false,
"autowire": false,
"autoconfigure": false,
"arguments": [
"arg1",
"arg2"
@ -41,6 +43,7 @@
"shared": true,
"abstract": false,
"autowire": false,
"autoconfigure": false,
"arguments": [],
"file": null,
"tags": []

View File

@ -5,6 +5,7 @@
- Shared: yes
- Abstract: yes
- Autowired: no
- Autoconfigured: no
- Arguments: yes
- Factory Class: `Full\Qualified\FactoryClass`
- Factory Method: `get`

View File

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<definition class="Full\Qualified\Class1" public="true" synthetic="false" lazy="true" shared="true" abstract="true" autowired="false" file="">
<definition class="Full\Qualified\Class1" public="true" synthetic="false" lazy="true" shared="true" abstract="true" autowired="false" autoconfigured="false" file="">
<factory class="Full\Qualified\FactoryClass" method="get"/>
<argument type="service" id="definition2"/>
<argument>%parameter%</argument>
<argument>
<definition class="inline_service" public="true" synthetic="false" lazy="false" shared="true" abstract="false" autowired="false" file="">
<definition class="inline_service" public="true" synthetic="false" lazy="false" shared="true" abstract="false" autowired="false" autoconfigured="false" file="">
<argument>arg1</argument>
<argument>arg2</argument>
</definition>
@ -13,7 +13,7 @@
<argument>foo</argument>
<argument type="service" id="definition2"/>
<argument>
<definition class="inline_service" public="true" synthetic="false" lazy="false" shared="true" abstract="false" autowired="false" file=""/>
<definition class="inline_service" public="true" synthetic="false" lazy="false" shared="true" abstract="false" autowired="false" autoconfigured="false" file=""/>
</argument>
</argument>
<argument type="iterator">

View File

@ -6,6 +6,7 @@
"shared": true,
"abstract": false,
"autowire": false,
"autoconfigure": false,
"arguments": [],
"file": "\/path\/to\/file",
"factory_service": "factory.service",

View File

@ -5,6 +5,7 @@
- Shared: yes
- Abstract: no
- Autowired: no
- Autoconfigured: no
- Arguments: no
- File: `/path/to/file`
- Factory Service: `factory.service`

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<definition class="Full\Qualified\Class2" public="false" synthetic="true" lazy="false" shared="true" abstract="false" autowired="false" file="/path/to/file">
<definition class="Full\Qualified\Class2" public="false" synthetic="true" lazy="false" shared="true" abstract="false" autowired="false" autoconfigured="false" file="/path/to/file">
<factory service="factory.service" method="get"/>
<calls>
<call method="setMailer"/>

View File

@ -19,13 +19,13 @@ use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Security\Core\Authorization\ExpressionLanguage;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
/**
* SecurityExtension.
@ -110,6 +110,9 @@ class SecurityExtension extends Extension
$this->aclLoad($config['acl'], $container);
}
$container->registerForAutoconfiguration(VoterInterface::class)
->addTag('security.voter');
if (PHP_VERSION_ID < 70000) {
// add some required classes for compilation
$this->addClassesToCompile(array(

View File

@ -13,6 +13,7 @@ namespace Symfony\Bundle\TwigBundle\DependencyInjection;
use Symfony\Bridge\Twig\Extension\WebLinkExtension;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
@ -143,6 +144,11 @@ class TwigExtension extends Extension
$container->getDefinition('twig')->replaceArgument(1, $config);
$container->registerForAutoconfiguration(\Twig_ExtensionInterface::class)
->addTag('twig.extension');
$container->registerForAutoconfiguration(\Twig_LoaderInterface::class)
->addTag('twig.loader');
if (PHP_VERSION_ID < 70000) {
$this->addClassesToCompile(array(
'Twig_Environment',

View File

@ -101,6 +101,7 @@ class ResolveDefinitionTemplatesPass extends AbstractRecursivePass
$def->setPublic($parentDef->isPublic());
$def->setLazy($parentDef->isLazy());
$def->setAutowired($parentDef->isAutowired());
$def->setAutoconfigured($parentDef->isAutoconfigured());
$def->setChanges($parentDef->getChanges());
// overwrite with values specified in the decorator
@ -129,6 +130,9 @@ class ResolveDefinitionTemplatesPass extends AbstractRecursivePass
if (isset($changes['autowired'])) {
$def->setAutowired($definition->isAutowired());
}
if (isset($changes['autoconfigured'])) {
$def->setAutoconfigured($definition->isAutoconfigured());
}
if (isset($changes['shared'])) {
$def->setShared($definition->isShared());
}

View File

@ -38,25 +38,37 @@ class ResolveInstanceofConditionalsPass implements CompilerPassInterface
private function processDefinition(ContainerBuilder $container, $id, Definition $definition)
{
if (!$instanceofConditionals = $definition->getInstanceofConditionals()) {
$instanceofConditionals = $definition->getInstanceofConditionals();
$automaticInstanceofConditionals = $definition->isAutoconfigured() ? $container->getAutomaticInstanceofDefinitions() : array();
if (!$instanceofConditionals && !$automaticInstanceofConditionals) {
return $definition;
}
if (!$class = $container->getParameterBag()->resolveValue($definition->getClass())) {
return $definition;
}
$conditionals = $this->mergeConditionals($automaticInstanceofConditionals, $instanceofConditionals);
$definition->setInstanceofConditionals(array());
$parent = $shared = null;
$instanceofTags = array();
foreach ($instanceofConditionals as $interface => $instanceofDef) {
foreach ($conditionals as $interface => $instanceofDefs) {
if ($interface !== $class && (!$container->getReflectionClass($interface) || !$container->getReflectionClass($class))) {
continue;
}
if ($interface === $class || is_subclass_of($class, $interface)) {
if ($interface !== $class && !is_subclass_of($class, $interface)) {
continue;
}
foreach ($instanceofDefs as $key => $instanceofDef) {
/** @var ChildDefinition $instanceofDef */
$instanceofDef = clone $instanceofDef;
$instanceofDef->setAbstract(true)->setInheritTags(false)->setParent($parent ?: 'abstract.instanceof.'.$id);
$parent = 'instanceof.'.$interface.'.'.$id;
$parent = 'instanceof.'.$interface.'.'.$key.'.'.$id;
$container->setDefinition($parent, $instanceofDef);
$instanceofTags[] = $instanceofDef->getTags();
$instanceofDef->setTags(array());
@ -100,4 +112,20 @@ class ResolveInstanceofConditionalsPass implements CompilerPassInterface
return $definition;
}
private function mergeConditionals(array $automaticInstanceofConditionals, array $instanceofConditionals)
{
// make each value an array of ChildDefinition
$conditionals = array_map(function($childDef) { return array($childDef); }, $automaticInstanceofConditionals);
foreach ($instanceofConditionals as $interface => $instanceofDef) {
if (!isset($automaticInstanceofConditionals[$interface])) {
$conditionals[$interface] = array();
}
$conditionals[$interface][] = $instanceofDef;
}
return $conditionals;
}
}

View File

@ -118,6 +118,8 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
*/
private $vendors;
private $automaticInstanceofDefinitions = array();
public function __construct(ParameterBagInterface $parameterBag = null)
{
parent::__construct($parameterBag);
@ -638,6 +640,14 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
$this->envCounters[$env] += $count;
}
}
foreach ($container->getAutomaticInstanceofDefinitions() as $interface => $childDefinition) {
if (isset($this->automaticInstanceofDefinitions[$interface])) {
throw new InvalidArgumentException(sprintf('%s has already been autoconfigured and merge() does not support merging autoconfiguration for the same class/interface.', $interface));
}
$this->automaticInstanceofDefinitions[$interface] = $childDefinition;
}
}
/**
@ -1259,6 +1269,31 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
return $this->expressionLanguageProviders;
}
/**
* Returns a ChildDefinition that will be used for autoconfiguring the interface/class.
*
* @param string $interface The class or interface to match
* @return ChildDefinition
*/
public function registerForAutoconfiguration($interface)
{
if (!isset($this->automaticInstanceofDefinitions[$interface])) {
$this->automaticInstanceofDefinitions[$interface] = new ChildDefinition('');
}
return $this->automaticInstanceofDefinitions[$interface];
}
/**
* Returns an array of ChildDefinition[] keyed by interface.
*
* @return ChildDefinition[]
*/
public function getAutomaticInstanceofDefinitions()
{
return $this->automaticInstanceofDefinitions;
}
/**
* Resolves env parameter placeholders in a string or an array.
*

View File

@ -30,6 +30,7 @@ class Definition
private $properties = array();
private $calls = array();
private $instanceof = array();
private $autoconfigured = false;
private $configurator;
private $tags = array();
private $public = true;
@ -388,6 +389,30 @@ class Definition
return $this->instanceof;
}
/**
* Sets whether or not instanceof conditionals should be prepended with a global set.
*
* @param bool $autoconfigured
*
* @return $this
*/
public function setAutoconfigured($autoconfigured)
{
$this->changes['autoconfigured'] = true;
$this->autoconfigured = $autoconfigured;
return $this;
}
/**
* @return bool
*/
public function isAutoconfigured()
{
return $this->autoconfigured;
}
/**
* Sets tags for this definition.
*

View File

@ -205,6 +205,10 @@ class XmlDumper extends Dumper
$service->appendChild($autowiringType);
}
if ($definition->isAutoconfigured()) {
$service->setAttribute('autoconfigure', 'true');
}
if ($callable = $definition->getConfigurator()) {
$configurator = $this->document->createElement('configurator');

View File

@ -180,6 +180,9 @@ class XmlFileLoader extends FileLoader
if ($defaultsNode->hasAttribute('inherit-tags')) {
$defaults['inherit-tags'] = XmlUtils::phpize($defaultsNode->getAttribute('inherit-tags'));
}
if ($defaultsNode->hasAttribute('autoconfigure')) {
$defaults['autoconfigure'] = XmlUtils::phpize($defaultsNode->getAttribute('autoconfigure'));
}
return $defaults;
}
@ -229,6 +232,9 @@ class XmlFileLoader extends FileLoader
if (isset($defaults['autowire'])) {
$definition->setAutowired($defaults['autowire']);
}
if (isset($defaults['autoconfigure'])) {
$definition->setAutoconfigured($defaults['autoconfigure']);
}
$definition->setChanges(array());
}
@ -248,6 +254,10 @@ class XmlFileLoader extends FileLoader
$definition->setAutowired(XmlUtils::phpize($value));
}
if ($value = $service->getAttribute('autoconfigure')) {
$definition->setAutoconfigured(XmlUtils::phpize($value));
}
if ($files = $this->getChildren($service, 'file')) {
$definition->setFile($files[0]->nodeValue);
}

View File

@ -57,6 +57,7 @@ class YamlFileLoader extends FileLoader
'decoration_priority' => 'decoration_priority',
'autowire' => 'autowire',
'autowiring_types' => 'autowiring_types',
'autoconfigure' => 'autoconfigure',
);
private static $prototypeKeywords = array(
@ -75,6 +76,7 @@ class YamlFileLoader extends FileLoader
'tags' => 'tags',
'inherit_tags' => 'inherit_tags',
'autowire' => 'autowire',
'autoconfigure' => 'autoconfigure',
);
private static $instanceofKeywords = array(
@ -86,6 +88,7 @@ class YamlFileLoader extends FileLoader
'calls' => 'calls',
'tags' => 'tags',
'autowire' => 'autowire',
'autoconfigure' => 'autoconfigure',
);
private static $defaultsKeywords = array(
@ -93,6 +96,7 @@ class YamlFileLoader extends FileLoader
'tags' => 'tags',
'inherit_tags' => 'inherit_tags',
'autowire' => 'autowire',
'autoconfigure' => 'autoconfigure',
);
private $yamlParser;
@ -369,6 +373,9 @@ class YamlFileLoader extends FileLoader
if (isset($defaults['autowire'])) {
$definition->setAutowired($defaults['autowire']);
}
if (isset($defaults['autoconfigure'])) {
$definition->setAutoconfigured($defaults['autoconfigure']);
}
$definition->setChanges(array());
}
@ -510,6 +517,10 @@ class YamlFileLoader extends FileLoader
}
}
if (isset($service['autoconfigure'])) {
$definition->setAutoconfigured($service['autoconfigure']);
}
if (array_key_exists('resource', $service)) {
if (!is_string($service['resource'])) {
throw new InvalidArgumentException(sprintf('A "resource" attribute must be of type string for service "%s" in %s. Check your YAML syntax.', $id, $file));

View File

@ -104,6 +104,7 @@
<xsd:attribute name="public" type="boolean" />
<xsd:attribute name="autowire" type="boolean" />
<xsd:attribute name="inherit-tags" type="boolean" />
<xsd:attribute name="autoconfigure" type="boolean" />
</xsd:complexType>
<xsd:complexType name="service">
@ -132,6 +133,7 @@
<xsd:attribute name="decoration-priority" type="xsd:integer" />
<xsd:attribute name="autowire" type="boolean" />
<xsd:attribute name="inherit-tags" type="boolean" />
<xsd:attribute name="autoconfigure" type="boolean" />
</xsd:complexType>
<xsd:complexType name="instanceof">
@ -146,6 +148,7 @@
<xsd:attribute name="public" type="boolean" />
<xsd:attribute name="lazy" type="boolean" />
<xsd:attribute name="autowire" type="boolean" />
<xsd:attribute name="autoconfigure" type="boolean" />
</xsd:complexType>
<xsd:complexType name="prototype">
@ -167,6 +170,7 @@
<xsd:attribute name="parent" type="xsd:string" />
<xsd:attribute name="autowire" type="boolean" />
<xsd:attribute name="inherit-tags" type="boolean" />
<xsd:attribute name="autoconfigure" type="boolean" />
</xsd:complexType>
<xsd:complexType name="tag">

View File

@ -130,6 +130,7 @@ class IntegrationTest extends TestCase
// instanceof overrides defaults
$simpleService = $container->getDefinition('service_simple');
$this->assertFalse($simpleService->isAutowired());
$this->assertFalse($simpleService->isAutoconfigured());
$this->assertFalse($simpleService->isShared());
// all tags are kept
@ -156,6 +157,7 @@ class IntegrationTest extends TestCase
// service override instanceof
$overrideService = $container->getDefinition('service_override_instanceof');
$this->assertTrue($overrideService->isAutowired());
$this->assertTrue($overrideService->isAutoconfigured());
// children definitions get no instanceof
$childDef = $container->getDefinition('child_service');

View File

@ -381,6 +381,38 @@ class ResolveDefinitionTemplatesPassTest extends TestCase
$this->assertSame(array(2, 1, 'foo' => 3), $def->getArguments());
}
public function testSetAutoconfiguredOnServiceHasParent()
{
$container = new ContainerBuilder();
$container->register('parent', 'stdClass')
->setAutoconfigured(true)
;
$container->setDefinition('child1', new ChildDefinition('parent'))
->setAutoconfigured(false)
;
$this->process($container);
$this->assertFalse($container->getDefinition('child1')->isAutoconfigured());
}
public function testSetAutoconfiguredOnServiceIsParent()
{
$container = new ContainerBuilder();
$container->register('parent', 'stdClass')
->setAutoconfigured(true)
;
$container->setDefinition('child1', new ChildDefinition('parent'));
$this->process($container);
$this->assertTrue($container->getDefinition('child1')->isAutoconfigured());
}
protected function process(ContainerBuilder $container)
{
$pass = new ResolveDefinitionTemplatesPass();

View File

@ -15,6 +15,7 @@ use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\ResolveInstanceofConditionalsPass;
use Symfony\Component\DependencyInjection\Compiler\ResolveDefinitionTemplatesPass;
use Symfony\Component\DependencyInjection\Compiler\ResolveTagsInheritancePass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class ResolveInstanceofConditionalsPassTest extends TestCase
@ -29,7 +30,7 @@ class ResolveInstanceofConditionalsPassTest extends TestCase
(new ResolveInstanceofConditionalsPass())->process($container);
$parent = 'instanceof.'.parent::class.'.foo';
$parent = 'instanceof.'.parent::class.'.0.foo';
$def = $container->getDefinition('foo');
$this->assertEmpty($def->getInstanceofConditionals());
$this->assertInstanceof(ChildDefinition::class, $def);
@ -106,4 +107,51 @@ class ResolveInstanceofConditionalsPassTest extends TestCase
$this->assertTrue($def->isLazy());
$this->assertTrue($def->isShared());
}
public function testProcessUsesAutomaticInstanceofDefinitions()
{
$container = new ContainerBuilder();
$def = $container->register('normal_service', self::class);
$def->setInstanceofConditionals(array(
parent::class => (new ChildDefinition(''))
->addTag('local_instanceof_tag')
->setFactory('locally_set_factory'),
));
$def->setAutoconfigured(true);
$container->registerForAutoconfiguration(parent::class)
->addTag('automatic_instanceof_tag')
->setAutowired(true)
->setFactory('automatically_set_factory');
(new ResolveInstanceofConditionalsPass())->process($container);
(new ResolveTagsInheritancePass())->process($container);
(new ResolveDefinitionTemplatesPass())->process($container);
$def = $container->getDefinition('normal_service');
// autowired thanks to the automatic instanceof
$this->assertTrue($def->isAutowired());
// factory from the specific instanceof overrides global one
$this->assertEquals('locally_set_factory', $def->getFactory());
// tags are merged, the locally set one is first
$this->assertSame(array('local_instanceof_tag' => array(array()), 'automatic_instanceof_tag' => array(array())), $def->getTags());
}
public function testProcessDoesNotUseAutomaticInstanceofDefinitionsIfNotEnabled()
{
$container = new ContainerBuilder();
$def = $container->register('normal_service', self::class);
$def->setInstanceofConditionals(array(
parent::class => (new ChildDefinition(''))
->addTag('foo_tag'),
));
$container->registerForAutoconfiguration(parent::class)
->setAutowired(true);
(new ResolveInstanceofConditionalsPass())->process($container);
(new ResolveDefinitionTemplatesPass())->process($container);
$def = $container->getDefinition('normal_service');
// no automatic_tag, it was not enabled on the Definition
$this->assertFalse($def->isAutowired());
}
}

View File

@ -567,6 +567,26 @@ class ContainerBuilderTest extends TestCase
$this->assertSame(array('%env(Bar)%'), $config->resolveEnvPlaceholders(array($bag->get('env(Bar)'))));
$container->merge($config);
$this->assertEquals(array('Foo' => 0, 'Bar' => 1), $container->getEnvCounters());
$container = new ContainerBuilder();
$config = new ContainerBuilder();
$childDefA = $container->registerForAutoconfiguration('AInterface');
$childDefB = $config->registerForAutoconfiguration('BInterface');
$container->merge($config);
$this->assertSame(array('AInterface' => $childDefA, 'BInterface' => $childDefB), $container->getAutomaticInstanceofDefinitions());
}
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* @expectedExceptionMessage AInterface has already been autoconfigured and merge() does not support merging autoconfiguration for the same class/interface.
*/
public function testMergeThrowsExceptionForDuplicateAutomaticInstanceofDefinitions()
{
$container = new ContainerBuilder();
$config = new ContainerBuilder();
$container->registerForAutoconfiguration('AInterface');
$config->registerForAutoconfiguration('AInterface');
$container->merge($config);
}
public function testResolveEnvValues()
@ -1097,6 +1117,17 @@ class ContainerBuilderTest extends TestCase
$this->assertInstanceOf(ServiceLocator::class, $foo = $container->get('foo_service'));
$this->assertSame($container->get('bar_service'), $foo->get('bar'));
}
public function testRegisterForAutoconfiguration()
{
$container = new ContainerBuilder();
$childDefA = $container->registerForAutoconfiguration('AInterface');
$childDefB = $container->registerForAutoconfiguration('BInterface');
$this->assertSame(array('AInterface' => $childDefA, 'BInterface' => $childDefB), $container->getAutomaticInstanceofDefinitions());
// when called multiple times, the same instance is returned
$this->assertSame($childDefA, $container->registerForAutoconfiguration('AInterface'));
}
}
class FooClass

View File

@ -344,6 +344,7 @@ class DefinitionTest extends TestCase
$def->addTag('foo_tag');
$def->addMethodCall('methodCall');
$def->setProperty('fooprop', true);
$def->setAutoconfigured(true);
$this->assertSame(array(
'class' => true,
@ -356,6 +357,7 @@ class DefinitionTest extends TestCase
'lazy' => true,
'public' => true,
'shared' => true,
'autoconfigured' => true,
), $def->getChanges());
$def->setChanges(array());
@ -377,4 +379,12 @@ class DefinitionTest extends TestCase
$this->assertSame($def, $def->removeAutowiringType('Foo'));
$this->assertEquals(array('Bar'), $def->getAutowiringTypes());
}
public function testShouldAutoconfigure()
{
$def = new Definition('stdClass');
$this->assertFalse($def->isAutoconfigured());
$def->setAutoconfigured(true);
$this->assertTrue($def->isAutoconfigured());
}
}

View File

@ -0,0 +1,9 @@
<?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 http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<defaults autoconfigure="true" />
<service id="use_defaults_settings" />
<service id="override_defaults_settings_to_false" autoconfigure="false" />
</services>
</container>

View File

@ -0,0 +1,9 @@
services:
_defaults:
autoconfigure: true
use_defaults_settings: ~
override_defaults_settings_to_false:
autoconfigure: false

View File

@ -1,11 +1,13 @@
services:
_defaults:
autowire: true
autoconfigure: true
_instanceof:
Symfony\Component\DependencyInjection\Tests\Compiler\IntegrationTestStubParent:
# should override _defaults
autowire: false
autoconfigure: false
shared: false
tags:
- { name: foo_tag, tag_option: from_instanceof }
@ -30,6 +32,7 @@ services:
class: Symfony\Component\DependencyInjection\Tests\Compiler\IntegrationTestStub
# override instanceof
autowire: true
autoconfigure: true
parent_service:
abstract: true

View File

@ -693,6 +693,16 @@ class XmlFileLoaderTest extends TestCase
$this->assertTrue($definition->isLazy());
$this->assertSame(array('foo' => array(array()), 'bar' => array(array())), $definition->getTags());
}
public function testAutoConfigureInstanceof()
{
$container = new ContainerBuilder();
$loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml'));
$loader->load('services_autoconfigure.xml');
$this->assertTrue($container->getDefinition('use_defaults_settings')->isAutoconfigured());
$this->assertFalse($container->getDefinition('override_defaults_settings_to_false')->isAutoconfigured());
}
}
interface BarInterface

View File

@ -567,6 +567,16 @@ class YamlFileLoaderTest extends TestCase
$loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml'));
$loader->load('anonymous_services_in_parameters.yml');
}
public function testAutoConfigureInstanceof()
{
$container = new ContainerBuilder();
$loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml'));
$loader->load('services_autoconfigure.yml');
$this->assertTrue($container->getDefinition('use_defaults_settings')->isAutoconfigured());
$this->assertFalse($container->getDefinition('override_defaults_settings_to_false')->isAutoconfigured());
}
}
interface FooInterface