-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[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
Changes from all commits
6e1be02
f179bb6
e3c5999
316b818
24b29d9
e30e63e
35fcc43
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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; | ||
} | ||
} |
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; | ||
} | ||
} | ||
|
||
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 |
---|---|---|
|
@@ -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; | ||
|
||
|
@@ -41,6 +42,7 @@ class Definition | |
private $autowired = false; | ||
private $autowiringTypes = array(); | ||
private $changes = array(); | ||
private $bindings = array(); | ||
|
||
protected $arguments = array(); | ||
|
||
|
@@ -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. | ||
* | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe add some explanations, eg: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I adapted a bit your proposal to:
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; | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I have to do that :)
What if the class/interface doesn't exist/is not loaded yet? Is this an unsupported case?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unsupported yes, DX is way more important than supporting funky edge cases