Skip to content

Commit 444f928

Browse files
committed
feature #35076 [DI] added possibility to define services with abstract arguments (Islam93)
This PR was merged into the 5.1-dev branch. Discussion ---------- [DI] added possibility to define services with abstract arguments | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | Fix #31769 | License | MIT | Doc PR | n/a feature caused by rfc #31769 from issues list I hope, this PR will be useful Abstract argument have to replaced by one of compiler passes or exception will be thrown. Example: This service definition ```xml ... <service id="App\Test\Test"> <argument key="$a" type="abstract">should be defined by TestPass</argument> </service> ... ``` or this for yaml ```yaml App\Test\Test: arguments: $a: !abstract should be defined by TestPass ``` causes exception like `Argument "$a" of service "App\Test\Test" is abstract (should be defined by TestPass), did you forget to define it?` if argument was not replaced by compiler pass ```php ... public function process(ContainerBuilder $container) { $test = $container->getDefinition(Test::class); $test->setArgument('$a', 'test'); } ... ``` Commits ------- 62fefaa59f [DI] added possibility to define services with abstract arguments
2 parents f8c01b0 + 8a5715a commit 444f928

19 files changed

+229
-3
lines changed

Argument/AbstractArgument.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Argument;
13+
14+
/**
15+
* Represents an abstract service argument, which have to be set by a compiler pass or a DI extension.
16+
*/
17+
final class AbstractArgument
18+
{
19+
private $serviceId;
20+
private $argKey;
21+
private $text;
22+
23+
public function __construct(string $serviceId, string $argKey, string $text = '')
24+
{
25+
$this->serviceId = $serviceId;
26+
$this->argKey = $argKey;
27+
$this->text = $text;
28+
}
29+
30+
public function getServiceId(): string
31+
{
32+
return $this->serviceId;
33+
}
34+
35+
public function getArgumentKey(): string
36+
{
37+
return $this->argKey;
38+
}
39+
40+
public function getText(): string
41+
{
42+
return $this->text;
43+
}
44+
}

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ CHANGELOG
66

77
* added support to autowire public typed properties in php 7.4
88
* added support for defining method calls, a configurator, and property setters in `InlineServiceConfigurator`
9+
* added possibility to define abstract service arguments
910

1011
5.0.0
1112
-----

ContainerBuilder.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use Symfony\Component\Config\Resource\GlobResource;
2121
use Symfony\Component\Config\Resource\ReflectionClassResource;
2222
use Symfony\Component\Config\Resource\ResourceInterface;
23+
use Symfony\Component\DependencyInjection\Argument\AbstractArgument;
2324
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
2425
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
2526
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
@@ -1215,6 +1216,8 @@ private function doResolveServices($value, array &$inlineServices = [], bool $is
12151216
$value = $this->getParameter((string) $value);
12161217
} elseif ($value instanceof Expression) {
12171218
$value = $this->getExpressionLanguage()->evaluate($value, ['container' => $this]);
1219+
} elseif ($value instanceof AbstractArgument) {
1220+
throw new RuntimeException(sprintf('Argument "%s" of service "%s" is abstract%s, did you forget to define it?', $value->getArgumentKey(), $value->getServiceId(), $value->getText() ? ' ('.$value->getText().')' : ''));
12181221
}
12191222

12201223
return $value;

Dumper/PhpDumper.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Composer\Autoload\ClassLoader;
1515
use Symfony\Component\Debug\DebugClassLoader as LegacyDebugClassLoader;
16+
use Symfony\Component\DependencyInjection\Argument\AbstractArgument;
1617
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
1718
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
1819
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
@@ -1739,6 +1740,8 @@ private function dumpValue($value, bool $interpolate = true): string
17391740

17401741
return $code;
17411742
}
1743+
} elseif ($value instanceof AbstractArgument) {
1744+
throw new RuntimeException(sprintf('Argument "%s" of service "%s" is abstract%s, did you forget to define it?', $value->getArgumentKey(), $value->getServiceId(), $value->getText() ? ' ('.$value->getText().')' : ''));
17421745
} elseif (\is_object($value) || \is_resource($value)) {
17431746
throw new RuntimeException('Unable to dump a service container if a parameter is an object or a resource.');
17441747
}

Dumper/XmlDumper.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\DependencyInjection\Dumper;
1313

