8000 [DI] Add getter injection · symfony/symfony@6b2cca4 · GitHub
[go: up one dir, main page]

Skip to content

Commit 6b2cca4

Browse files
[DI] Add getter injection
1 parent 16d33e1 commit 6b2cca4

33 files changed

+822
-47
lines changed

src/Symfony/Component/DependencyInjection/CHANGELOG.md

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

7+
* added support for getter-injection
78
* added "iterator" argument type for lazy iteration over a set of values and services
89
* added "closure-proxy" argument type for turning services' methods into lazy callables
910
* added file-wide configurable defaults for service attributes "public", "tags",

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ public function process(ContainerBuilder $container)
7676

7777
if (!$this->onlyConstructorArguments) {
7878
$this->processArguments($definition->getMethodCalls());
79+
$this->processArguments($definition->getOverriddenGetters());
7980
$this->processArguments($definition->getProperties());
8081
if ($definition->getConfigurator()) {
8182
$this->processArguments(array($definition->getConfigurator()));
@@ -115,6 +116,7 @@ private function processArguments(array $arguments, $lazy = false)
115116
} elseif ($argument instanceof Definition) {
116117
$this->processArguments($argument->getArguments());
117118
$this->processArguments($argument->getMethodCalls());
119+
$this->processArguments($argument->getOverriddenGetters());
118120
$this->processArguments($argument->getProperties());
119121

120122
if (is_array($argument->getFactory())) {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ private function processDefinition(Definition $definition)
4242
{
4343
$this->processReferences($definition->getArguments());
4444
$this->processReferences($definition->getMethodCalls());
45+
$this->processReferences($definition->getOverriddenGetters());
4546
$this->processReferences($definition->getProperties());
4647
}
4748

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public function process(ContainerBuilder $container)
4848

4949
$this->validateReferences($definition->getArguments());
5050
$this->validateReferences($definition->getMethodCalls());
51+
$this->validateReferences($definition->getOverriddenGetters());
5152
$this->validateReferences($definition->getProperties());
5253
}
5354
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ private function inlineArguments(ContainerBuilder $container, array $arguments,
8787
} elseif ($argument instanceof Definition) {
8888
$argument->setArguments($this->inlineArguments($container, $argument->getArguments()));
8989
$argument->setMethodCalls($this->inlineArguments($container, $argument->getMethodCalls()));
90+
$argument->setOverriddenGetters($this->inlineArguments($container, $argument->getOverriddenGetters()));
9091
$argument->setProperties($this->inlineArguments($container, $argument->getProperties()));
9192

9293
$configurator = $this->inlineArguments($container, array($argument->getConfigurator()));

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ public function process(ContainerBuilder $container)
7777
foreach ($container->getDefinitions() as $definitionId => $definition) {
7878
$definition->setArguments($this->updateArgumentReferences($replacements, $definitionId, $definition->getArguments()));
7979
$definition->setMethodCalls($this->updateArgumentReferences($replacements, $definitionId, $definition->getMethodCalls()));
80+
$definition->setOverriddenGetters($this->updateArgumentReferences($replacements, $definitionId, $definition->getOverriddenGetters()));
8081
$definition->setProperties($this->updateArgumentReferences($replacements, $definitionId, $definition->getProperties()));
8182
$definition->setFactory($this->updateFactoryReference($replacements, $definition->getFactory()));
8283
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ private function resolveArguments(ContainerBuilder $container, array $arguments,
7575
}
7676
$argument->setArguments($this->resolveArguments($container, $argument->getArguments()));
7777
$argument->setMethodCalls($this->resolveArguments($container, $argument->getMethodCalls()));
78+
$argument->setOverriddenGetters($this->resolveArguments($container, $argument->getOverriddenGetters()));
7879
$argument->setProperties($this->resolveArguments($container, $argument->getProperties()));
7980

8081
$configurator = $this->resolveArguments($container, array($argument->getConfigurator()));
@@ -134,6 +135,7 @@ private function doResolveDefinition(ContainerBuilder $container, ChildDefinitio
134135
$def->setClass($parentDef->getClass());
135136
$def->setArguments($parentDef->getArguments());
136137
$def->setMethodCalls($parentDef->getMethodCalls());
138+
$def->setOverriddenGetters($parentDef->getOverriddenGetters());
137139
$def->setProperties($parentDef->getProperties());
138140
$def->setAutowiringTypes($parentDef->getAutowiringTypes());
139141
if ($parentDef->isDeprecated()) {
@@ -206,6 +208,11 @@ private function doResolveDefinition(ContainerBuilder $container, ChildDefinitio
206208
$def->setMethodCalls(array_merge($def->getMethodCalls(), $calls));
207209
}
208210

211+
// merge overridden getters
212+
foreach ($definition->getOverriddenGetters() as $k => $v) {
213+
$def->setOverriddenGetter($k, $v);
214+
}
215+
209216
// merge autowiring types
210217
foreach ($definition->getAutowiringTypes() as $autowiringType) {
211218
$def->addAutowiringType($autowiringType);

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,17 @@ public function process(ContainerBuilder $container)
5454
}
5555
$definition->setMethodCalls($calls);
5656

57+
$getters = array();
58+
foreach ($definition->getOverriddenGetters() as $name => $value) {
59+
try {
60+
$value = $this->processArguments(array($value), true);
61+
$getters[$name] = reset($value);
62+
} catch (RuntimeException $e) {
63+
// this call is simply removed
64+
}
65+
}
66+
$definition->setOverriddenGetters($getters);
67+
5768
$properties = array();
5869
foreach ($definition->getProperties() as $name => $value) {
5970
try {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public function process(ContainerBuilder $container)
5151
}
5252
$definition->setMethodCalls($calls);
5353

54+
$definition->setOverriddenGetters($parameterBag->resolveValue($definition->getOverriddenGetters()));
5455
$definition->setProperties($parameterBag->resolveValue($definition->getProperties()));
5556
} catch (ParameterNotFoundException $e) {
5657
$e->setSourceId($id);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public function process(ContainerBuilder $container)
4242

4343
$definition->setArguments($this->processArguments($definition->getArguments()));
4444
$definition->setMethodCalls($this->processArguments($definition->getMethodCalls()));
45+
$definition->setOverriddenGetters($this->processArguments($definition->getOverriddenGetters()));
4546
$definition->setProperties($this->processArguments($definition->getProperties()));
4647
$definition->setFactory($this->processFactory($definition->getFactory()));
4748
}

src/Symfony/Component/DependencyInjection/ContainerBuilder.php

Lines changed: 122 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
use Symfony\Component\Config\Resource\ResourceInterface;
3030
use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface;
3131
use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\RealServiceInstantiator;
32+
use Symfony\Component\DependencyInjection\LazyProxy\GetterProxyInterface;
3233
use Symfony\Component\ExpressionLanguage\Expression;
3334
use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
3435

@@ -923,6 +924,9 @@ private function createService(Definition $definition, $id, $tryProxy = true)
923924
$arguments = $this->resolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getArguments())));
924925

925926
if (null !== $factory = $definition->getFactory()) {
927+
if ($definition->getOverriddenGetters()) {
928+
throw new RuntimeException(sprintf('Cannot create service "%s": factories and overridden getters are incompatible with each other.', $id));
929+
}
926930
if (is_array($factory)) {
927931
$factory = array($this->resolveServices($parameterBag->resolveValue($factory[0])), $factory[1]);
928932
} elseif (!is_string($factory)) {
@@ -941,11 +945,31 @@ private function createService(Definition $definition, $id, $tryProxy = true)
941945
} else {
942946
$r = new \ReflectionClass($parameterBag->resolveValue($definition->getClass()));
943947

944-
$service = null === $r->getConstructor() ? $r->newInstance() : $r->newInstanceArgs($arguments);
945-
946948
if (!$definition->isDeprecated() && 0 < strpos($r->getDocComment(), "\n * @deprecated ")) {
947949
@trigger_error(sprintf('The "%s" service relies on the deprecated "%s" class. It should either be deprecated or its implementation upgraded.', $id, $r->name), E_USER_DEPRECATED);
948950
}
951+
if ($definition->getOverriddenGetters()) {
952+
static $salt;
953+
if (null === $salt) {
954+
$salt = str_replace('.', '', uniqid('', true));
955+
}
956+
$service = sprintf('%s implements \\%s { private $container%4$s; private $values%4$s; %s }', $r->name, GetterProxyInterface::class, $this->generateOverriddenGetters($id, $definition, $r, $salt), $salt);
957+
if (!class_exists($proxyClass = 'SymfonyProxy_'.md5($service), false)) {
958+
eval(sprintf('class %s extends %s', $proxyClass, $service));
959+
}
960+
$r = new \ReflectionClass($proxyClass);
961+
$constructor = $r->getConstructor();
962+
if ($constructor && !defined('HHVM_VERSION') && $constructor->getDeclaringClass()->isInternal()) {
963+
$constructor = null;
964+
}
965+
$service = $constructor ? $r->newInstanceWithoutConstructor() : $r->newInstanceArgs($arguments);
966+
call_user_func(\Closure::bind(function ($c, $v, $s) { $this->{'container'.$s} = $c; $this->{'values'.$s} = $v; }, $service, $service), $this, $definition->getOverriddenGetters(), $salt);
967+
if ($constructor) {
968+
$constructor->invokeArgs($service, $arguments);
969+
}
970+
} else {
971+
$service = null === $r->getConstructor() ? $r->newInstance() : $r->newInstanceArgs($arguments);
972+
}
949973
}
950974

951975
if ($tryProxy || !$definition->isLazy()) {
@@ -1188,6 +1212,102 @@ public function getEnvCounters()
11881212
return $this->envCounters;
11891213
}
11901214

1215+
private function generateOverriddenGetters($id, Definition $definition, \ReflectionClass $class, $salt)
1216+
{
1217+
if ($class->isFinal()) {
1218+
throw new RuntimeException(sprintf('Unable to configure getter injection for service "%s": class "%s" cannot be marked as final.', $id, $class->name));
1219+
}
1220+
$getters = '';
1221+
foreach ($definition->getOverriddenGetters() as $name => $returnValue) {
1222+
$r = self::getGetterReflector($class, $name, $id, $type);
1223+
$visibility = $r->isProtected() ? 'protected' : 'public';
1224+
$name = var_export($name, true);
1225+
$getters .= <<<EOF
1226+
1227+
{$visibility} function {$r->name}(){$type} {
1228+
\$c = \$this->container{$salt};
1229+
\$b = \$c->getParameterBag();
1230+
\$v = \$this->values{$salt}[{$name}];
1231+
1232+
foreach (\$c->getServiceConditionals(\$v) as \$s) {
1233+
if (!\$c->has(\$s)) {
1234+
return parent::{$r->name}();
1235+
}
1236+
}
1237+
1238+
return \$c->resolveServices(\$b->unescapeValue(\$b->resolveValue(\$v)));
1239+
}
1240+
EOF;
1241+
}
1242+
1243+
return $getters;
1244+
}
1245+
1246+
/**
1247+
* @internal
1248+
*/
1249+
public static function getGetterReflector(\ReflectionClass $class, $name, $id, &$type)
1250+
{
1251+
if (!$class->hasMethod($name)) {
1252+
throw new RuntimeException(sprintf('Unable to configure getter injection for service "%s": method "%s::%s" does not exist.', $id, $class->name, $name));
1253+
}
1254+
$r = $class->getMethod($name);
1255+
if ($r->isPrivate()) {
1256+
throw new RuntimeException(sprintf('Unable to configure getter injection for service "%s": method "%s::%s" must be public or protected.', $id, $class->name, $r->name));
1257+
}
1258+
if ($r->isStatic()) {
1259+
throw new RuntimeException(sprintf('Unable to configure getter injection for service "%s": method "%s::%s" cannot be static.', $id, $class->name, $r->name));
1260+
}
1261+
if ($r->isFinal()) {
1262+
throw new RuntimeException(sprintf('Unable to configure getter injection for service "%s": method "%s::%s" cannot be marked as final.', $id, $class->name, $r->name));
1263+
}
1264+
if ($r->returnsReference()) {
1265+
throw new RuntimeException(sprintf('Unable to configure getter injection for service "%s": method "%s::%s" cannot return by reference.', $id, $class->name, $r->name));
1266+
}
1267+
if (0 < $r->getNumberOfParameters()) {
1268+
throw new RuntimeException(sprintf('Unable to configure getter injection for service "%s": method "%s::%s" cannot have any arguments.', $id, $class->name, $r->name));
1269+
}
1270+
if ($type = method_exists($r, 'getReturnType') ? $r->getReturnType() : null) {
1271+
$type = ': '.($type->allowsNull() ? '?' : '').self::generateTypeHint($type, $r);
1272+
}
1273+
1274+
return $r;
1275+
}
1276+
1277+
/**
1278+
* @internal
1279+
*/
1280+
public static function generateTypeHint($type, \ReflectionFunctionAbstract $r)
1281+
{
1282+
if (is_string($type)) {
1283+
$name = $type;
1284+
1285+
if ('callable' === $name || 'array' === $name) {
1286+
return $name;
1287+
}
1288+
} else {
1289+
$name = $type instanceof \ReflectionNamedType ? $type->getName() : $type->__toString();
1290+
1291+
if ($type->isBuiltin()) {
1292+
return $name;
1293+
}
1294+
}
1295+
$lcName = strtolower($name);
1296+
1297+
if ('self' !== $lcName && 'parent' !== $lcName) {
1298+
return '\\'.$name;
1299+
}
1300+
if (!$r instanceof \ReflectionMethod) {
1301+
return;
1302+
}
1303+
if ('self' === $lcName) {
1304+
return '\\'.$r->getDeclaringClass()->name;
1305+
}
1306+
if ($parent = $r->getDeclaringClass()->getParentClass()) {
1307+
return '\\'.$parent->name;
1308+
}
1309+
}
1310+
11911311
/**
11921312
* Returns the Service Conditionals.
11931313
*

src/Symfony/Component/DependencyInjection/Definition.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class Definition
2929
private $deprecationTemplate = 'The "%service_id%" service is deprecated. You should stop using it, as it will soon be removed.';
3030
private $properties = array();
3131
private $calls = array();
32+
private $getters = array();
3233
private $configurator;
3334
private $tags = array();
3435
private $public = true;
@@ -319,6 +320,28 @@ public function getMethodCalls()
319320
return $this->calls;
320321
}
321322

323+
public function setOverriddenGetter($name, $returnValue)
324+
{
325+
if (!$name) {
326+
throw new InvalidArgumentException(sprintf('Getter name cannot be empty.'));
327+
}
328+
$this->getters[strtolower($name)] = $returnValue;
329+
330+
return $this;
331+
}
332+
333+
public function setOverriddenGetters(array $getters)
334+
{
335+
$this->getters = array_change_key_case($getters, CASE_LOWER);
336+
337+
return $this;
338+
}
339+
340+
public function getOverriddenGetters()
341+
{
342+
return $this->getters;
343+
}
344+
322345
/**
323346
* Sets tags for this definition.
324347
*

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,13 @@ public function dump(array $options = array())
8080
$this->findEdges($id, $call[1], false, $call[0].'()')
8181
);
8282
}
83+
84+
foreach ($definition->getOverriddenGetters() as $name => $value) {
85+
$this->edges[$id] = array_merge(
86+
$this->edges[$id],
87+
$this->findEdges($id, $value, false, $name.'()')
88+
);
89+
}
8390
}
8491

8592
return $this->container->resolveEnvPlaceholders($this->startDot().$this->addNodes().$this->addEdges().$this->endDot(), '__ENV_%s__');

0 commit comments

Comments
 (0)
0