Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6cc9dc7

Browse files
committed
feature #22060 [DI] Add "by-id" autowiring: a side-effect free variant of it based on the class<>id convention (nicolas-grekas)
This PR was merged into the 3.3-dev branch. Discussion ---------- [DI] Add "by-id" autowiring: a side-effect free variant of it based on the class<>id convention | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | - This PR adds a new autowiring mode, based only on the class <> id convention. This way of autowiring is free from any conflicting behavior, which is what I was looking for to begin with. The expected DX is a bit more involving than the current way we do autowiring. But it's worth it to me, because it's plain predictable - a lot less "magic" imho. So in this mode, for each `App\Foo` type hint, a reference to an "App\Foo" service will be created. If no such service exists, an exception will be thrown. To me, this opens a nice DX: when type hinting interfaces (which is the best practice), this will tell you when you need to create the explicit interface <> id mapping that is missing - thus encourage things to be made explicit, but only when required, and gradually, in a way that will favor discoverability by devs. Of course, this is opt-in, and BC. You'd need to do eg in yaml: `autowire: by_id`. For consistency, the current mode (`autowire: true`) can be configured using `autowire: by_type`. Commits ------- c298f2a [DI] Add "by-id" autowiring: a side-effect free variant of it based on the class<>id convention
2 parents 9896f86 + c298f2a commit 6cc9dc7

File tree

18 files changed

+135
-25
lines changed

18 files changed

+135
-25
lines changed

src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/JsonDescriptor.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ private function getContainerDefinitionData(Definition $definition, $omitTags =
221221
'lazy' => $definition->isLazy(),
222222
'shared' => $definition->isShared(),
223223
'abstract' => $definition->isAbstract(),
224-
'autowire' => $definition->isAutowired(),
224+
'autowire' => $definition->isAutowired() ? (Definition::AUTOWIRE_BY_TYPE === $definition->getAutowired() ? 'by-type' : 'by-id') : false,
225225
);
226226