1414
use Symfony\Component\DependencyInjection\Alias;
15+
use Symfony\Component\DependencyInjection\Argument\AbstractArgument;
1516
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
1617
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
1718
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
@@ -312,6 +313,14 @@ private function convertParameters(array $parameters, string $type, \DOMElement
312313
$element->setAttribute('type', 'binary');
313314
$text = $this->document->createTextNode(self::phpToXml(base64_encode($value)));
314315
$element->appendChild($text);
316+
} elseif ($value instanceof AbstractArgument) {
317+
$argKey = $value->getArgumentKey();
318+
if (!is_numeric($argKey)) {
319+
$element->setAttribute('key', $argKey);
320+
}
321+
$element->setAttribute('type', 'abstract');
322+
$text = $this->document->createTextNode(self::phpToXml($value->getText()));
323+
$element->appendChild($text);
315324
} else {
316325
if (\in_array($value, ['null', 'true', 'false'], true)) {
317326
$element->setAttribute('type', 'string');

Dumper/YamlDumper.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\DependencyInjection\Dumper;
1313

1414
use Symfony\Component\DependencyInjection\Alias;
15+
use Symfony\Component\DependencyInjection\Argument\AbstractArgument;
1516
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
1617
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
1718
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
@@ -284,6 +285,8 @@ private function dumpValue($value)
284285
return $this->getExpressionCall((string) $value);
285286
} elseif ($value instanceof Definition) {
286287
return new TaggedValue('service', (new Parser())->parse("_:\n".$this->addService('_', $value), Yaml::PARSE_CUSTOM_TAGS)['_']['_']);
288+
} elseif ($value instanceof AbstractArgument) {
289+
return new TaggedValue('abstract', $value->getText());
287290
} elseif (\is_object($value) || \is_resource($value)) {
288291
throw new RuntimeException('Unable to dump a service container if a parameter is an object or a resource.');
289292
}

Loader/XmlFileLoader.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Component\Config\Util\XmlUtils;
1515
use Symfony\Component\DependencyInjection\Alias;
16+
use Symfony\Component\DependencyInjection\Argument\AbstractArgument;
1617
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
1718
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
1819
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
@@ -537,6 +538,10 @@ private function getArgumentsAsPhp(\DOMElement $node, string $name, string $file
537538
}
538539
$arguments[$key] = $value;
539540
break;
541+
case 'abstract':
542+
$serviceId = $node->getAttribute('id');
543+
$arguments[$key] = new AbstractArgument($serviceId, (string) $key, $arg->nodeValue);
544+
break;
540545
case 'string':
541546
$arguments[$key] = $arg->nodeValue;
542547
break;

Loader/YamlFileLoader.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\DependencyInjection\Loader;
1313

1414
use Symfony\Component\DependencyInjection\Alias;
15+
use Symfony\Component\DependencyInjection\Argument\AbstractArgument;
1516
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
1617
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
1718
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
@@ -446,7 +447,7 @@ private function parseDefinition(string $id, $service, string $file, array $defa
446447
}
447448

448449
if (isset($service['arguments'])) {
449-
$definition->setArguments($this->resolveServices($service['arguments'], $file));
450+
$definition->setArguments($this->resolveServices($service['arguments'], $file, false, $id));
450451
}
451452

452453
if (isset($service['properties'])) {
@@ -722,7 +723,7 @@ private function validate($content, string $file): ?array
722723
*
723724
* @return array|string|Reference|ArgumentInterface
724725
*/
725-
private function resolveServices($value, string $file, bool $isParameter = false)
726+
private function resolveServices($value, string $file, bool $isParameter = false, string $serviceId = '', string $argKey = '')
726727
{
727728
if ($value instanceof TaggedValue) {
728729
$argument = $value->getValue();
@@ -795,13 +796,16 @@ private function resolveServices($value, string $file, bool $isParameter = false
795796

796797
return new Reference($id);
797798
}
799+
if ('abstract' === $value->getTag()) {
800+
return new AbstractArgument($serviceId, $argKey, $value->getValue());
801+
}
798802

799803
throw new InvalidArgumentException(sprintf('Unsupported tag "!%s".', $value->getTag()));
800804
}
801805

802806
if (\is_array($value)) {
803807
foreach ($value as $k => $v) {
804-
$value[$k] = $this->resolveServices($v, $file, $isParameter);
808+
$value[$k] = $this->resolveServices($v, $file, $isParameter, $serviceId, $k);
805809
}
806810
} elseif (\is_string($value) && 0 === strpos($value, '@=')) {
807811
if (!class_exists(Expression::class)) {

Loader/schema/dic/services/services-1.0.xsd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@
260260

261261
<xsd:simpleType name="argument_type">
262262
<xsd:restriction base="xsd:string">
263+
<xsd:enumeration value="abstract" />
263264
<xsd:enumeration value="collection" />
264265
<xsd:enumeration value="service" />
265266
<xsd:enumeration value="expression" />
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Tests\Argument;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\DependencyInjection\Argument\AbstractArgument;
16+
17+
class AbstractArgumentTest extends TestCase
18+
{
19+
public function testAbstractArgumentGetters()
20+
{
21+
$argument = new AbstractArgument('foo', '$bar', 'should be defined by Pass');
22+
$this->assertSame('foo', $argument->getServiceId());
23+
$this->assertSame('$bar', $argument->getArgumentKey());
24+
$this->assertSame('should be defined by Pass', $argument->getText());
25+
}
26+
}

0 commit comments

Comments
 (0)