Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

[DI] Add getter injection #20973

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 30, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions 1 src/Symfony/Component/DependencyInjection/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ CHANGELOG
3.3.0
-----

* [EXPERIMENTAL] added support for getter-injection
* added support for omitting the factory class name in a service definition if the definition class is set
* deprecated case insensitivity of service identifiers
* added "iterator" argument type for lazy iteration over a set of values and services
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ protected function processValue($value, $isRoot = false)
} elseif ($value instanceof Definition) {
$value->setArguments($this->processValue($value->getArguments()));
$value->setProperties($this->processValue($value->getProperties()));
$value->setOverriddenGetters($this->processValue($value->getOverriddenGetters()));
$value->setMethodCalls($this->processValue($value->getMethodCalls()));

if ($v = $value->getFactory()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ private function doResolveDefinition(ChildDefinition $definition)
$def->setClass($parentDef->getClass());
$def->setArguments($parentDef->getArguments());
$def->setMethodCalls($parentDef->getMethodCalls());
$def->setOverriddenGetters($parentDef->getOverriddenGetters());
$def->setProperties($parentDef->getProperties());
$def->setAutowiringTypes($parentDef->getAutowiringTypes());
if ($parentDef->isDeprecated()) {
Expand Down Expand Up @@ -161,6 +162,11 @@ private function doResolveDefinition(ChildDefinition $definition)
$def->setMethodCalls(array_merge($def->getMethodCalls(), $calls));
}

// merge overridden getters
foreach ($definition->getOverriddenGetters() as $k => $v) {
$def->setOverriddenGetter($k, $v);
}

// merge autowiring types
foreach ($definition->getAutowiringTypes() as $autowiringType) {
$def->addAutowiringType($autowiringType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ private function processValue($value, $rootLevel = 0, $level = 0)
}
$value->setArguments($this->processValue($value->getArguments(), 0));
$value->setProperties($this->processValue($value->getProperties(), 1));
$value->setOverriddenGetters($this->processValue($value->getOverriddenGetters(), 1));
$value->setMethodCalls($this->processValue($value->getMethodCalls(), 2));
} elseif (is_array($value)) {
$i = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public function process(ContainerBuilder $container)

$definition->setArguments($this->processArguments($definition->getArguments()));
$definition->setMethodCalls($this->processArguments($definition->getMethodCalls()));
$definition->setOverriddenGetters($this->processArguments($definition->getOverriddenGetters()));
$definition->setProperties($this->processArguments($definition->getProperties()));
$definition->setFactory($this->processFactory($definition->getFactory()));
}
Expand Down
124 changes: 122 additions & 2 deletions 124 src/Symfony/Component/DependencyInjection/ContainerBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
use Symfony\Component\Config\Resource\ResourceInterface;
use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface;
use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\RealServiceInstantiator;
use Symfony\Component\DependencyInjection\LazyProxy\GetterProxyInterface;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;

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

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

$service = null === $r->getConstructor() ? $r->newInstance() : $r->newInstanceArgs($arguments);

if (!$definition->isDeprecated() && 0 < strpos($r->getDocComment(), "\n * @deprecated ")) {
@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);
}
if ($definition->getOverriddenGetters()) {
static $salt;
if (null === $salt) {
$salt = str_replace('.', '', uniqid('', true));
}
$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);
if (!class_exists($proxyClass = 'SymfonyProxy_'.md5($service), false)) {
eval(sprintf('class %s extends %s', $proxyClass, $service));
}
$r = new \ReflectionClass($proxyClass);
$constructor = $r->getConstructor();
if ($constructor && !defined('HHVM_VERSION') && $constructor->getDeclaringClass()->isInternal()) {
$constructor = null;
}
$service = $constructor ? $r->newInstanceWithoutConstructor() : $r->newInstanceArgs($arguments);
call_user_func(\Closure::bind(function ($c, $v, $s) { $this->{'container'.$s} = $c; $this->{'values'.$s} = $v; }, $service, $service), $this, $definition->getOverriddenGetters(), $salt);
if ($constructor) {
$constructor->invokeArgs($service, $arguments);
}
} else {
$service = null === $r->getConstructor() ? $r->newInstance() : $r->newInstanceArgs($arguments);
}
}

if ($tryProxy || !$definition->isLazy()) {
Expand Down Expand Up @@ -1155,6 +1179,102 @@ public function getEnvCounters()
return $this->envCounters;
}