227227
foreach ($definition->getAutowiringTypes(false) as $autowiringType) {

src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/MarkdownDescriptor.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ protected function describeContainerDefinition(Definition $definition, array $op
182182
."\n".'- Lazy: '.($definition->isLazy() ? 'yes' : 'no')
183183
."\n".'- Shared: '.($definition->isShared() ? 'yes' : 'no')
184184
."\n".'- Abstract: '.($definition->isAbstract() ? 'yes' : 'no')
185-
."\n".'- Autowired: '.($definition->isAutowired() ? 'yes' : 'no')
185+
."\n".'- Autowired: '.($definition->isAutowired() ? (Definition::AUTOWIRE_BY_TYPE === $definition->getAutowired() ? 'by-type' : 'by-id') : 'no')
186186
;
187187

188188
foreach ($definition->getAutowiringTypes(false) as $autowiringType) {

src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/TextDescriptor.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ protected function describeContainerDefinition(Definition $definition, array $op
295295
$tableRows[] = array('Lazy', $definition->isLazy() ? 'yes' : 'no');
296296
$tableRows[] = array('Shared', $definition->isShared() ? 'yes' : 'no');
297297
$tableRows[] = array('Abstract', $definition->isAbstract() ? 'yes' : 'no');
298-
$tableRows[] = array('Autowired', $definition->isAutowired() ? 'yes' : 'no');
298+
$tableRows[] = array('Autowired', $definition->isAutowired() ? (Definition::AUTOWIRE_BY_TYPE === $definition->getAutowired() ? 'by-type' : 'by-id') : 'no');
299299

300300
if ($autowiringTypes = $definition->getAutowiringTypes(false)) {
301301
$tableRows[] = array('Autowiring Types', implode(', ', $autowiringTypes));

src/Symfony/Bundle/FrameworkBundle/Console/Descriptor/XmlDescriptor.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ private function getContainerDefinitionDocument(Definition $definition, $id = nu
371371
$serviceXML->setAttribute('lazy', $definition->isLazy() ? 'true' : 'false');
372372
$serviceXML->setAttribute('shared', $definition->isShared() ? 'true' : 'false');
373373
$serviceXML->setAttribute('abstract', $definition->isAbstract() ? 'true' : 'false');
374-
$serviceXML->setAttribute('autowired', $definition->isAutowired() ? 'true' : 'false');
374+
$serviceXML->setAttribute('autowired', $definition->isAutowired() ? (Definition::AUTOWIRE_BY_TYPE === $definition->getAutowired() ? 'by-type' : 'by-id') : 'false');
375375
$serviceXML->setAttribute('file', $definition->getFile());
376376

377377
$calls = $definition->getMethodCalls();

src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,10 @@ private function getAutowiredReference($type, $autoRegister = true)
312312
return new Reference($type);
313313
}
314314

315+
if (Definition::AUTOWIRE_BY_ID === $this->currentDefinition->getAutowired()) {
316+
return;
317+
}
318+
315319
if (null === $this->types) {
316320
$this->populateAvailableTypes();
317321
}
@@ -462,10 +466,18 @@ private function createAutowiredDefinition(\ReflectionClass $typeHint)
462466

463467
private function createTypeNotFoundMessage($type, $label)
464468
{
465-
if (!$classOrInterface = class_exists($type, false) ? 'class' : (interface_exists($type, false) ? 'interface' : null)) {
469+
$autowireById = Definition::AUTOWIRE_BY_ID === $this->currentDefinition->getAutowired();
470+
if (!$classOrInterface = class_exists($type, $autowireById) ? 'class' : (interface_exists($type, false) ? 'interface' : null)) {
466471
return sprintf('Cannot autowire service "%s": %s has type "%s" but this class does not exist.', $this->currentId, $label, $type);
467472
}
468-
$message = sprintf('no services were found matching the "%s" %s and it cannot be auto-registered for %s.', $type, $classOrInterface, $label);
473+
if (null === $this->types) {
474+
$this->populateAvailableTypes();
475+
}
476+
if ($autowireById) {
477+
$message = sprintf('%s references %s "%s" but no such service exists.%s', $label, $classOrInterface, $type, $this->createTypeAlternatives($type));
478+
} else {
479+
$message = sprintf('no services were found matching the "%s" %s and it cannot be auto-registered for %s.', $type, $classOrInterface, $label);
480+
}
469481

470482
return sprintf('Cannot autowire service "%s": %s', $this->currentId, $message);
471483
}

src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ private function doResolveDefinition(ChildDefinition $definition)
100100
$def->setFile($parentDef->getFile());
101101
$def->setPublic($parentDef->isPublic());
102102
$def->setLazy($parentDef->isLazy());
103-
$def->setAutowired($parentDef->isAutowired());
103+
$def->setAutowired($parentDef->getAutowired());
104104

105105
self::mergeDefinition($def, $definition);
106106

@@ -146,7 +146,7 @@ public static function mergeDefinition(Definition $def, ChildDefinition $definit
146146
$def->setDeprecated($definition->isDeprecated(), $definition->getDeprecationMessage('%service_id%'));
147147
}
148148
if (isset($changes['autowired'])) {
149-
$def->setAutowired($definition->isAutowired());
149+
$def->setAutowired($definition->getAutowired());
150150
}
151151
if (isset($changes['decorated_service'])) {
152152
$decoratedService = $definition->getDecoratedService();

src/Symfony/Component/DependencyInjection/Definition.php

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
*/
2222
class Definition
2323
{
24+
const AUTOWIRE_BY_TYPE = 1;
25+
const AUTOWIRE_BY_ID = 2;
26+
2427
private $class;
2528
private $file;
2629
private $factory;
@@ -37,7 +40,7 @@ class Definition
3740
private $abstract = false;
3841
private $lazy = false;
3942
private $decoratedService;
40-
private $autowired = false;
43+
private $autowired = 0;
4144
private $autowiringTypes = array();
4245

4346
protected $arguments;
@@ -700,20 +703,35 @@ public function setAutowiringTypes(array $types)
700703
* @return bool
701704
*/
702705
public function isAutowired()
706+
{
707+
return (bool) $this->autowired;
708+
}
709+
710+
/**
711+
* Gets the autowiring mode.
712+
*
713+
* @return int
714+
*/
715+
public function getAutowired()
703716
{
704717
return $this->autowired;
705718
}
706719

707720
/**
708721
* Sets autowired.
709722
*
710-
* @param bool $autowired
723+
* @param bool|int $autowired
711724
*
712725
* @return $this
713726
*/
714727
public function setAutowired($autowired)
715728
{
716-
$this->autowired = (bool) $autowired;
729+
$autowired = (int) $autowired;
730+
731+
if ($autowired && self::AUTOWIRE_BY_TYPE !== $autowired && self::AUTOWIRE_BY_ID !== $autowired) {
732+
throw new InvalidArgumentException(sprintf('Invalid argument: Definition::AUTOWIRE_BY_TYPE (%d) or Definition::AUTOWIRE_BY_ID (%d) expected, %d given.', self::AUTOWIRE_BY_TYPE, self::AUTOWIRE_BY_ID, $autowired));
733+
}
734+
$this->autowired = $autowired;
717735

718736
return $this;
719737
}

src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -646,10 +646,11 @@ private function addService($id, Definition $definition)
646646
}
647647

648648
if ($definition->isAutowired()) {
649+
$autowired = Definition::AUTOWIRE_BY_TYPE === $definition->getAutowired() ? 'types' : 'ids';
649650
$doc .= <<<EOF
650651
651652
*
652-
* This service is autowired.
653+
* This service is autowired by {$autowired}.
653654
EOF;
654655
}
655656

src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ private function addService($definition, $id, \DOMElement $parent)
195195
}
196196

197197
if ($definition->isAutowired()) {
198-
$service->setAttribute('autowire', 'true');
198+
$service->setAttribute('autowire', Definition::AUTOWIRE_BY_TYPE === $definition->getAutowired() ? 'by-type' : 'by-id');
199199
}
200200

201201
foreach ($definition->getAutowiringTypes(false) as $autowiringTypeValue) {

src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ private function addService($id, $definition)
106106
}
107107

108108
if ($definition->isAutowired()) {
109-
$code .= " autowire: true\n";
109+
$code .= sprintf(" autowire: %s\n", Definition::AUTOWIRE_BY_TYPE === $definition->getAutowired() ? 'by_type' : 'by_id');
110110
}
111111

112112
$autowiringTypesCode = '';

src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ private function getServiceDefaults(\DOMDocument $xml, $file)
172172
}
173173
}
174174
if ($defaultsNode->hasAttribute('autowire')) {
175-
$defaults['autowire'] = XmlUtils::phpize($defaultsNode->getAttribute('autowire'));
175+
$defaults['autowire'] = $this->getAutowired($defaultsNode->getAttribute('autowire'), $file);
176176
}
177177
if ($defaultsNode->hasAttribute('public')) {
178178
$defaults['public'] = XmlUtils::phpize($defaultsNode->getAttribute('public'));
@@ -238,7 +238,7 @@ private function parseDefinition(\DOMElement $service, $file, array $defaults =
238238
}
239239

240240
if ($value = $service->getAttribute('autowire')) {
241-
$definition->setAutowired(XmlUtils::phpize($value));
241+
$definition->setAutowired($this->getAutowired($value, $file));
242242
} elseif (isset($defaults['autowire'])) {
243243
$definition->setAutowired($defaults['autowire']);
244244
}
@@ -665,6 +665,23 @@ private function loadFromExtensions(\DOMDocument $xml)
665665
}
666666
}
667667

668+
private function getAutowired($value, $file)
669+
{
670+
if (is_bool($value = XmlUtils::phpize($value))) {
671+
return $value;
672+
}
673+
674+
if ('by-type' === $value) {
675+
return Definition::AUTOWIRE_BY_TYPE;
676+
}
677+
678+
if ('by-id' === $value) {
679+
return Definition::AUTOWIRE_BY_ID;
680+
}
681+
682+
throw new InvalidArgumentException(sprintf('Invalid autowire attribute: "by-type", "by-id", "true" or "false" expected, "%s" given in "%s".', $value, $file));
683+
}
684+
668685
/**
669686
* Converts a \DomElement object to a PHP array.
670687
*

src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,14 @@ private function parseDefinition($id, $service, $file, array $defaults)
493493

494494
$autowire = isset($service['autowire']) ? $service['autowire'] : (isset($defaults['autowire']) ? $defaults['autowire'] : null);
495495
if (null !== $autowire) {
496+
if ('by_type' === $autowire) {
497+
$autowire = Definition::AUTOWIRE_BY_TYPE;
498+
} elseif ('by_id' === $autowire) {
499+
$autowire = Definition::AUTOWIRE_BY_ID;
500+
} elseif (!is_bool($autowire)) {
501+
throw new InvalidArgumentException(sprintf('Invalid autowire attribute: "by_type", "by_id", true or false expected, "%s" given in "%s".', is_string($autowire) ? $autowire : gettype($autowire), $file));
502+
}
503+
496504
$definition->setAutowired($autowire);
497505
}
498506

src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@
102102
<xsd:element name="tag" type="tag" minOccurs="0" maxOccurs="unbounded" />
103103
</xsd:choice>
104104
<xsd:attribute name="public" type="boolean" />
105-
<xsd:attribute name="autowire" type="boolean" />
105+
<xsd:attribute name="autowire" type="autowire" />
106106
<xsd:attribute name="inherit-tags" type="boolean" />
107107
</xsd:complexType>
108108

@@ -130,7 +130,7 @@
130130
<xsd:attribute name="decorates" type="xsd:string" />
131131
<xsd:attribute name="decoration-inner-name" type="xsd:string" />
132132
<xsd:attribute name="decoration-priority" type="xsd:integer" />
133-
<xsd:attribute name="autowire" type="boolean" />
133+
<xsd:attribute name="autowire" type="autowire" />
134134
<xsd:attribute name="inherit-tags" type="boolean" />
135135
</xsd:complexType>
136136

@@ -149,7 +149,7 @@
149149
<xsd:attribute name="public" type="boolean" />
150150
<xsd:attribute name="lazy" type="boolean" />
151151
<xsd:attribute name="abstract" type="boolean" />
152-
<xsd:attribute name="autowire" type="boolean" />
152+
<xsd:attribute name="autowire" type="autowire" />
153153
</xsd:complexType>
154154

155155
<xsd:complexType name="prototype">
@@ -169,7 +169,7 @@
169169
<xsd:attribute name="lazy" type="boolean" />
170170
<xsd:attribute name="abstract" type="boolean" />
171171
<xsd:attribute name="parent" type="xsd:string" />
172-
<xsd:attribute name="autowire" type="boolean" />
172+
<xsd:attribute name="autowire" type="autowire" />
173173
<xsd:attribute name="inherit-tags" type="boolean" />
174174
</xsd:complexType>
175175

@@ -264,4 +264,10 @@
264264
<xsd:pattern value="(%.+%|true|false)" />
265265
</xsd:restriction>
266266
</xsd:simpleType>
267+
268+
<xsd:simpleType name="autowire">
269+
<xsd:restriction base="xsd:string">
270+
<xsd:pattern value="(true|false|by-type|by-id)" />
271+
</xsd:restriction>
272+
</xsd:simpleType>
267273
</xsd:schema>

src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\DependencyInjection\Compiler\AutowirePass;
1616
use Symfony\Component\DependencyInjection\ContainerBuilder;
17+
use Symfony\Component\DependencyInjection\Definition;
1718
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
1819
use Symfony\Component\DependencyInjection\Reference;
1920
use Symfony\Component\DependencyInjection\Tests\Fixtures\includes\FooVariadic;
@@ -709,6 +710,53 @@ public function testAlternatives()
709710
$pass = new AutowirePass();
710711
$pass->process($container);
711712
}
713+
714+
public function testById()
715+
{
716+
$container = new ContainerBuilder();
717+
718+
$container->register(A::class, A::class);
719+
$container->register(DInterface::class, F::class);
720+
$container->register('d', D::class)
721+
->setAutowired(Definition::AUTOWIRE_BY_ID);
722+
723+
$pass = new AutowirePass();
724+
$pass->process($container);
725+
726+
$this->assertSame(array('service_container', A::class, DInterface::class, 'd'), array_keys($container->getDefinitions()));
727+
$this->assertEquals(array(new Reference(A::class), new Reference(DInterface::class)), $container->getDefinition('d')->getArguments());
728+
}
729+
730+
public function testByIdDoesNotAutoregister()
731+
{
732+
$container = new ContainerBuilder();
733+
734+
$container->register('f', F::class);
735+
$container->register('e', E::class)
736+
->setAutowired(Definition::AUTOWIRE_BY_ID);
737+
738+
$pass = new AutowirePass();
739+
$pass->process($container);
740+
741+
$this->assertSame(array('service_container', 'f', 'e'), array_keys($container->getDefinitions()));
742+
}
743+
744+
/**
745+
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
746+
* @expectedExceptionMessage Cannot autowire service "j": argument $i of method Symfony\Component\DependencyInjection\Tests\Compiler\J::__construct() references class "Symfony\Component\DependencyInjection\Tests\Compiler\I" but no such service exists. This type-hint could be aliased to the existing "i" service; or be updated to "Symfony\Component\DependencyInjection\Tests\Compiler\IInterface".
747+
*/
748+
public function testByIdAlternative()
749+
{
750+
$container = new ContainerBuilder();
751+
752+
$container->setAlias(IInterface::class, 'i');
753+
$container->register('i', I::class);
754+
$container->register('j', J::class)
755+
->setAutowired(Definition::AUTOWIRE_BY_ID);
756+
757+
$pass = new AutowirePass();
758+
$pass->process($container);
759+
}
712760
}
713761

714762
class Foo

src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services24.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public function isFrozen()
7070
* This service is shared.
7171
* This method always returns the same instance of the service.
7272
*
73-
* This service is autowired.
73+
* This service is autowired by types.
7474
*
7575
* @return \Foo A Foo instance
7676
*/

src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_subscriber.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ protected function getTestServiceSubscriberService()
9191
* This service is shared.
9292
* This method always returns the same instance of the service.
9393
*
94-
* This service is autowired.
94+
* This service is autowired by types.
9595
*
9696
* @return \TestServiceSubscriber A TestServiceSubscriber instance
9797
*/
@@ -118,7 +118,7 @@ protected function getFooServiceService()
118118
* If you want to be able to request this service from the container directly,
119119
* make it public, otherwise you might end up with broken code.
120120
*
121-
* This service is autowired.
121+
* This service is autowired by types.
122122
*
123123
* @return \stdClass A stdClass instance
124124
*/

src/Symfony/Component/DependencyInjection/Tests/Fixtures/xml/services24.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<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">
33
<services>
44
<service id="service_container" class="Symfony\Component\DependencyInjection\ContainerInterface" synthetic="true"/>
5-
<service id="foo" class="Foo" autowire="true"/>
5+
<service id="foo" class="Foo" autowire="by-type"/>
66
<service id="Psr\Container\ContainerInterface" alias="service_container" public="false"/>
77
<service id="Symfony\Component\DependencyInjection\ContainerInterface" alias="service_container" public="false"/>
88
</services>

src/Symfony/Component/DependencyInjection/Tests/Fixtures/yaml/services24.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ services:
55
synthetic: true
66
foo:
77
class: Foo
8-
autowire: true
8+
autowire: by_type
99
Psr\Container\ContainerInterface:
1010
alias: service_container
1111
public: false

0 commit comments

Comments
 (0)