feature #30212 [DI] Add support for "wither" methods - for greater immutable services (nicolas-grekas)
This PR was merged into the 4.3-dev branch.
Discussion
----------
[DI] Add support for "wither" methods - for greater immutable services
| Q | A
| ------------- | ---
| Branch? | master
| Bug fix? | no
| New feature? | yes
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | -
| License | MIT
| Doc PR | https://github.com/symfony/symfony-docs/pull/10991
Let's say we want to define an immutable service while still using traits for composing its optional features. A nice way to do so without hitting [the downsides of setters](https://symfony.com/doc/current/service_container/injection_types.html#setter-injection) is to use withers. Here would be an example:
```php
class MyService
{
use LoggerAwareTrait;
}
trait LoggerAwareTrait
{
private $logger;
/**
* @required
* @return static
*/
public function withLogger(LoggerInterface $logger)
{
$new = clone $this;
$new->logger = $logger;
return $new;
}
}
$service = new MyService();
$service = $service->withLogger($logger);
```
As you can see, this nicely solves the setter issues.
BUT how do you make the service container create such a service? Right now, you need to resort to complex gymnastic using the "factory" setting - manageable for only one wither, but definitely not when more are involved and not compatible with autowiring.
So here we are: this PR allows configuring such services seamlessly.
Using explicit configuration, it adds a 3rd parameter to method calls configuration: after the method name and its parameters, you can pass `true` and done, you just declared a wither:
```yaml
services:
MyService:
calls:
- [withLogger, ['@logger'], true]
```
In XML, you could use the new `returns-clone` attribute on the `<call>` tag.
And when using autowiring, the code looks for the `@return static` annotation and turns the flag on if found.
There is only one limitation: unlike services with regular setters, services with withers cannot be part of circular loops that involve calls to wither methods (unless they're declared lazy of course).
Commits
-------
f455d1bd97
[DI] Add support for "wither" methods - for greater immutable services
This commit is contained in:
commit
539f4ca162
@ -357,6 +357,9 @@ class XmlDescriptor extends Descriptor
|
|||||||
foreach ($calls as $callData) {
|
foreach ($calls as $callData) {
|
||||||
$callsXML->appendChild($callXML = $dom->createElement('call'));
|
$callsXML->appendChild($callXML = $dom->createElement('call'));
|
||||||
$callXML->setAttribute('method', $callData[0]);
|
$callXML->setAttribute('method', $callData[0]);
|
||||||
|
if ($callData[2] ?? false) {
|
||||||
|
$callXML->setAttribute('returns-clone', 'true');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,11 +140,41 @@ class AnalyzeServiceReferencesPass extends AbstractRecursivePass implements Repe
|
|||||||
$this->byConstructor = true;
|
$this->byConstructor = true;
|
||||||
$this->processValue($value->getFactory());
|
$this->processValue($value->getFactory());
|
||||||
$this->processValue($value->getArguments());
|
$this->processValue($value->getArguments());
|
||||||
|
|
||||||
|
$properties = $value->getProperties();
|
||||||
|
$setters = $value->getMethodCalls();
|
||||||
|
|
||||||
|
// Any references before a "wither" are part of the constructor-instantiation graph
|
||||||
|
$lastWitherIndex = null;
|
||||||
|
foreach ($setters as $k => $call) {
|
||||||
|
if ($call[2] ?? false) {
|
||||||
|
$lastWitherIndex = $k;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== $lastWitherIndex) {
|
||||||
|
$this->processValue($properties);
|
||||||
|
$setters = $properties = [];
|
||||||
|
|
||||||
|
foreach ($value->getMethodCalls() as $k => $call) {
|
||||||
|
if (null === $lastWitherIndex) {
|
||||||
|
$setters[] = $call;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($lastWitherIndex === $k) {
|
||||||
|
$lastWitherIndex = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->processValue($call);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$this->byConstructor = $byConstructor;
|
$this->byConstructor = $byConstructor;
|
||||||
|
|
||||||
if (!$this->onlyConstructorArguments) {
|
if (!$this->onlyConstructorArguments) {
|
||||||
$this->processValue($value->getProperties());
|
$this->processValue($properties);
|
||||||
$this->processValue($value->getMethodCalls());
|
$this->processValue($setters);
|
||||||
$this->processValue($value->getConfigurator());
|
$this->processValue($value->getConfigurator());
|
||||||
}
|
}
|
||||||
$this->lazy = $lazy;
|
$this->lazy = $lazy;
|
||||||
|
@ -35,6 +35,7 @@ class AutowireRequiredMethodsPass extends AbstractRecursivePass
|
|||||||
}
|
}
|
||||||
|
|
||||||
$alreadyCalledMethods = [];
|
$alreadyCalledMethods = [];
|
||||||
|
$withers = [];
|
||||||
|
|
||||||
foreach ($value->getMethodCalls() as list($method)) {
|
foreach ($value->getMethodCalls() as list($method)) {
|
||||||
$alreadyCalledMethods[strtolower($method)] = true;
|
$alreadyCalledMethods[strtolower($method)] = true;
|
||||||
@ -50,7 +51,11 @@ class AutowireRequiredMethodsPass extends AbstractRecursivePass
|
|||||||
while (true) {
|
while (true) {
|
||||||
if (false !== $doc = $r->getDocComment()) {
|
if (false !== $doc = $r->getDocComment()) {
|
||||||
if (false !== stripos($doc, '@required') && preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@required(?:\s|\*/$)#i', $doc)) {
|
if (false !== stripos($doc, '@required') && preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@required(?:\s|\*/$)#i', $doc)) {
|
||||||
$value->addMethodCall($reflectionMethod->name);
|
if (preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@return\s++static[\s\*]#i', $doc)) {
|
||||||
|
$withers[] = [$reflectionMethod->name, [], true];
|
||||||
|
} else {
|
||||||
|
$value->addMethodCall($reflectionMethod->name, []);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (false === stripos($doc, '@inheritdoc') || !preg_match('#(?:^/\*\*|\n\s*+\*)\s*+(?:\{@inheritdoc\}|@inheritdoc)(?:\s|\*/$)#i', $doc)) {
|
if (false === stripos($doc, '@inheritdoc') || !preg_match('#(?:^/\*\*|\n\s*+\*)\s*+(?:\{@inheritdoc\}|@inheritdoc)(?:\s|\*/$)#i', $doc)) {
|
||||||
@ -65,6 +70,15 @@ class AutowireRequiredMethodsPass extends AbstractRecursivePass
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($withers) {
|
||||||
|
// Prepend withers to prevent creating circular loops
|
||||||
|
$setters = $value->getMethodCalls();
|
||||||
|
$value->setMethodCalls($withers);
|
||||||
|
foreach ($setters as $call) {
|
||||||
|
$value->addMethodCall($call[0], $call[1], $call[2] ?? false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1139,8 +1139,15 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($tryProxy || !$definition->isLazy()) {
|
$lastWitherIndex = null;
|
||||||
// share only if proxying failed, or if not a proxy
|
foreach ($definition->getMethodCalls() as $k => $call) {
|
||||||
|
if ($call[2] ?? false) {
|
||||||
|
$lastWitherIndex = $k;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $lastWitherIndex && ($tryProxy || !$definition->isLazy())) {
|
||||||
|
// share only if proxying failed, or if not a proxy, and if no withers are found
|
||||||
$this->shareService($definition, $service, $id, $inlineServices);
|
$this->shareService($definition, $service, $id, $inlineServices);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1149,8 +1156,13 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
|
|||||||
$service->$name = $value;
|
$service->$name = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($definition->getMethodCalls() as $call) {
|
foreach ($definition->getMethodCalls() as $k => $call) {
|
||||||
$this->callMethod($service, $call, $inlineServices);
|
$service = $this->callMethod($service, $call, $inlineServices);
|
||||||
|
|
||||||
|
if ($lastWitherIndex === $k && ($tryProxy || !$definition->isLazy())) {
|
||||||
|
// share only if proxying failed, or if not a proxy, and this is the last wither
|
||||||
|
$this->shareService($definition, $service, $id, $inlineServices);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($callable = $definition->getConfigurator()) {
|
if ($callable = $definition->getConfigurator()) {
|
||||||
@ -1569,16 +1581,18 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
|
|||||||
{
|
{
|
||||||
foreach (self::getServiceConditionals($call[1]) as $s) {
|
foreach (self::getServiceConditionals($call[1]) as $s) {
|
||||||
if (!$this->has($s)) {
|
if (!$this->has($s)) {
|
||||||
return;
|
return $service;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach (self::getInitializedConditionals($call[1]) as $s) {
|
foreach (self::getInitializedConditionals($call[1]) as $s) {
|
||||||
if (!$this->doGet($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE, $inlineServices)) {
|
if (!$this->doGet($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE, $inlineServices)) {
|
||||||
return;
|
return $service;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$service->{$call[0]}(...$this->doResolveServices($this->getParameterBag()->unescapeValue($this->getParameterBag()->resolveValue($call[1])), $inlineServices));
|
$result = $service->{$call[0]}(...$this->doResolveServices($this->getParameterBag()->unescapeValue($this->getParameterBag()->resolveValue($call[1])), $inlineServices));
|
||||||
|
|
||||||
|
return empty($call[2]) ? $service : $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -330,7 +330,7 @@ class Definition
|
|||||||
{
|
{
|
||||||
$this->calls = [];
|
$this->calls = [];
|
||||||
foreach ($calls as $call) {
|
foreach ($calls as $call) {
|
||||||
$this->addMethodCall($call[0], $call[1]);
|
$this->addMethodCall($call[0], $call[1], $call[2] ?? false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
@ -339,19 +339,20 @@ class Definition
|
|||||||
/**
|
/**
|
||||||
* Adds a method to call after service initialization.
|
* Adds a method to call after service initialization.
|
||||||
*
|
*
|
||||||
* @param string $method The method name to call
|
* @param string $method The method name to call
|
||||||
* @param array $arguments An array of arguments to pass to the method call
|
* @param array $arguments An array of arguments to pass to the method call
|
||||||
|
* @param bool $returnsClone Whether the call returns the service instance or not
|
||||||
*
|
*
|
||||||
* @return $this
|
* @return $this
|
||||||
*
|
*
|
||||||
* @throws InvalidArgumentException on empty $method param
|
* @throws InvalidArgumentException on empty $method param
|
||||||
*/
|
*/
|
||||||
public function addMethodCall($method, array $arguments = [])
|
public function addMethodCall($method, array $arguments = []/*, bool $returnsClone = false*/)
|
||||||
{
|
{
|
||||||
if (empty($method)) {
|
if (empty($method)) {
|
||||||
throw new InvalidArgumentException('Method name cannot be empty.');
|
throw new InvalidArgumentException('Method name cannot be empty.');
|
||||||
}
|
}
|
||||||
$this->calls[] = [$method, $arguments];
|
$this->calls[] = 2 < \func_num_args() && \func_get_arg(2) ? [$method, $arguments, true] : [$method, $arguments];
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
@ -506,7 +506,14 @@ EOF;
|
|||||||
$isProxyCandidate = $this->getProxyDumper()->isProxyCandidate($definition);
|
$isProxyCandidate = $this->getProxyDumper()->isProxyCandidate($definition);
|
||||||
$instantiation = '';
|
$instantiation = '';
|
||||||
|
|
||||||
if (!$isProxyCandidate && $definition->isShared() && !isset($this->singleUsePrivateIds[$id])) {
|
$lastWitherIndex = null;
|
||||||
|
foreach ($definition->getMethodCalls() as $k => $call) {
|
||||||
|
if ($call[2] ?? false) {
|
||||||
|
$lastWitherIndex = $k;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$isProxyCandidate && $definition->isShared() && !isset($this->singleUsePrivateIds[$id]) && null === $lastWitherIndex) {
|
||||||
$instantiation = sprintf('$this->%s[\'%s\'] = %s', $this->container->getDefinition($id)->isPublic() ? 'services' : 'privates', $id, $isSimpleInstance ? '' : '$instance');
|
$instantiation = sprintf('$this->%s[\'%s\'] = %s', $this->container->getDefinition($id)->isPublic() ? 'services' : 'privates', $id, $isSimpleInstance ? '' : '$instance');
|
||||||
} elseif (!$isSimpleInstance) {
|
} elseif (!$isSimpleInstance) {
|
||||||
$instantiation = '$instance';
|
$instantiation = '$instance';
|
||||||
@ -563,16 +570,32 @@ EOF;
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function addServiceMethodCalls(Definition $definition, string $variableName = 'instance'): string
|
private function addServiceMethodCalls(Definition $definition, string $variableName, ?string $sharedNonLazyId): string
|
||||||
{
|
{
|
||||||
|
$lastWitherIndex = null;
|
||||||
|
foreach ($definition->getMethodCalls() as $k => $call) {
|
||||||
|
if ($call[2] ?? false) {
|
||||||
|
$lastWitherIndex = $k;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$calls = '';
|
$calls = '';
|
||||||
foreach ($definition->getMethodCalls() as $call) {
|
foreach ($definition->getMethodCalls() as $k => $call) {
|
||||||
$arguments = [];
|
$arguments = [];
|
||||||
foreach ($call[1] as $value) {
|
foreach ($call[1] as $value) {
|
||||||
$arguments[] = $this->dumpValue($value);
|
$arguments[] = $this->dumpValue($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
$calls .= $this->wrapServiceConditionals($call[1], sprintf(" \$%s->%s(%s);\n", $variableName, $call[0], implode(', ', $arguments)));
|
$witherAssignation = '';
|
||||||
|
|
||||||
|
if ($call[2] ?? false) {
|
||||||
|
if (null !== $sharedNonLazyId && $lastWitherIndex === $k) {
|
||||||
|
$witherAssignation = sprintf('$this->%s[\'%s\'] = ', $definition->isPublic() ? 'services' : 'privates', $sharedNonLazyId);
|
||||||
|
}
|
||||||
|
$witherAssignation .= sprintf('$%s = ', $variableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
$calls .= $this->wrapServiceConditionals($call[1], sprintf(" %s\$%s->%s(%s);\n", $witherAssignation, $variableName, $call[0], implode(', ', $arguments)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $calls;
|
return $calls;
|
||||||
@ -814,7 +837,7 @@ EOTXT
|
|||||||
}
|
}
|
||||||
|
|
||||||
$code .= $this->addServiceProperties($inlineDef, $name);
|
$code .= $this->addServiceProperties($inlineDef, $name);
|
||||||
$code .= $this->addServiceMethodCalls($inlineDef, $name);
|
$code .= $this->addServiceMethodCalls($inlineDef, $name, !$this->getProxyDumper()->isProxyCandidate($inlineDef) && $inlineDef->isShared() && !isset($this->singleUsePrivateIds[$id]) ? $id : null);
|
||||||
$code .= $this->addServiceConfigurator($inlineDef, $name);
|
$code .= $this->addServiceConfigurator($inlineDef, $name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,6 +84,9 @@ class XmlDumper extends Dumper
|
|||||||
if (\count($methodcall[1])) {
|
if (\count($methodcall[1])) {
|
||||||
$this->convertParameters($methodcall[1], 'argument', $call);
|
$this->convertParameters($methodcall[1], 'argument', $call);
|
||||||
}
|
}
|
||||||
|
if ($methodcall[2] ?? false) {
|
||||||
|
$call->setAttribute('returns-clone', 'true');
|
||||||
|
}
|
||||||
$parent->appendChild($call);
|
$parent->appendChild($call);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -337,7 +337,7 @@ class XmlFileLoader extends FileLoader
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach ($this->getChildren($service, 'call') as $call) {
|
foreach ($this->getChildren($service, 'call') as $call) {
|
||||||
$definition->addMethodCall($call->getAttribute('method'), $this->getArgumentsAsPhp($call, 'argument', $file));
|
$definition->addMethodCall($call->getAttribute('method'), $this->getArgumentsAsPhp($call, 'argument', $file), XmlUtils::phpize($call->getAttribute('returns-clone')));
|
||||||
}
|
}
|
||||||
|
|
||||||
$tags = $this->getChildren($service, 'tag');
|
$tags = $this->getChildren($service, 'tag');
|
||||||
|
@ -463,15 +463,17 @@ class YamlFileLoader extends FileLoader
|
|||||||
if (isset($call['method'])) {
|
if (isset($call['method'])) {
|
||||||
$method = $call['method'];
|
$method = $call['method'];
|
||||||
$args = isset($call['arguments']) ? $this->resolveServices($call['arguments'], $file) : [];
|
$args = isset($call['arguments']) ? $this->resolveServices($call['arguments'], $file) : [];
|
||||||
|
$returnsClone = $call['returns_clone'] ?? false;
|
||||||
} else {
|
} else {
|
||||||
$method = $call[0];
|
$method = $call[0];
|
||||||
$args = isset($call[1]) ? $this->resolveServices($call[1], $file) : [];
|
$args = isset($call[1]) ? $this->resolveServices($call[1], $file) : [];
|
||||||
|
$returnsClone = $call[2] ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!\is_array($args)) {
|
if (!\is_array($args)) {
|
||||||
throw new InvalidArgumentException(sprintf('The second parameter for function call "%s" must be an array of its arguments for service "%s" in %s. Check your YAML syntax.', $method, $id, $file));
|
throw new InvalidArgumentException(sprintf('The second parameter for function call "%s" must be an array of its arguments for service "%s" in %s. Check your YAML syntax.', $method, $id, $file));
|
||||||
}
|
}
|
||||||
$definition->addMethodCall($method, $args);
|
$definition->addMethodCall($method, $args, $returnsClone);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,6 +243,7 @@
|
|||||||
<xsd:element name="argument" type="argument" maxOccurs="unbounded" />
|
<xsd:element name="argument" type="argument" maxOccurs="unbounded" />
|
||||||
</xsd:choice>
|
</xsd:choice>
|
||||||
<xsd:attribute name="method" type="xsd:string" />
|
<xsd:attribute name="method" type="xsd:string" />
|
||||||
|
<xsd:attribute name="returns-clone" type="boolean" />
|
||||||
</xsd:complexType>
|
</xsd:complexType>
|
||||||
|
|
||||||
<xsd:simpleType name="parameter_type">
|
<xsd:simpleType name="parameter_type">
|
||||||
|
@ -77,4 +77,26 @@ class AutowireRequiredMethodsPassTest extends TestCase
|
|||||||
);
|
);
|
||||||
$this->assertEquals([], $methodCalls[0][1]);
|
$this->assertEquals([], $methodCalls[0][1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testWitherInjection()
|
||||||
|
{
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$container->register(Foo::class);
|
||||||
|
|
||||||
|
$container
|
||||||
|
->register('wither', Wither::class)
|
||||||
|
->setAutowired(true);
|
||||||
|
|
||||||
|
(new ResolveClassPass())->process($container);
|
||||||
|
(new AutowireRequiredMethodsPass())->process($container);
|
||||||
|
|
||||||
|
$methodCalls = $container->getDefinition('wither')->getMethodCalls();
|
||||||
|
|
||||||
|
$expected = [
|
||||||
|
['withFoo1', [], true],
|
||||||
|
['withFoo2', [], true],
|
||||||
|
['setFoo', []],
|
||||||
|
];
|
||||||
|
$this->assertSame($expected, $methodCalls);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\DependencyInjection\Tests;
|
namespace Symfony\Component\DependencyInjection\Tests;
|
||||||
|
|
||||||
|
require_once __DIR__.'/Fixtures/includes/autowiring_classes.php';
|
||||||
require_once __DIR__.'/Fixtures/includes/classes.php';
|
require_once __DIR__.'/Fixtures/includes/classes.php';
|
||||||
require_once __DIR__.'/Fixtures/includes/ProjectExtension.php';
|
require_once __DIR__.'/Fixtures/includes/ProjectExtension.php';
|
||||||
|
|
||||||
@ -36,6 +37,8 @@ use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBa
|
|||||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
|
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
|
||||||
use Symfony\Component\DependencyInjection\Reference;
|
use Symfony\Component\DependencyInjection\Reference;
|
||||||
use Symfony\Component\DependencyInjection\ServiceLocator;
|
use Symfony\Component\DependencyInjection\ServiceLocator;
|
||||||
|
use Symfony\Component\DependencyInjection\Tests\Compiler\Foo;
|
||||||
|
use Symfony\Component\DependencyInjection\Tests\Compiler\Wither;
|
||||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\CaseSensitiveClass;
|
use Symfony\Component\DependencyInjection\Tests\Fixtures\CaseSensitiveClass;
|
||||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition;
|
use Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition;
|
||||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\SimilarArgumentsDummy;
|
use Symfony\Component\DependencyInjection\Tests\Fixtures\SimilarArgumentsDummy;
|
||||||
@ -1565,6 +1568,22 @@ class ContainerBuilderTest extends TestCase
|
|||||||
|
|
||||||
$this->assertSame(['service_container'], array_keys($container->getDefinitions()));
|
$this->assertSame(['service_container'], array_keys($container->getDefinitions()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testWither()
|
||||||
|
{
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$container->register(Foo::class);
|
||||||
|
|
||||||
|
$container
|
||||||
|
->register('wither', Wither::class)
|
||||||
|
->setPublic(true)
|
||||||
|
->setAutowired(true);
|
||||||
|
|
||||||
|
$container->compile();
|
||||||
|
|
||||||
|
$wither = $container->get('wither');
|
||||||
|
$this->assertInstanceOf(Foo::class, $wither->foo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FooClass
|
class FooClass
|
||||||
|
@ -95,10 +95,16 @@ class DefinitionTest extends TestCase
|
|||||||
$this->assertEquals([['foo', ['foo']]], $def->getMethodCalls(), '->getMethodCalls() returns the methods to call');
|
$this->assertEquals([['foo', ['foo']]], $def->getMethodCalls(), '->getMethodCalls() returns the methods to call');
|
||||||
$this->assertSame($def, $def->addMethodCall('bar', ['bar']), '->addMethodCall() implements a fluent interface');
|
$this->assertSame($def, $def->addMethodCall('bar', ['bar']), '->addMethodCall() implements a fluent interface');
|
||||||
$this->assertEquals([['foo', ['foo']], ['bar', ['bar']]], $def->getMethodCalls(), '->addMethodCall() adds a method to call');
|
$this->assertEquals([['foo', ['foo']], ['bar', ['bar']]], $def->getMethodCalls(), '->addMethodCall() adds a method to call');
|
||||||
|
$this->assertSame($def, $def->addMethodCall('foobar', ['foobar'], true), '->addMethodCall() implements a fluent interface with third parameter');
|
||||||
|
$this->assertEquals([['foo', ['foo']], ['bar', ['bar']], ['foobar', ['foobar'], true]], $def->getMethodCalls(), '->addMethodCall() adds a method to call');
|
||||||
$this->assertTrue($def->hasMethodCall('bar'), '->hasMethodCall() returns true if first argument is a method to call registered');
|
$this->assertTrue($def->hasMethodCall('bar'), '->hasMethodCall() returns true if first argument is a method to call registered');
|
||||||
$this->assertFalse($def->hasMethodCall('no_registered'), '->hasMethodCall() returns false if first argument is not a method to call registered');
|
$this->assertFalse($def->hasMethodCall('no_registered'), '->hasMethodCall() returns false if first argument is not a method to call registered');
|
||||||
$this->assertSame($def, $def->removeMethodCall('bar'), '->removeMethodCall() implements a fluent interface');
|
$this->assertSame($def, $def->removeMethodCall('bar'), '->removeMethodCall() implements a fluent interface');
|
||||||
|
$this->assertTrue($def->hasMethodCall('foobar'), '->hasMethodCall() returns true if first argument is a method to call registered');
|
||||||
|
$this->assertSame($def, $def->removeMethodCall('foobar'), '->removeMethodCall() implements a fluent interface');
|
||||||
$this->assertEquals([['foo', ['foo']]], $def->getMethodCalls(), '->removeMethodCall() removes a method to call');
|
$this->assertEquals([['foo', ['foo']]], $def->getMethodCalls(), '->removeMethodCall() removes a method to call');
|
||||||
|
$this->assertSame($def, $def->setMethodCalls([['foobar', ['foobar'], true]]), '->setMethodCalls() implements a fluent interface with third parameter');
|
||||||
|
$this->assertEquals([['foobar', ['foobar'], true]], $def->getMethodCalls(), '->addMethodCall() adds a method to call');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -30,6 +30,8 @@ use Symfony\Component\DependencyInjection\Parameter;
|
|||||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
|
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
|
||||||
use Symfony\Component\DependencyInjection\Reference;
|
use Symfony\Component\DependencyInjection\Reference;
|
||||||
use Symfony\Component\DependencyInjection\ServiceLocator;
|
use Symfony\Component\DependencyInjection\ServiceLocator;
|
||||||
|
use Symfony\Component\DependencyInjection\Tests\Compiler\Foo;
|
||||||
|
use Symfony\Component\DependencyInjection\Tests\Compiler\Wither;
|
||||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition;
|
use Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition;
|
||||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\StubbedTranslator;
|
use Symfony\Component\DependencyInjection\Tests\Fixtures\StubbedTranslator;
|
||||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber;
|
use Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber;
|
||||||
@ -37,6 +39,7 @@ use Symfony\Component\DependencyInjection\TypedReference;
|
|||||||
use Symfony\Component\DependencyInjection\Variable;
|
use Symfony\Component\DependencyInjection\Variable;
|
||||||
use Symfony\Component\ExpressionLanguage\Expression;
|
use Symfony\Component\ExpressionLanguage\Expression;
|
||||||
|
|
||||||
|
require_once __DIR__.'/../Fixtures/includes/autowiring_classes.php';
|
||||||
require_once __DIR__.'/../Fixtures/includes/classes.php';
|
require_once __DIR__.'/../Fixtures/includes/classes.php';
|
||||||
|
|
||||||
class PhpDumperTest extends TestCase
|
class PhpDumperTest extends TestCase
|
||||||
@ -1216,6 +1219,28 @@ class PhpDumperTest extends TestCase
|
|||||||
$container->set('foo5', $foo5 = new \stdClass());
|
$container->set('foo5', $foo5 = new \stdClass());
|
||||||
$this->assertSame($foo5, $locator->get('foo5'));
|
$this->assertSame($foo5, $locator->get('foo5'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testWither()
|
||||||
|
{
|
||||||
|
$container = new ContainerBuilder();
|
||||||
|
$container->register(Foo::class);
|
||||||
|
|
||||||
|
$container
|
||||||
|
->register('wither', Wither::class)
|
||||||
|
->setPublic(true)
|
||||||
|
->setAutowired(true);
|
||||||
|
|
||||||
|
$container->compile();
|
||||||
|
$dumper = new PhpDumper($container);
|
||||||
|
$dump = $dumper->dump(['class' => 'Symfony_DI_PhpDumper_Service_Wither']);
|
||||||
|
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services_wither.php', $dump);
|
||||||
|
eval('?>'.$dump);
|
||||||
|
|
||||||
|
$container = new \Symfony_DI_PhpDumper_Service_Wither();
|
||||||
|
|
||||||
|
$wither = $container->get('wither');
|
||||||
|
$this->assertInstanceOf(Foo::class, $wither->foo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Rot13EnvVarProcessor implements EnvVarProcessorInterface
|
class Rot13EnvVarProcessor implements EnvVarProcessorInterface
|
||||||
|
@ -278,6 +278,39 @@ class SetterInjection extends SetterInjectionParent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Wither
|
||||||
|
{
|
||||||
|
public $foo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @required
|
||||||
|
*/
|
||||||
|
public function setFoo(Foo $foo)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @required
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function withFoo1(Foo $foo)
|
||||||
|
{
|
||||||
|
return $this->withFoo2($foo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @required
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function withFoo2(Foo $foo)
|
||||||
|
{
|
||||||
|
$new = clone $this;
|
||||||
|
$new->foo = $foo;
|
||||||
|
|
||||||
|
return $new;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class SetterInjectionParent
|
class SetterInjectionParent
|
||||||
{
|
{
|
||||||
/** @required*/
|
/** @required*/
|
||||||
|
@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\Container;
|
||||||
|
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
||||||
|
use Symfony\Component\DependencyInjection\Exception\LogicException;
|
||||||
|
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
|
||||||
|
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class has been auto-generated
|
||||||
|
* by the Symfony Dependency Injection Component.
|
||||||
|
*
|
||||||
|
* @final since Symfony 3.3
|
||||||
|
*/
|
||||||
|
class Symfony_DI_PhpDumper_Service_Wither extends Container
|
||||||
|
{
|
||||||
|
private $parameters;
|
||||||
|
private $targetDirs = [];
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->services = $this->privates = [];
|
||||||
|
$this->methodMap = [
|
||||||
|
'wither' => 'getWitherService',
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->aliases = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function compile()
|
||||||
|
{
|
||||||
|
throw new LogicException('You cannot compile a dumped container that was already compiled.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isCompiled()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRemovedIds()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'Psr\\Container\\ContainerInterface' => true,
|
||||||
|
'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true,
|
||||||
|
'Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Foo' => true,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the public 'wither' shared autowired service.
|
||||||
|
*
|
||||||
|
* @return \Symfony\Component\DependencyInjection\Tests\Compiler\Wither
|
||||||
|
*/
|
||||||
|
protected function getWitherService()
|
||||||
|
{
|
||||||
|
$instance = new \Symfony\Component\DependencyInjection\Tests\Compiler\Wither();
|
||||||
|
|
||||||
|
$a = new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo();
|
||||||
|
|
||||||
|
$instance = $instance->withFoo1($a);
|
||||||
|
$this->services['wither'] = $instance = $instance->withFoo2($a);
|
||||||
|
$instance->setFoo($a);
|
||||||
|
|
||||||
|
return $instance;
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user