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

Skip to content

Commit dae5821

Browse files
committed
feature #20973 [DI] Add getter injection (nicolas-grekas)
This PR was merged into the 3.3-dev branch. Discussion ---------- [DI] Add getter injection | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #20657 | License | MIT | Doc PR | symfony/symfony-docs#7300 Getter overriding by the container will allow a new kind of dependency injection which enables easier laziness and more immutable classes, by not requiring any corresponding setter. See linked issue for more. This is WIP: - [x] wire the concept - [x] dump anonymous classes with PhpDumper - [x] generate at runtime in ContainerBuilder::createService - [x] tests - [x] make it work on PHP 5 Commits ------- cb498580d1 [DI] Add getter injection
2 parents 23b672f + 2c3e5c5 commit dae5821

25 files changed

+780
-40
lines changed

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+
* [EXPERIMENTAL] added support for getter-injection
78
* added support for omitting the factory class name in a service definition if the definition class is set
89
* deprecated case insensitivity of service identifiers
910
* added "iterator" argument type for lazy iteration over a set of values and services

Compiler/AbstractRecursivePass.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ protected function processValue($value, $isRoot = false)
6161
} elseif ($value instanceof Definition) {
6262
$value->setArguments($this->processValue($value->getArguments()));
6363
$value->setProperties($this->processValue($value->getProperties()));
64+
$value->setOverriddenGetters($this->processValue($value->getOverriddenGetters()));
6465
$value->setMethodCalls($this->processValue($value->getMethodCalls()));
6566

6667
if ($v = $value->getFactory()) {

Compiler/ResolveDefinitionTemplatesPass.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ private function doResolveDefinition(ChildDefinition $definition)
8888
$def->setClass($parentDef->getClass());
8989
$def->setArguments($parentDef->getArguments());
9090
$def->setMethodCalls($parentDef->getMethodCalls());
91+
$def->setOverriddenGetters($parentDef->getOverriddenGetters());
9192
$def->setProperties($parentDef->getProperties());
9293
$def->setAutowiringTypes($parentDef->getAutowiringTypes());
9394
if ($parentDef->isDeprecated()) {
@@ -160,6 +161,11 @@ private function doResolveDefinition(ChildDefinition $definition)
160161
$def->setMethodCalls(array_merge($def->getMethodCalls(), $calls));
161162
}
162163

164+
// merge overridden getters
165+
foreach ($definition->getOverriddenGetters() as $k => $v) {
166+
$def->setOverriddenGetter($k, $v);
167+
}
168+
163169
// merge autowiring types
164170
foreach ($definition->getAutowiringTypes() as $autowiringType) {
165171
$def->addAutowiringType($autowiringType);

Compiler/ResolveInvalidReferencesPass.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ private function processValue($value, $rootLevel = 0, $level = 0)
5959
}
6060
$value->setArguments($this->processValue($value->getArguments(), 0));
6161
$value->setProperties($this->processValue($value->getProperties(), 1));
62+
$value->setOverriddenGetters($this->processValue($value->getOverriddenGetters(), 1));
6263
$value->setMethodCalls($this->processValue($value->getMethodCalls(), 2));
6364
} elseif (is_array($value)) {
6465
$i = 0;

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
}

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

@@ -890,6 +891,9 @@ private function createService(Definition $definition, $id, $tryProxy = true)
890891
$arguments = $this->resolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getArguments())));
891892

892893
if (null !== $factory = $definition->getFactory()) {
894+
if ($definition->getOverriddenGetters()) {
895+
throw new RuntimeException(sprintf('Cannot create service "%s": factories and overridden getters are incompatible with each other.', $id));
896+
}
893897
if (is_array($factory)) {
894898
$factory = array($this->resolveServices($parameterBag->resolveValue($factory[0])), $factory[1]);
895899
} elseif (!is_string($factory)) {
@@ -908,11 +912,31 @@ private function createService(Definition $definition, $id, $tryProxy = true)
908912
} else {
909913
$r = new \ReflectionClass($parameterBag->resolveValue($definition->getClass()));
910914

911-
$service = null === $r->getConstructor() ? $r->newInstance() : $r->newInstanceArgs($arguments);
912-
913915
if (!$definition->isDeprecated() && 0 < strpos($r->getDocComment(), "\n * @deprecated ")) {
914916
@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);
915917
}
918+
if ($definition->getOverriddenGetters()) {
919+
static $salt;
920+
if (null === $salt) {
921+
$salt = str_replace('.', '', uniqid('', true));
922+
}
923+
$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);
924+
if (!class_exists($proxyClass = 'SymfonyProxy_'.md5($service), false)) {
925+
eval(sprintf('class %s extends %s', $proxyClass, $service));
926+
}
927+
$r = new \ReflectionClass($proxyClass);
928+
$constructor = $r->getConstructor();
929+
if ($constructor && !defined('HHVM_VERSION') && $constructor->getDeclaringClass()->isInternal()) {
930+
$constructor = null;
931+
}
932+
$service = $constructor ? $r->newInstanceWithoutConstructor() : $r->newInstanceArgs($arguments);
933+
call_user_func(\Closure::bind(function ($c, $v, $s) { $this->{'container'.$s} = $c; $this->{'values'.$s} = $v; }, $service, $service), $this, $definition->getOverriddenGetters(), $salt);
934+
if ($constructor) {
935+
$constructor->invokeArgs($service, $arguments);
936+
}
937+
} else {
938+
$service = null === $r->getConstructor() ? $r->newInstance() : $r->newInstanceArgs($arguments);
939+
}
916940
}
917941

