Skip to content

ContainerBuilder::getReflectionClass() + autowiring breaks resource cache & debug:config #29019

@weaverryan

Description

@weaverryan

Symfony version(s) affected: 4.1.6 (but probably back to 3.4)

Description
Hey guys!

I found a very deep bug related to autowiring & the container resources. Steps to reproduce are below, but here is a description of the issue. Also, this seems to only affect the debug:config and related commands where the container is rebuilt manually. The normal container does not seem to suffer from this problem. The key part is 3 - I believe that's where the bug actually exists.

  1. In AutowirePass, a type somewhere in the system cannot be guessed. And so, AutowirePass:: populateAvailableTypes() is called to try to determine a good error message. This is already slightly flawed, as the error message may ultimately be "hidden" because the definition in question is removed... and so we're doing work unnecessarily. But, that's not the bug - just a little performance thing.

  2. The AutowirePass::populateAvailableTypes() eventually calls: $this->container->getReflectionClass($definition->getClass(), false) on each definition.

  3. Here is the important part: IF the class in question exists (e.g. DoctrineOrmTypeGuesser was the culprit for me) but some of the interfaces that it implements do NOT exist, then the ContainerBuilder::getReflectionClass() method will create and use a ClassExistenceResource for that class where $exists = false.

  4. The next time the container checks to see if its cache is fresh, it will always return false because the class above (e.g. DoctrineOrmTypeGuesser) DOES exist, but its ClassExistenceResource expects it to not exist.

How to reproduce
Reproducer: https://github.com/weaverryan/autowiring-class-existence-reproducer

Steps:

  1. Install form (because it has a good example - DoctrineOrmTypeGuesser) of a class that will be registered as a service, even though some of its parent interfaces do not exist in the app

  2. Create an "autowiring failure" situation that will ultimately be silent. Easy way: add a controller and type-hint it with an entity.

  3. Install security. Not needed for the bug - it's just an easy way to "see" the bug... because the bad behavior causes a weird situation.

  4. Run rm -rf var/cache/* && php bin/console debug:config framework. You'll see this exception:

The service "debug.security.firewall" has a dependency on a non-existent service ".security.
request_matcher.zfHj2lW".

This is not really the bug - it's just a "symptom" of the fact that debug:config will build the container multiple times, because after building it, it immediately looks "stale".

Possible Solution
Make ContainerBuilder::getReflectionClass() a bit smarter: only add the class existence resource if the class really does not exist?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions