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

[DependencyInjection] Support local binding #22187

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

Closed
wants to merge 7 commits into from
Closed
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\DependencyInjection\Argument;

/**
* @author Guilhem Niot <guilhem.niot@gmail.com>
*/
final class BoundArgument implements ArgumentInterface
{
private static $sequence = 0;

private $value;
private $identifier;
private $used;

public function __construct($value)
{
$this->value = $value;
$this->identifier = ++self::$sequence;
}

/**
* {@inheritdoc}
*/
public function getValues()
{
return array($this->value, $this->identifier, $this->used);
}

/**
* {@inheritdoc}
*/
public function setValues(array $values)
{
list($this->value, $this->identifier, $this->used) = $values;
}
}
8 changes: 8 additions & 0 deletions 8 src/Symfony/Component/DependencyInjection/ChildDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,14 @@ public function setInstanceofConditionals(array $instanceof)
{
throw new BadMethodCallException('A ChildDefinition cannot have instanceof conditionals set on it.');
}

/**
* @internal
*/
public function setBindings(array $bindings)
{
throw new BadMethodCallException('A ChildDefinition cannot have bindings set on it.');
}
}

class_alias(ChildDefinition::class, DefinitionDecorator::class);
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ protected function processValue($value, $isRoot = false)
$value->setArguments($this->processValue($value->getArguments()));
$value->setProperties($this->processValue($value->getProperties()));
$value->setMethodCalls($this->processValue($value->getMethodCalls()));
$value->setBindings($this->processValue($value->getBindings()));

$changes = $value->getChanges();
if (isset($changes['factory'])) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public function __construct()
new CheckDefinitionValidityPass(),
new RegisterServiceSubscribersPass(),
new ResolveNamedArgumentsPass(),
new ResolveBindingsPass(),
$autowirePass = new AutowirePass(false),
new ResolveServiceSubscribersPass(),
new ResolveReferencesToAliasesPass(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Argument\BoundArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper;
use Symfony\Component\DependencyInjection\TypedReference;
use Symfony\Component\DependencyInjection\Reference;

/**
* @author Guilhem Niot <guilhem.niot@gmail.com>
*/
class ResolveBindingsPass extends AbstractRecursivePass
{
private $usedBindings = array();
private $unusedBindings = array();

/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
try {
parent::process($container);

foreach ($this->unusedBindings as list($key, $serviceId)) {
throw new InvalidArgumentException(sprintf('Unused binding "%s" in service "%s".', $key, $serviceId));
}
} finally {
$this->usedBindings = array();
$this->unusedBindings = array();
}
}

/**
* {@inheritdoc}
*/
protected function processValue($value, $isRoot = false)
{
if ($value instanceof TypedReference && $value->getType() === (string) $value) {
// Already checked
$bindings = $this->container->getDefinition($this->currentId)->getBindings();

if (isset($bindings[$value->getType()])) {
return $this->getBindingValue($bindings[$value->getType()]);
}

return parent::processValue($value, $isRoot);
}

if (!$value instanceof Definition || !$bindings = $value->getBindings()) {
return parent::processValue($value, $isRoot);
}

foreach ($bindings as $key => $binding) {
list($bindingValue, $bindingId, $used) = $binding->getValues();
if ($used) {
$this->usedBindings[$bindingId] = true;
unset($this->unusedBindings[$bindingId]);
} elseif (!isset($this->usedBindings[$bindingId])) {
$this->unusedBindings[$bindingId] = array($key, $this->currentId);
}

if (isset($key[0]) && '$' === $key[0]) {
continue;
}

if (null !== $bindingValue && !$bindingValue instanceof Reference && !$bindingValue instanceof Definition) {
throw new InvalidArgumentException(sprintf('Invalid value for binding key "%s" for service "%s": expected null, an instance of %s or an instance of %s, %s given.', $key, $this->currentId, Reference::class, Definition::class, gettype($bindingValue)));
}
}

if ($value->isAbstract()) {
return parent::processValue($value, $isRoot);
}

$calls = $value->getMethodCalls();

if ($constructor = $this->getConstructor($value, false)) {
$calls[] = array($constructor, $value->getArguments());
}

foreach ($calls as $i => $call) {
list($method, $arguments) = $call;

if ($method instanceof \ReflectionFunctionAbstract) {
$reflectionMethod = $method;
} else {
$reflectionMethod = $this->getReflectionMethod($value, $method);
}

foreach ($reflectionMethod->getParameters() as $key => $parameter) {
if (array_key_exists($key, $arguments) && '' !== $arguments[$key]) {
continue;
}

if (array_key_exists('$'.$parameter->name, $bindings)) {
$arguments[$key] = $this->getBindingValue($bindings['$'.$parameter->name]);

continue;
}

$typeHint = ProxyHelper::getTypeHint($reflectionMethod, $parameter, true);

if (!isset($bindings[$typeHint])) {
continue;
}

$arguments[$key] = $this->getBindingValue($bindings[$typeHint]);
}

if ($arguments !== $call[1]) {
ksort($arguments);
$calls[$i][1] = $arguments;
}
}
Copy link
Member

@nicolas-grekas nicolas-grekas Mar 28, 2017

Choose a reason for hiding this comment

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

$bindings should be allowed to contain $named args, so that it can be used with scalar/array params also.
$bindings could also be validated: what about throwing when a key doesn't match any existing class/interface? That would help people spot typos easily.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

$bindings should be allowed to contain $named args, so that it can be used with scalar/array params also.

Yes I have to do that :)

$bindings could also be validated: what about throwing when a key doesn't match any existing class/interface? That would help people spot typos easily.

What if the class/interface doesn't exist/is not loaded yet? Is this an unsupported case?

Copy link
Member

@nicolas-grekas nicolas-grekas Mar 28, 2017

Choose a reason for hiding this comment

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

What if the class/interface doesn't exist

unsupported yes, DX is way more important than supporting funky edge cases


if ($constructor) {
list(, $arguments) = array_pop($calls);

if ($arguments !== $value->getArguments()) {
$value->setArguments($arguments);
}
}

if ($calls !== $value->getMethodCalls()) {
$value->setMethodCalls($calls);
}

return parent::processValue($value, $isRoot);
}

