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

Segmentation fault with the container, Doctrine and the EntityManagerInterface #34200

Copy link
Copy link
Closed
@sylfabre

Description

@sylfabre
Issue body actions

Symfony version(s) affected: 4.3.5

Description
@nicolas-grekas Following up on our discussion at the meetup, I've researched the segmentation fault / memory limit error.

The bug comes from the way the method getDoctrine_Dbal_DefaultConnectionService() in the dumped container instantiates $this->services['doctrine.dbal.default_connection'] for Doctrine\DBAL\Connection

The method first instantiate all dependencies as expected:

protected function getDoctrine_Dbal_DefaultConnectionService()
{
	$a = ($this->services['validator'] ?? $this->getValidatorService());

	if (isset($this->services['doctrine.dbal.default_connection'])) {
		return $this->services['doctrine.dbal.default_connection'];
	}
	$b = ($this->services['search.search_indexer_subscriber'] ?? $this->getSearch_SearchIndexerSubscriberService());

	if (isset($this->services['doctrine.dbal.default_connection'])) {
		return $this->services['doctrine.dbal.default_connection'];
	}
	$c = new \Doctrine\DBAL\Configuration();

	$d = new \Doctrine\DBAL\Logging\LoggerChain();

	$e = new \Symfony\Bridge\Monolog\Logger('doctrine');
	$e->pushHandler(($this->privates['monolog.handler.main'] ?? $this->getMonolog_Handler_MainService()));
	$e->pushHandler(($this->privates['monolog.handler.syslog_handler'] ?? $this->getMonolog_Handler_SyslogHandlerService()));

	$d->addLogger(new \Symfony\Bridge\Doctrine\Logger\DbalLogger($e, ($this->privates['debug.stopwatch'] ?? ($this->privates['debug.stopwatch'] = new \Symfony\Component\Stopwatch\Stopwatch(true)))));
	$d->addLogger(new \Doctrine\DBAL\Logging\DebugStack());

	$c->setSQLLogger($d);
	$c->setSchemaAssetsFilter(new \Doctrine\Bundle\DoctrineBundle\Dbal\SchemaAssetsFilterManager([0 => new \Doctrine\Bundle\DoctrineBundle\Dbal\RegexSchemaAssetFilter('~^(?!(BuyPacker_|SMoney_|Spark_|buypacker_|smoney_|spark))~')]));
	$f = new \Symfony\Bridge\Doctrine\ContainerAwareEventManager(new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($this->getService, [
		'doctrine.orm.default_listeners.attach_entity_listeners' => ['privates', 'doctrine.orm.default_listeners.attach_entity_listeners', 'getDoctrine_Orm_DefaultListeners_AttachEntityListenersService.php', true],
	], [
		'doctrine.orm.default_listeners.attach_entity_listeners' => '?',
	]));

	$g = new \App\Doctrine\ORM\Subscriber\LoggerSubscriber();
	$g->setDebugHelper(($this->services['legacy.helpers.debug'] ?? ($this->services['legacy.helpers.debug'] = new \App\Helper\DebugHelper($this->targetDirs[3]))));
	$g->setFormatter(new \App\Formatter\DoctrineLogFormatter());
	$h = new \App\Doctrine\ORM\Subscriber\ValidatorSubscriber();
	$h->setValidator($a);

	$f->addEventSubscriber($g);
	$f->addEventSubscriber($h);
	$f->addEventSubscriber($b);
	$f->addEventListener([0 => 'loadClassMetadata'], 'doctrine.orm.default_listeners.attach_entity_listeners');

	return $this->services['doctrine.dbal.default_connection'] = (new \Doctrine\Bundle\DoctrineBundle\ConnectionFactory($this->parameters['doctrine.dbal.connection_factory.types']))->createConnection(['driver' => 'pdo_mysql', 'charset' => 'utf8mb4', 'url' => 'mysql://root:root@127.0.0.1:3306/assoconnect', 'host' => 'localhost', 'port' => NULL, 'user' => 'root', 'password' => NULL, 'driverOptions' => [], 'serverVersion' => '5.7', 'defaultTableOptions' => []], $c, $f, ['bic' => 'string', 'country' => 'string', 'currency' => 'string', 'datetimetz' => 'datetime', 'datetimeutc' => 'datetime', 'email' => 'string', 'iban' => 'string', 'ip' => 'string', 'latitude' => 'decimal', 'locale' => 'string', 'longitude' => 'decimal', 'money' => 'decimal', 'percent' => 'decimal', 'phone' => 'string', 'phonelandline' => 'string', 'phonemobile' => 'string', 'timezone' => 'string', 'uuid_binary_ordered_time' => 'binary']);
}

and ends by defining $this->services['doctrine.dbal.default_connection'] as expected.

BUT in our case there is one dependency $b = ($this->services['search.search_indexer_subscriber'] ?? $this->getSearch_SearchIndexerSubscriberService()); to define Algolia\SearchBundle\EventListener\SearchIndexerSubscriber.

This dependency is used as an event subscriber:
$f->addEventSubscriber($b);

The dependency chain is:

  • Algolia\SearchBundle\EventListener\SearchIndexerSubscriber
  • Algolia\SearchBundle\IndexManager
  • Symfony\Component\Serializer\Serializer

The Serializer lists all the Normalizers for its first argument:
\Symfony\Component\Serializer\Serializer::__construct(array $normalizers = [], array $encoders = []))

One of our normalizers depends on a service depending on EntityManagerInterface which requires the actual service Doctrine\ORM\EntityManager which depends on Doctrine\DBAL\Connection.

So in the end, $this->services['doctrine.dbal.default_connection'] is never defined and we have an infine loop.

Bottom line, I think there are different bugs here:

  • the circular-dependency checker seems to only look at constructors' argument type hints
  • the documentation should state to "never inject EntityManagerInterface but rely on Doctrine\Common\Persistence\ManagerRegistry" as you were saying.

For the 2nd one, https://symfony.com/doc/current/doctrine.html states:

you can add an argument to the action: createProduct(EntityManagerInterface $entityManager)
This works because Controllers usually are not used as dependencies.
But it is misleading for people writing services: a best practice should be given somewhere.

How to reproduce
I haven't written yet a reproducer but I'm not sure it's useful here.

Possible Solution
I can submit a doc PR if you want.

And if updating the circular-dependency checker is too difficult, an alternative may be to create a state checker in the dumper container for each method:

protected function getService_XXX()
{
	if (in_array('some.service', $this->instantiating)) {
		// dump some useful stack for the developer
		
		die(1);
	} else {
		$this->instantiating []= 'some.service';
	}
	
	
	// Current code
	
	
	return $this->services['some.service'] = new SomeService(...);
}

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions

    Morty Proxy This is a proxified and sanitized view of the page, visit original site.