918942
if ($tryProxy || !$definition->isLazy()) {
@@ -1155,6 +1179,102 @@ public function getEnvCounters()
11551179
return $this->envCounters;
11561180
}
11571181

1182+
private function generateOverriddenGetters($id, Definition $definition, \ReflectionClass $class, $salt)
1183+
{
1184+
if ($class->isFinal()) {
1185+
throw new RuntimeException(sprintf('Unable to configure getter injection for service "%s": class "%s" cannot be marked as final.', $id, $class->name));
1186+
}
1187+
$getters = '';
1188+
foreach ($definition->getOverriddenGetters() as $name => $returnValue) {
1189+
$r = self::getGetterReflector($class, $name, $id, $type);
1190+
$visibility = $r->isProtected() ? 'protected' : 'public';
1191+
$name = var_export($name, true);
1192+
$getters .= <<<EOF
1193+
1194+
{$visibility} function {$r->name}(){$type} {
1195+
\$c = \$this->container{$salt};
1196+
\$b = \$c->getParameterBag();
1197+
\$v = \$this->values{$salt}[{$name}];
1198+
1199+
foreach (\$c->getServiceConditionals(\$v) as \$s) {
1200+
if (!\$c->has(\$s)) {
1201+
return parent::{$r->name}();
1202+
}
1203+
}
1204+
1205+
return \$c->resolveServices(\$b->unescapeValue(\$b->resolveValue(\$v)));
1206+
}
1207+
EOF;
1208+
}
1209+
1210+
return $getters;
1211+
}
1212+
1213+
/**
1214+
* @internal
1215+
*/
1216+
public static function getGetterReflector(\ReflectionClass $class, $name, $id, &$type)
1217+
{
1218+
if (!$class->hasMethod($name)) {
1219+
throw new RuntimeException(sprintf('Unable to configure getter injection for service "%s": method "%s::%s" does not exist.', $id, $class->name, $name));
1220+
}
1221+
$r = $class->getMethod($name);
1222+
if ($r->isPrivate()) {
1223+
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));
1224+
}
1225+
if ($r->isStatic()) {
1226+
throw new RuntimeException(sprintf('Unable to configure getter injection for service "%s": method "%s::%s" cannot be static.', $id, $class->name, $r->name));
1227+
}
1228+
if ($r->isFinal()) {
1229+
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));
1230+
}
1231+
if ($r->returnsReference()) {
1232+
throw new RuntimeException(sprintf('Unable to configure getter injection for service "%s": method "%s::%s" cannot return by reference.', $id, $class->name, $r->name));
1233+
}
1234+
if (0 < $r->getNumberOfParameters()) {
1235+
throw new RuntimeException(sprintf('Unable to configure getter injection for service "%s": method "%s::%s" cannot have any arguments.', $id, $class->name, $r->name));
1236+
}
1237+
if ($type = method_exists($r, 'getReturnType') ? $r->getReturnType() : null) {
1238+
$type = ': '.($type->allowsNull() ? '?' : '').self::generateTypeHint($type, $r);
1239+
}
1240+
1241+
return $r;
1242+
}
1243+
1244+
/**
1245+
* @internal
1246+
*/
1247+
public static function generateTypeHint($type, \ReflectionFunctionAbstract $r)
1248+
{
1249+
if (is_string($type)) {
1250+
$name = $type;
1251+
1252+
if ('callable' === $name || 'array' === $name) {
1253+
return $name;
1254+
}
1255+
} else {
1256+
$name = $type instanceof \ReflectionNamedType ? $type->getName() : $type->__toString();
1257+
1258+
if ($type->isBuiltin()) {
1259+
return $name;
1260+
}
1261+
}
1262+
$lcName = strtolower($name);
1263+
1264+
if ('self' !== $lcName && 'parent' !== $lcName) {
1265+
return '\\'.$name;
1266+
}
1267+
if (!$r instanceof \ReflectionMethod) {
1268+
return;
1269+
}
1270+
if ('self' === $lcName) {
1271+
return '\\'.$r->getDeclaringClass()->name;
1272+
}
1273+
if ($parent = $r->getDeclaringClass()->getParentClass()) {
1274+
return '\\'.$parent->name;
1275+
}
1276+
}
1277+
11581278
/**
11591279
* @internal
11601280
*/

Definition.php

Lines changed: 32 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;
@@ -323,6 +324,37 @@ public function getMethodCalls()
323324
return $this->calls;
324325
}
325326

327+
/**
328+
* @experimental in version 3.3
329+
*/
330+
public function setOverriddenGetter($name, $returnValue)
331+
{
332+
if (!$name) {
333+
throw new InvalidArgumentException(sprintf('Getter name cannot be empty.'));
334+
}
335+
$this->getters[strtolower($name)] = $returnValue;
336+
337+
return $this;
338+
}
339+
340+
/**
341+
* @experimental in version 3.3
342+
*/
343+
public function setOverriddenGetters(array $getters)
344+
{
345+
$this->getters = array_change_key_case($getters, CASE_LOWER);
346+
347+
return $this;
348+
}
349+
350+
/**
351+
* @experimental in version 3.3
352+
*/
353+
public function getOverriddenGetters()
354+
{
355+
return $this->getters;
356+
}
357+
326358
/**
327359
* Sets tags for this definition.
328360
*

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)