orm: Upgrading from 2.16.3 to 2.17.0 breaks serialization when using symfony/var-exporter

BC Break Report

Could not find anything in release notes so far that predicted this would break. Was also not yet able to determine which, if any, of the changes listed cause this behaviour. Will update this issue as I get further along.

Stacktrace is as follows (omitted a few lines at the bottom for brevity):

TypeError:
Doctrine\DBAL\Connection::executeQuery(): Argument #1 ($sql) must be of type string, null given, called in /srv/vendor/doctrine/orm/lib/Doctrine/ORM/Query/Exec/SingleSelectExecutor.php on line 33

  at vendor/doctrine/dbal/src/Connection.php:1071
  at Doctrine\DBAL\Connection->executeQuery(null, array(), array(), null)
     (vendor/doctrine/orm/lib/Doctrine/ORM/Query/Exec/SingleSelectExecutor.php:33)
  at Doctrine\ORM\Query\Exec\SingleSelectExecutor->execute(object(Connection), array(), array())
     (vendor/doctrine/orm/lib/Doctrine/ORM/Query.php:327)
  at Doctrine\ORM\Query->_doExecute()
     (vendor/doctrine/orm/lib/Doctrine/ORM/AbstractQuery.php:1212)
  at Doctrine\ORM\AbstractQuery->executeIgnoreQueryCache(null, 3)
     (vendor/doctrine/orm/lib/Doctrine/ORM/AbstractQuery.php:1166)
  at Doctrine\ORM\AbstractQuery->execute(null, 3)
     (vendor/doctrine/orm/lib/Doctrine/ORM/AbstractQuery.php:937)
  at Doctrine\ORM\AbstractQuery->getScalarResult()
     (vendor/doctrine/orm/lib/Doctrine/ORM/Tools/Pagination/Paginator.php:153)
  at Doctrine\ORM\Tools\Pagination\Paginator->getIterator()
  at iterator_to_array(object(Paginator))
     (src/<redacted>/Doctrine/Repository/CategoryRepository.php:46)
  at <redacted>\Doctrine\Repository\CategoryRepository->getRootCategories(25, 0)
     (src/<redacted>/CategoryBundle/Menu/MenuBuilder.php:119)
  at <redacted>\CategoryBundle\Menu\MenuBuilder->getCategoryTreeMenu(object(MenuItem), true)
     (src/<redacted>/CategoryBundle/Menu/MenuBuilder.php:114)
  at <redacted>\CategoryBundle\Menu\MenuBuilder->createMobileCategoryMenu(object(RequestStack))
     (var/cache/nl/docker/ContainerKpuxm9P/getPsCategory_Menu_MobileCategoriesService.php:23)
  at ContainerKpuxm9P\getPsCategory_Menu_MobileCategoriesService::do(object(AppKernelDockerDebugContainer), true)
     (var/cache/nl/docker/ContainerKpuxm9P/AppKernelDockerDebugContainer.php:898)
  at ContainerKpuxm9P\AppKernelDockerDebugContainer->load('getPsCategory_Menu_MobileCategoriesService.php')
     (var/cache/nl/docker/ContainerKpuxm9P/getKnpMenu_MenuProvider_LazyService.php:22)
  at ContainerKpuxm9P\getKnpMenu_MenuProvider_LazyService::ContainerKpuxm9P\{closure}(array())
     (vendor/knplabs/knp-menu/src/Knp/Menu/Provider/LazyProvider.php:39)
  at Knp\Menu\Provider\LazyProvider->get('mobile_categories', array())
     (vendor/knplabs/knp-menu/src/Knp/Menu/Provider/ChainProvider.php:26)
  at Knp\Menu\Provider\ChainProvider->get('mobile_categories', array())
     (vendor/knplabs/knp-menu/src/Knp/Menu/Twig/Helper.php:43)
  at Knp\Menu\Twig\Helper->get('mobile_categories', array())
     (vendor/knplabs/knp-menu/src/Knp/Menu/Twig/Helper.php:142)
  at Knp\Menu\Twig\Helper->castMenu('mobile_categories')
     (vendor/knplabs/knp-menu/src/Knp/Menu/Twig/Helper.php:75)
Q A
BC Break yes
Version 2.17.0

Summary

See stacktrace above.

Previous behavior

No exception was thrown.

Current behavior

Exception is thrown.

How to reproduce

Hard to tell, might be related to our code, might not be? Still debugging.


Update(s)

It seems that turning on lazy ghosts resolves the issue. However, that does not seem to be the default configuration in our symfony application. Is this the right way to deprecate something?

Turning it back off does not break things. Looking more likely to be cache related, making this a non-issue. Will try a new deployment with some more rigorous cache purging.

Sigh. Works on development environment now, broken on production still. More debugging needed.

About this issue

  • Original URL
  • State: closed
  • Created 7 months ago
  • Reactions: 17
  • Comments: 76 (66 by maintainers)

Commits related to this issue

Most upvoted comments