private function generateOverriddenGetters($id, Definition $definition, \ReflectionClass $class, $salt)
{
if ($class->isFinal()) {
throw new RuntimeException(sprintf('Unable to configure getter injection for service "%s": class "%s" cannot be marked as final.', $id, $class->name));
}
$getters = '';
foreach ($definition->getOverriddenGetters() as $name => $returnValue) {
$r = self::getGetterReflector($class, $name, $id, $type);
$visibility = $r->isProtected() ? 'protected' : 'public';
$name = var_export($name, true);
$getters .= <<<EOF

{$visibility} function {$r->name}(){$type} {
\$c = \$this->container{$salt};
\$b = \$c->getParameterBag();
\$v = \$this->values{$salt}[{$name}];

foreach (\$c->getServiceConditionals(\$v) as \$s) {
if (!\$c->has(\$s)) {
return parent::{$r->name}();
}
}

return \$c->resolveServices(\$b->unescapeValue(\$b->resolveValue(\$v)));
}
EOF;
}

return $getters;
}

/**
* @internal
*/
public static function getGetterReflector(\ReflectionClass $class, $name, $id, &$type)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rather than returning the type by reference, it would be more readable to handle it outside getGetterReflector IMO (it only depend on the returned value anyway

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getGetterReflector is used in two places (ContainerBuilder & PhpDumper), so I'd prefer sharing the implementation. Even short, it's not trivial.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and please mark this method as @internal

{
if (!$class->hasMethod($name)) {
throw new RuntimeException(sprintf('Unable to configure getter injection for service "%s": method "%s::%s" does not exist.', $id, $class->name, $name));
}
$r = $class->getMethod($name);
if ($r->isPrivate()) {
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));
}
if ($r->isStatic()) {
throw new RuntimeException(sprintf('Unable to configure getter injection for service "%s": method "%s::%s" cannot be static.', $id, $class->name, $r->name));
}
if ($r->isFinal()) {
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));
}
if ($r->returnsReference()) {
throw new RuntimeException(sprintf('Unable to configure getter injection for service "%s": method "%s::%s" cannot return by reference.', $id, $class->name, $r->name));
}
if (0 < $r->getNumberOfParameters()) {
throw new RuntimeException(sprintf('Unable to configure getter injection for service "%s": method "%s::%s" cannot have any arguments.', $id, $class->name, $r->name));
}
if ($type = method_exists($r, 'getReturnType') ? $r->getReturnType() : null) {
$type = ': '.($type->allowsNull() ? '?' : '').self::generateTypeHint($type, $r);
}

return $r;
}

/**
* @internal
*/
public static function generateTypeHint($type, \ReflectionFunctionAbstract $r)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be internal

{
if (is_string($type)) {
$name = $type;

if ('callable' === $name || 'array' === $name) {
return $name;
}
} else {
$name = $type instanceof \ReflectionNamedType ? $type->getName() : $type->__toString();

if ($type->isBuiltin()) {
return $name;
}
}
$lcName = strtolower($name);

if ('self' !== $lcName && 'parent' !== $lcName) {
return '\\'.$name;
}
if (!$r instanceof \ReflectionMethod) {
return;
}
if ('self' === $lcName) {
return '\\'.$r->getDeclaringClass()->name;
}
if ($parent = $r->getDeclaringClass()->getParentClass()) {
return '\\'.$parent->name;
}
}

/**
* @internal
*/
Expand Down
32 changes: 32 additions & 0 deletions 32 src/Symfony/Component/DependencyInjection/Definition.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class Definition
private $deprecationTemplate = 'The "%service_id%" service is deprecated. You should stop using it, as it will soon be removed.';
private $properties = array();
private $calls = array();
private $getters = array();
private $configurator;
private $tags = array();
private $public = true;
Expand Down Expand Up @@ -319,6 +320,37 @@ public function getMethodCalls()
return $this->calls;
}

/**
* @experimental in version 3.3
*/
public function setOverriddenGetter($name, $returnValue)
{
if (!$name) {
throw new InvalidArgumentException(sprintf('Getter name cannot be empty.'));
}
$this->getters[strtolower($name)] = $returnValue;

return $this;
}

/**
* @experimental in version 3.3
*/
public function setOverriddenGetters(array $getters)
{
$this->getters = array_change_key_case($getters, CASE_LOWER);

return $this;
}

/**
* @experimental in version 3.3
*/
public function getOverriddenGetters()
{
return $this->getters;
}

/**
* Sets tags for this definition.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ public function dump(array $options = array())
$this->findEdges($id, $call[1], false, $call[0].'()')
);
}

foreach ($definition->getOverriddenGetters() as $name => $value) {
$this->edges[$id] = array_merge(
$this->edges[$id],
$this->findEdges($id, $value, false, $name.'()')
);
}
}

return $this->container->resolveEnvPlaceholders($this->startDot().$this->addNodes().$this->addEdges().$this->endDot(), '__ENV_%s__');
Expand Down
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.