Skip to content

Navigation Menu

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] Incorrect handling when decorating a service created by a factory #49591

Copy link
Copy link
Open
@dbu

Description

@dbu
Issue body actions

Symfony version(s) affected

6.2.7

Description

the sulu project is using jackalope-doctrine-dbal through the phpcr-bundle. Sulu decorates the phpcr-bundle session service with its own service. since recently, the factory for the phpcr session accesses the doctrine connection. According to the stack trace we see, accessing the connection in turn triggers creating the session (and i did not fully understand why that does not lead to an endless loop).

the problem we observe now is that some parts of the system are using a different instance of the session than the rest, leading to problems.

i don't have the most simple reproducer. i would be happy to help fix the bug if somebody can point me to where i should put a functional test that recreates the problem in a phpunit test and then from there we can figure out how to fix it.

How to reproduce

i captured the stack trace in the problematic case:

ObjectManager.php:172, Jackalope\ObjectManager->__construct()
Factory.php:62, ReflectionClass->newInstanceArgs()
Factory.php:62, Jackalope\Factory->get()
Session.php:131, Jackalope\Session->__construct()
Factory.php:62, ReflectionClass->newInstanceArgs()
Factory.php:62, Jackalope\Factory->get()
Repository.php:155, Jackalope\Repository->login()
App_KernelDevDebugContainer.php:1430, ContainerTqpZCZm\App_KernelDevDebugContainer->getDoctrinePhpcr_SessionService()
App_KernelDevDebugContainer.php:5170, ContainerTqpZCZm\App_KernelDevDebugContainer->getSuluDocumentManager_NodeManagerService()
App_KernelDevDebugContainer.php:2505, ContainerTqpZCZm\App_KernelDevDebugContainer->getSuluDocumentManager_DocumentManagerService()
App_KernelDevDebugContainer.php:5498, ContainerTqpZCZm\App_KernelDevDebugContainer->getSuluSecurity_AccessControlManagerService()
getSuluSecurity_PermissionInheritanceSubscriberService.php:23, ContainerTqpZCZm\getSuluSecurity_PermissionInheritanceSubscriberService::do()
App_KernelDevDebugContainer.php:1118, ContainerTqpZCZm\App_KernelDevDebugContainer->load()
Container.php:381, Symfony\Component\DependencyInjection\Container->getService()
ServiceLocator.php:40, Symfony\Component\DependencyInjection\Argument\ServiceLocator->get()
ContainerAwareEventManager.php:191, Symfony\Bridge\Doctrine\ContainerAwareEventManager->initializeSubscribers()
ContainerAwareEventManager.php:105, Symfony\Bridge\Doctrine\ContainerAwareEventManager->hasListeners()
Connection.php:384, Doctrine\DBAL\Connection->connect()
Connection.php:446, Doctrine\DBAL\Connection->getDatabasePlatformVersion()
Connection.php:408, Doctrine\DBAL\Connection->detectDatabasePlatform()
Connection.php:316, Doctrine\DBAL\Connection->getDatabasePlatform()
RepositoryFactoryDoctrineDBAL.php:94, Jackalope\RepositoryFactoryDoctrineDBAL->getRepository()
App_KernelDevDebugContainer.php:1430, ContainerTqpZCZm\App_KernelDevDebugContainer->getDoctrinePhpcr_SessionService()
App_KernelDevDebugContainer.php:5170, ContainerTqpZCZm\App_KernelDevDebugContainer->getSuluDocumentManager_NodeManagerService()
App_KernelDevDebugContainer.php:2505, ContainerTqpZCZm\App_KernelDevDebugContainer->getSuluDocumentManager_DocumentManagerService()
App_KernelDevDebugContainer.php:1971, ContainerTqpZCZm\App_KernelDevDebugContainer->getSulu_Content_MapperService()
App_KernelDevDebugContainer.php:5741, ContainerTqpZCZm\App_KernelDevDebugContainer->getTwigService()
App_KernelDevDebugContainer.php:5312, ContainerTqpZCZm\App_KernelDevDebugContainer->getSuluForm_RequestListenerService()
App_KernelDevDebugContainer.php:1472, ContainerTqpZCZm\App_KernelDevDebugContainer->ContainerTqpZCZm\{closure:/home/david/tmp/reproducer-jackalope-error-1-9-0/var/cache/admin/dev/ContainerTqpZCZm/App_KernelDevDebugContainer.php:1471-1473}()
EventDispatcher.php:221, Symfony\Component\EventDispatcher\EventDispatcher->sortListeners()
EventDispatcher.php:70, Symfony\Component\EventDispatcher\EventDispatcher->getListeners()
TraceableEventDispatcher.php:257, Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher->preProcess()
TraceableEventDispatcher.php:121, Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher->dispatch()
HttpKernel.php:139, Symfony\Component\HttpKernel\HttpKernel->handleRaw()
HttpKernel.php:74, Symfony\Component\HttpKernel\HttpKernel->handle()
Kernel.php:184, Symfony\Component\HttpKernel\Kernel->handle()
index.php:68, {main}()