Fix for Symfony: https://github.com/symfony/symfony/pull/52618

Thank you all for the help on this one!

Hi. I have the same error. Reverting to 2.16.3 solves the problem.

Awesome, many thanks to everyone who got involved so quickly to resolve this 🥳 !

@szczyglis-dev I said: YOU JUST NEED TO WAIT

So just wait. No need for more reports.

On 2.16.3 it yields this file:

<?php //ec5dca27522cfad6c17af2027e1d3000

return [PHP_INT_MAX, static function () { return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
    $o = [
        clone (($p = &\Symfony\Component\VarExporter\Internal\Registry::$prototypes)['Doctrine\\ORM\\Query\\ParserResult'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('Doctrine\\ORM\\Query\\ParserResult')),
        clone ($p['Doctrine\\ORM\\Query\\Exec\\SingleSelectExecutor'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('Doctrine\\ORM\\Query\\Exec\\SingleSelectExecutor')),
        clone ($p['Doctrine\\ORM\\Query\\ResultSetMapping'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('Doctrine\\ORM\\Query\\ResultSetMapping')),
    ],
    null,
    [
        'Doctrine\\ORM\\Query\\Exec\\AbstractSqlExecutor' => [
            '_sqlStatements' => [
                1 => 'SELECT c0_.id AS id_0, c0_.name AS name_1, c0_.slug AS slug_2, c0_.icon AS icon_3, c0_.datatricsId AS datatricsId_4, c0_.weight AS weight_5, c0_.filteringEnabled AS filteringEnabled_6, c0_.root AS root_7, c0_.lvl AS lvl_8, c0_.lft AS lft_9, c0_.rgt AS rgt_10, c0_.createdAt AS createdAt_11, c0_.updatedAt AS updatedAt_12, c0_.parent_id AS parent_id_13, c0_.seoDetails_id AS seoDetails_id_14 FROM category c0_ WHERE c0_.lft <= 1 AND c0_.rgt >= 2 AND c0_.root = ? ORDER BY c0_.lft ASC',
            ],
        ],
        'stdClass' => [
            'aliasMap' => [
                2 => [
                    'node' => '<redacted>\\Entity\\Category',
                ],
            ],
            'fieldMappings' => [
                2 => [
                    'id_0' => 'id',
                    'name_1' => 'name',
                    'slug_2' => 'slug',
                    'icon_3' => 'icon',
                    'datatricsId_4' => 'datatricsId',
                    'weight_5' => 'weight',
                    'filteringEnabled_6' => 'filteringEnabled',
                    'root_7' => 'root',
                    'lvl_8' => 'level',
                    'lft_9' => 'left',
                    'rgt_10' => 'right',
                    'createdAt_11' => 'createdAt',
                    'updatedAt_12' => 'updatedAt',
                ],
            ],
            'typeMappings' => [
                2 => [
                    'parent_id_13' => 'smallint',
                    'seoDetails_id_14' => 'integer',
                ],
            ],
            'entityMappings' => [
                2 => [
                    'node' => null,
                ],
            ],
            'metaMappings' => [
                2 => [
                    'parent_id_13' => 'parent_id',
                    'seoDetails_id_14' => 'seoDetails_id',
                ],
            ],
            'columnOwnerMap' => [
                2 => [
                    'id_0' => 'node',
                    'name_1' => 'node',
                    'slug_2' => 'node',
                    'icon_3' => 'node',
                    'datatricsId_4' => 'node',
                    'weight_5' => 'node',
                    'filteringEnabled_6' => 'node',
                    'root_7' => 'node',
                    'lvl_8' => 'node',
                    'lft_9' => 'node',
                    'rgt_10' => 'node',
                    'createdAt_11' => 'node',
                    'updatedAt_12' => 'node',
                    'parent_id_13' => 'node',
                    'seoDetails_id_14' => 'node',
                ],
            ],
            'declaringClasses' => [
                2 => [
                    'id_0' => '<redacted>\\Entity\\Category',
                    'name_1' => '<redacted>\\Entity\\Category',
                    'slug_2' => '<redacted>\\Entity\\Category',
                    'icon_3' => '<redacted>\\Entity\\Category',
                    'datatricsId_4' => '<redacted>\\Entity\\Category',
                    'weight_5' => '<redacted>\\Entity\\Category',
                    'filteringEnabled_6' => '<redacted>\\Entity\\Category',
                    'root_7' => '<redacted>\\Entity\\Category',
                    'lvl_8' => '<redacted>\\Entity\\Category',
                    'lft_9' => '<redacted>\\Entity\\Category',
                    'rgt_10' => '<redacted>\\Entity\\Category',
                    'createdAt_11' => '<redacted>\\Entity\\Category',
                    'updatedAt_12' => '<redacted>\\Entity\\Category',
                ],
            ],
        ],
    ],
    $o[0],
    [
        [
            "\0".'Doctrine\\ORM\\Query\\ParserResult'."\0".'sqlExecutor' => $o[1],
            "\0".'Doctrine\\ORM\\Query\\ParserResult'."\0".'resultSetMapping' => $o[2],
            "\0".'Doctrine\\ORM\\Query\\ParserResult'."\0".'parameterMappings' => [
                'rid' => [
                    0,
                ],
            ],
        ],
    ]
); }];

Thanks, but we have 2 fixes on the way already, and pinpointed the issue a few messages ago. You just need to wait for either a new version of doctrine/orm or symfony/var-exporter.

One could argue that this method should not return the properties with their internal visibility names, but as @greg0ire mentioned before, get_object_vars is not an option in this case as this will skip the private properties of subclasses. Using Reflection is another option, but I’m not sure if that’s the best option in this case neither 😕

@nicolas-grekas in short, this is the problem:

<?php

use Symfony\Component\VarExporter\VarExporter;

require_once '/path/to/project-with-var-exporter/autoload.php';

class T {
  protected $a;

  public function __construct()
  {
    $this->a = '123';
  }

  public function __sleep(): array
  {
    //return ['a'];
    return array_keys((array) $this);
  }
}

var_dump(VarExporter::export(new T()));
var_dump(serialize(new T()));

The (array) $this returns the properties with the internal visibility names, which does not work with VarExporter.

Returning those properties with the internal visibility names and executing serialize on it will work however.

From what I can tell though, it breaks on production when storing and retrieving the ParserResult instance. So production works on first deploy (no cache). Then as soon as cache items get generated, things break.

The PhpFilesAdapter uses VarExporter.

The result of VarExporter::export in PhpFilesAdapter with my change:

string(2103) "\Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
    $o = [
        clone (($p = &\Symfony\Component\VarExporter\Internal\Registry::$prototypes)['Doctrine\\ORM\\Query\\ParserResult'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('Doctrine\\ORM\\Query\\ParserResult')),
        clone ($p['Doctrine\\ORM\\Query\\Exec\\SingleSelectExecutor'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('Doctrine\\ORM\\Query\\Exec\\SingleSelectExecutor')),
        clone ($p['Doctrine\\ORM\\Query\\ResultSetMapping'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('Doctrine\\ORM\\Query\\ResultSetMapping')),
    ],
    null,
    [
        'Doctrine\\ORM\\Query\\Exec\\AbstractSqlExecutor' => [
            'sqlStatements' => [
                1 => 'Ive removed info here',
            ],
        ],
        'stdClass' => [
            'scalarMappings' => [2 => ['I removed info here']],
            'typeMappings' => [
                2 => ['Ive removed info here'],
            ],
        ],
    ],
    $o[0],
    [
        [
            "\0".'Doctrine\\ORM\\Query\\ParserResult'."\0".'sqlExecutor' => $o[1],
            "\0".'Doctrine\\ORM\\Query\\ParserResult'."\0".'resultSetMapping' => $o[2],
            "\0".'Doctrine\\ORM\\Query\\ParserResult'."\0".'parameterMappings' => [
                'asdf' => [
                    0,
                ],
            ],
        ],
        1,
    ]
)"

Result of unmodified code:

string(1560) "\Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
    $o = [
        clone (($p = &\Symfony\Component\VarExporter\Internal\Registry::$prototypes)['Doctrine\\ORM\\Query\\ParserResult'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('Doctrine\\ORM\\Query\\ParserResult')),
        clone ($p['Doctrine\\ORM\\Query\\Exec\\SingleSelectExecutor'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('Doctrine\\ORM\\Query\\Exec\\SingleSelectExecutor')),
        clone ($p['Doctrine\\ORM\\Query\\ResultSetMapping'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('Doctrine\\ORM\\Query\\ResultSetMapping')),
    ],
    null,
    [
        'stdClass' => [
            'scalarMappings' => [
                2 => ['ive removed info here'],
            ],
            'typeMappings' => [
                2 => ['ive removed info here'],
            ],
        ],
    ],
    $o[0],
    [
        [
            "\0".'Doctrine\\ORM\\Query\\ParserResult'."\0".'sqlExecutor' => $o[1],
            "\0".'Doctrine\\ORM\\Query\\ParserResult'."\0".'resultSetMapping' => $o[2],
            "\0".'Doctrine\\ORM\\Query\\ParserResult'."\0".'parameterMappings' => [
                'asdfasdf' => [
                    0,
                ],
            ],
        ],
        1,
    ]
)"

Notice the missing sqlStatements key.

You can check this by dumping the result of VarExporter::export in PhpFilesAdapter on line 227

@greg0ire Yes, I’m aware of the fact that it governs what to serialize. I’m switching to get_object_vars to get rid of the \0x\0 prefixes, which I thought was causing the issue.

As you can see in the grep result, my modification will still ensure sqlStatements is serialized, not _sqlStatements.

Anyways, i’m not saying this is the fix, but this is what I see

I take it back, development release works (but runs in development mode). Production broken again. Manually purging caches did not resolve it there. Diving back into what the cause is.

For me it was resolved by purging our cache more explicitly.