private function getBindingValue(BoundArgument $binding)
{
list($bindingValue, $bindingId) = $binding->getValues();

$this->usedBindings[$bindingId] = true;
unset($this->unusedBindings[$bindingId]);

return $bindingValue;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ private function doResolveDefinition(ChildDefinition $definition)
$def->setAutowired($parentDef->isAutowired());
$def->setChanges($parentDef->getChanges());

$def->setBindings($parentDef->getBindings());

// overwrite with values specified in the decorator
$changes = $definition->getChanges();
if (isset($changes['class'])) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper;
use Symfony\Component\DependencyInjection\Reference;

/**
* Resolves named arguments to their corresponding numeric index.
Expand Down Expand Up @@ -43,25 +45,38 @@ protected function processValue($value, $isRoot = false)
$resolvedArguments[$key] = $argument;
continue;
}
if ('' === $key || '$' !== $key[0]) {
throw new InvalidArgumentException(sprintf('Invalid key "%s" found in arguments of method "%s()" for service "%s": only integer or $named arguments are allowed.', $key, $method, $this->currentId));
}

if (null === $parameters) {
$r = $this->getReflectionMethod($value, $method);
$class = $r instanceof \ReflectionMethod ? $r->class : $this->currentId;
$parameters = $r->getParameters();
}

if (isset($key[0]) && '$' === $key[0]) {
foreach ($parameters as $j => $p) {
if ($key === '$'.$p->name) {
$resolvedArguments[$j] = $argument;

continue 2;
}
}

throw new InvalidArgumentException(sprintf('Unable to resolve service "%s": method "%s()" has no argument named "%s". Check your service definition.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method, $key));
}

if (null !== $argument && !$argument instanceof Reference && !$argument instanceof Definition) {
throw new InvalidArgumentException(sprintf('Unable to resolve service "%s": the value of argument "%s" of method "%s()" must be null, an instance of %s or an instance of %s, %s given.', $this->currentId, $key, $class !== $this->currentId ? $class.'::'.$method : $method, Reference::class, Definition::class, gettype($argument)));
}

foreach ($parameters as $j => $p) {
if ($key === '$'.$p->name) {
if (ProxyHelper::getTypeHint($r, $p, true) === $key) {
$resolvedArguments[$j] = $argument;

continue 2;
}
}

throw new InvalidArgumentException(sprintf('Unable to resolve service "%s": method "%s()" has no argument named "%s". Check your service definition.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method, $key));
throw new InvalidArgumentException(sprintf('Unable to resolve service "%s": method "%s()" has no argument type-hinted as "%s". Check your service definition.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method, $key));
}

if ($resolvedArguments !== $call[1]) {
Expand Down
36 changes: 36 additions & 0 deletions 36 src/Symfony/Component/DependencyInjection/Definition.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace Symfony\Component\DependencyInjection;

use Symfony\Component\DependencyInjection\Argument\BoundArgument;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\OutOfBoundsException;

Expand Down Expand Up @@ -41,6 +42,7 @@ class Definition
private $autowired = false;
private $autowiringTypes = array();
private $changes = array();
private $bindings = array();

protected $arguments = array();

Expand Down Expand Up @@ -860,4 +862,38 @@ public function hasAutowiringType($type)

return isset($this->autowiringTypes[$type]);
}

/**
* Gets bindings.
*
* @return array
*/
public function getBindings()
{
return $this->bindings;
}

/**
* Sets bindings.
*
Copy link
Member

Choose a reason for hiding this comment

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

Maybe add some explanations, eg:
Bindings map $named arguments or type-hints to values that should be injected in the matching parameters of the constructor and of setters.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I adapted a bit your proposal to:

Bindings map $named or FQCN arguments to values that should be
injected in the matching parameters (of the constructor, of methods
called and of controller actions).

Ok for you?

* Bindings map $named or FQCN arguments to values that should be
* injected in the matching parameters (of the constructor, of methods
* called and of controller actions).
*
* @param array $bindings
*
* @return $this
*/
public function setBindings(array $bindings)
{
foreach ($bindings as $key => $binding) {
if (!$binding instanceof BoundArgument) {
$bindings[$key] = new BoundArgument($binding);
}
}

$this->bindings = $bindings;

return $this;
}
}
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.