we have a reproducer application that you can hopefully use:

git clone git@github.com:alexander-schranz/reproducer-jackalope-error-1-9-0.git
cd reproducer-jackalope-error-1-9-0
composer install

docker compose up -d

bin/console sulu:build dev

symfony serve

go to https://localhost:8000/admin, login with admin/admin and try to create a new page in the example.com site. there is a async call that fails because an expected item is not found (it was created in the other session).

Possible Solution

the sulu session is defined as decorating the phpcr session at https://github.com/sulu/sulu/blob/57bf8c4dcc59361033ef6d7acc5898b850f15b5c/src/Sulu/Bundle/DocumentManagerBundle/DependencyInjection/SuluDocumentManagerExtension.php#L160.

this is whats generated in the container:

    /**
     * Gets the public 'doctrine_phpcr.session' shared service.
     *
     * @return \Sulu\Bundle\DocumentManagerBundle\Session\Session
     */
    protected function getDoctrinePhpcr_SessionService()
    {
        $a = ($this->services['doctrine.dbal.default_connection'] ?? $this->getDoctrine_Dbal_DefaultConnectionService());

        if (isset($this->services['doctrine_phpcr.session'])) {
            return $this->services['doctrine_phpcr.session'];
        }

        return $this->services['doctrine_phpcr.session'] = new \Sulu\Bundle\DocumentManagerBundle\Session\Session([($this->privates['doctrine_phpcr.jackalope.repository.factory.service.doctrinedbal'] ??= new \Jackalope\RepositoryFactoryDoctrineDBAL())->getRepository(['jackalope.doctrine_dbal_connection' => $a, 'jackalope.check_login_on_server' => false]), 'login'](new \PHPCR\SimpleCredentials('admin', 'admin'), $this->getEnv('PHPCR_WORKSPACE')));
    }

the factory instance is shared, but getRepository is called whenever the session does not yet exist.

if i hack the container to look as following, the error is gone:

    /**
     * Gets the public 'doctrine_phpcr.session' shared service.
     *
     * @return \Sulu\Bundle\DocumentManagerBundle\Session\Session
     */
    protected function getDoctrinePhpcr_SessionService()
    {
        $a = ($this->services['doctrine.dbal.default_connection'] ?? $this->getDoctrine_Dbal_DefaultConnectionService());

        if (isset($this->services['doctrine_phpcr.session'])) {
            return $this->services['doctrine_phpcr.session'];
        }

        $b = [($this->privates['doctrine_phpcr.jackalope.repository.factory.service.doctrinedbal'] ??= new \Jackalope\RepositoryFactoryDoctrineDBAL())->getRepository(['jackalope.doctrine_dbal_connection' => $a, 'jackalope.check_login_on_server' => false]), 'login'](new \PHPCR\SimpleCredentials('admin', 'admin'), $this->getEnv('PHPCR_WORKSPACE'));

        if (isset($this->services['doctrine_phpcr.session'])) {
            return $this->services['doctrine_phpcr.session'];
        }

        return $this->services['doctrine_phpcr.session'] = new \Sulu\Bundle\DocumentManagerBundle\Session\Session($b);
    }

to my understanding this means that $this->services['doctrine_phpcr.session'] is written twice, which seems incorrect to me.

the tricky bit is that my $b should not be above the first check if we have a session service, as it would create overhead and potentially trigger new problems if we already have the session. i guess the idea with $a is that if we already have the service, its super cheap as it must be in the container already.

Additional Context

As a workaround, we can make sulu define its decorating session service as lazy. When it is lazy, only one instance gets created as the access to doctrine connection that triggers the other instance only happens when the first instance is already registered in the container, so no extra instance is created.

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

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