Skip to content

[DependencyInjection] Add autowiring capabilities #15613

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 19 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Fix issues raised by @stof
  • Loading branch information
dunglas committed Oct 1, 2015
commit 5575ec1d9537fd35c0ad736542f1ed4dd53aac47
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,27 @@
class AutowiringPass implements CompilerPassInterface
{
private $container;
Copy link
Contributor

Choose a reason for hiding this comment

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

why storing the container? Can't you just pass it explicitly to your private methods?

Copy link
Member Author

Choose a reason for hiding this comment

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

As this class already has a lot of states, it's just to avoid passing the container as argument of other methods.
I can rewrite this class without states but not sure it has any interest (and other passes also have states).

private $definitions;
private $reflectionClassesToId = array();
private $reflectionClasses = array();
private $definedTypes = array();
private $typesToId;
private $notGuessableTypesToId = array();
private $types;
private $notGuessableTypes = array();

/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
$this->container = $container;
$this->definitions = $container->getDefinitions();
foreach ($this->definitions as $id => $definition) {
foreach ($container->getDefinitions() as $id => $definition) {
$this->completeDefinition($id, $definition);
}
Copy link
Member

Choose a reason for hiding this comment

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

please reset all the internal state at the end of the method to release memory (many things are not needed at all anymore, and there is no reason to kep a circular reference between the ContainerBuilder and this pass)

Copy link
Contributor

Choose a reason for hiding this comment

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

why aren't you using $container->findTaggedServiceIds instead ?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch, I'll take a look at that.

Copy link
Member Author

Choose a reason for hiding this comment

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

Because I need the definition instance and not the tags.


// Free memory and remove circular reference to container
$this->container = null;
$this->reflectionClasses = array();
$this->definedTypes = array();
$this->types = null;
$this->notGuessableTypes = array();
Copy link
Contributor

Choose a reason for hiding this comment

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

Not quite sure if everything should be reset (I can understand why the reflection classes or the container, but the rest, as they are kind of execution indepedant...).

Granted, this process should only be called once, but still...

Copy link
Member Author

Choose a reason for hiding this comment

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

Everything should be reseted to avoid memory leaks. In fact it can be called a lot of times, for instance when running test suites or using Behat.

Copy link
Member

Choose a reason for hiding this comment

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

@Taluu anything which is specific to the container passed as argument should disappear from the class when leaving the method. Otherwise, processing a container has side effects on the state of this class.
and yes, it would also leak memory.

@dunglas your cleanup is broken in 1 case though: the processing can throw exceptions, and the cleanup should also be applied in such case (this is the perfect use case for finally, but we cannot use it in 2.x)

Copy link
Member Author

Choose a reason for hiding this comment

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

@stof right, I'll add a finally block in 3.0 after the merge or this PR in 2.8.

}

/**
Expand All @@ -62,7 +67,7 @@ private function completeDefinition($id, Definition $definition)

$arguments = $definition->getArguments();
foreach ($constructor->getParameters() as $index => $parameter) {
Copy link
Member

Choose a reason for hiding this comment

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

getParameters returns an array indexed by name IIRC, not by numbers, so the $argumentExist check will always be false

Copy link
Member Author

Choose a reason for hiding this comment

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

The array returned by ReflectionMethod::getParameters() is numerically indexed: https://3v4l.org/6i1qY

if (!($typeHint = $parameter->getClass()) || $parameter->isOptional()) {
if (!($typeHint = $parameter->getClass())) {
continue;
}

Expand All @@ -71,20 +76,28 @@ private function completeDefinition($id, Definition $definition)
continue;
}

if (null === $this->typesToId) {
if (null === $this->types) {
$this->populateAvailableTypes();
}

if (isset($this->typesToId[$typeHint->name])) {
$reference = new Reference($this->typesToId[$typeHint->name]);
if (isset($this->types[$typeHint->name])) {
$value = new Reference($this->types[$typeHint->name]);
} else {
$reference = $this->createAutowiredDefinition($typeHint);
try {
$value = $this->createAutowiredDefinition($typeHint);
} catch (RuntimeException $e) {
if (!$parameter->isDefaultValueAvailable()) {
throw $e;
}

$value = $parameter->getDefaultValue();
}
}

if ($argumentExist) {
$definition->replaceArgument($index, $reference);
$definition->replaceArgument($index, $value);
} else {
$definition->addArgument($reference);
$definition->addArgument($value);
}
}
}
Expand All @@ -94,9 +107,9 @@ private function completeDefinition($id, Definition $definition)
*/
private function populateAvailableTypes()
{
$this->typesToId = array();
$this->types = array();

foreach ($this->definitions as $id => $definition) {
foreach ($this->container->getDefinitions() as $id => $definition) {
$this->populateAvailableType($id, $definition);
}
}
Expand All @@ -115,7 +128,7 @@ private function populateAvailableType($id, Definition $definition)

foreach ($definition->getTypes() as $type) {
$this->definedTypes[$type] = true;
$this->typesToId[$type] = $id;
$this->types[$type] = $id;
}

if ($reflectionClass = $this->getReflectionClass($id, $definition)) {
Expand Down Expand Up @@ -162,22 +175,22 @@ private function extractAncestors($id, \ReflectionClass $reflectionClass)
*/
private function set($type, $value)
{
if (isset($this->definedTypes[$type]) || isset($this->notGuessableTypesToId[$type])) {
if (isset($this->definedTypes[$type]) || isset($this->notGuessableTypes[$type])) {
return;
}

if (isset($this->typesToId[$type])) {
if ($this->typesToId[$type] === $value) {
if (isset($this->types[$type])) {
if ($this->types[$type] === $value) {
return;
}

unset($this->typesToId[$type]);
$this->notGuessableTypesToId[$type] = true;
unset($this->types[$type]);
$this->notGuessableTypes[$type] = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

Just out of curiosity ; why use an associative array here, and not a simple array ? Is it because it is faster this way (I think I read something on that matter a while ago) ?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes it's for performance.

Copy link
Member

Choose a reason for hiding this comment

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

@Taluu array_search is O(n) as it has to loop over the array to check each value. isset($array[$key]) is O(1) as it is an access by key.


return;
}

$this->typesToId[$type] = $value;
$this->types[$type] = $value;
}

/**
Expand All @@ -200,7 +213,6 @@ private function createAutowiredDefinition(\ReflectionClass $typeHint)
$argumentDefinition = $this->container->register($argumentId, $typeHint->name);
$argumentDefinition->setPublic(false);

$this->definitions = $this->container->getDefinitions();
$this->populateAvailableType($argumentId, $argumentDefinition);
$this->completeDefinition($argumentId, $argumentDefinition);

Expand All @@ -217,16 +229,18 @@ private function createAutowiredDefinition(\ReflectionClass $typeHint)
*/
private function getReflectionClass($id, Definition $definition)
{
if (isset($this->reflectionClassesToId[$id])) {
return $this->reflectionClassesToId[$id];
if (isset($this->reflectionClasses[$id])) {
return $this->reflectionClasses[$id];
}

if (!$class = $definition->getClass()) {
return;
}

$class = $this->container->getParameterBag()->resolveValue($class);

try {
return $this->reflectionClassesToId[$id] = new \ReflectionClass($class);
return $this->reflectionClasses[$id] = new \ReflectionClass($class);
} catch (\ReflectionException $e) {
// Skip invalid classes definitions to keep BC
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ public function __construct()
new CheckDefinitionValidityPass(),
new ResolveReferencesToAliasesPass(),
new ResolveInvalidReferencesPass(),
new AnalyzeServiceReferencesPass(true),
new AutowiringPass(),
Copy link
Member

Choose a reason for hiding this comment

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

this must be before the AnalyzeServiceReferencesPass, otherwise the changes done by the autowiring pass won't be taken into account when checking for circular references

new AnalyzeServiceReferencesPass(true),
new CheckCircularReferencesPass(),
new CheckReferenceValidityPass(),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,37 @@ public function testCreateDefinition()
$lilleDefinition = $container->getDefinition('autowired.symfony\component\dependencyinjection\tests\compiler\lille');
$this->assertEquals(__NAMESPACE__.'\Lille', $lilleDefinition->getClass());
}

public function testResolveParameter()
{
$container = new ContainerBuilder();

$container->setParameter('class_name', __NAMESPACE__.'\Foo');
$container->register('foo', '%class_name%');
$container->register('bar', __NAMESPACE__.'\Bar');

$pass = new AutowiringPass();
$pass->process($container);

$this->assertEquals('foo', $container->getDefinition('bar')->getArgument(0));
}

public function testOptionalParameter()
{
$container = new ContainerBuilder();

$container->register('a', __NAMESPACE__.'\A');
$container->register('foo', __NAMESPACE__.'\Foo');
$container->register('opt', __NAMESPACE__.'\OptionalParameter');

$pass = new AutowiringPass();
$pass->process($container);

$definition = $container->getDefinition('opt');
$this->assertNull($definition->getArgument(0));
$this->assertEquals('a', $definition->getArgument(1));
$this->assertEquals('foo', $definition->getArgument(2));
}
}

class Foo
Expand Down Expand Up @@ -237,3 +268,10 @@ public function __construct(Dunglas $k)
{
}
}

class OptionalParameter
{
public function __construct(CollisionInterface $c = null, A $a, Foo $f = null)
{
}
}
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy