Skip to content

Doctrine\Common\Collections\ArrayCollection::set(): Argument #1 ($key) must be of type string|int, null given, called in /path/to/project/vendor/doctrine/orm/src/PersistentCollection.php on line 162 #11790

@savemetenminutes

Description

@savemetenminutes

Bug Report

Q A
Version 3.3.1
Previous Version if the bug is a regression 3.3.0

Summary

A regression was introduced by this commit:
https://github.com/doctrine/orm/commit/439b4dacf415b743b0d40d65c490a4123759c520/#diff-ecc4306d602db732bf06eda6ac0fc41b23fe8a25f4a2548196f813f30dd2ac35R1277

  1. Have an entity with a one-to-many relation
  2. Define index-by in the mapping
  3. Within the same UnitOfWork perform (Edit: this probably happens every time an entity is fetched for a second time as part of a collection which has index-by defined in the mapping after enabling or disabling a filter, which causes the persister's filter hash to change):
  • $childEntity->getParent() so that it goes through vendor/doctrine/orm/src/Proxy/ProxyFactory.php:279
    $entityPersister = $this->uow->getEntityPersister($className);
  • enable a doctrine filter, which changes the $this->filterHash of the BasicEntityPersister
  • $parentEntity->getChildCollection() so that it goes through vendor/doctrine/orm/src/UnitOfWork.php:2702
    $persister = $this->getEntityPersister($assoc->targetEntity);
  1. This will cause the same instance of the BasicEntityPersister to populate its ->currentPersisterContext->rsm instance of ResultSetMapping by calling the ->addFieldResult twice for each ChildEntity column. So the ResultSetMapping->fieldMappings array in the end will hold each entity column twice, but with different alias (index). The \Doctrine\ORM\Persisters\Entity\BasicEntityPersister::getSelectColumnsSQL() mechanism for detecting whether the current instance has already calculated the select columns from a previous query
    vendor/doctrine/orm/src/Persisters/Entity/BasicEntityPersister.php:1234
    if ($this->currentPersisterContext->selectColumnListSql !== null && $this->filterHash === $this->em->getFilters()->getHash()) {
    is bypassed due to the new filter hash, but the ->currentPersisterContext->sqlAliasCounter is not reset and
    vendor/doctrine/orm/src/Persisters/Entity/BasicEntityPersister.php:1243
    $columnList[] = $this->getSelectColumnSQL($field, $this->class);
    will result in
    \Doctrine\ORM\Persisters\Entity\BasicEntityPersister::getSQLColumnAlias()
    to keep incrementing the
    $this->currentPersisterContext->sqlAliasCounter++
    from the position it was left at from the previous query, thus the second column set with the greater alias indexes will eventually be fetched in the result row. Due to the entity persister's ResultSetMapping instance's ->indexByMap pointing to the alias with the lesser index the following happens:
    vendor/doctrine/orm/src/Internal/Hydration/ObjectHydrator.php:498
    $resultKey = $row[$this->resultSetMapping()->indexByMap[$dqlAlias]];
    Here $resultKey becomes null and
    $this->hints['collection']->hydrateSet($resultKey, $element);
    in turn calls
    vendor/doctrine/orm/src/PersistentCollection.php:162
    $this->unwrap()->set($key, $element);
    resulting in the exception from the title of the issue.

Current behavior

An exception is thrown.

Expected behavior

Collection indexing used to work in this case in version 3.3.0 of doctrine/orm

How to reproduce

Providing partial stack traces of the two fetches within the same UnitOfWork - with the breakpoint set at the problematic double addition of the columns in the ResultSetMapping->fieldMappings.

ResultSetMapping.php:327, Doctrine\ORM\Query\ResultSetMapping->addFieldResult()
BasicEntityPersister.php:1505, Doctrine\ORM\Persisters\Entity\BasicEntityPersister->getSelectColumnSQL()
BasicEntityPersister.php:1243, Doctrine\ORM\Persisters\Entity\BasicEntityPersister->getSelectColumnsSQL()
BasicEntityPersister.php:1119, Doctrine\ORM\Persisters\Entity\BasicEntityPersister->getSelectSQL()
BasicEntityPersister.php:734, Doctrine\ORM\Persisters\Entity\BasicEntityPersister->load()
BasicEntityPersister.php:754, Doctrine\ORM\Persisters\Entity\BasicEntityPersister->loadById()
ProxyFactory.php:220, Doctrine\ORM\Proxy\ProxyFactory::Doctrine\ORM\Proxy\{closure:/path/to/project/vendor/doctrine/orm/src/Proxy/ProxyFactory.php:219-242}()
ProxyFactory.php:286, Doctrine\Entities\__CG__\Devision\Auth\Context\UserClient\Domain\UserClient::Doctrine\ORM\Proxy\{closure:/path/to/project/vendor/doctrine/orm/src/Proxy/ProxyFactory.php:285-287}()
LazyObjectState.php:100, Symfony\Component\VarExporter\Internal\LazyObjectState->initialize()
LazyGhostTrait.php:178, Doctrine\Entities\__CG__\Devision\Auth\Context\UserClient\Domain\UserClient->__get()
UserClient.php:51, Devision\Auth\Context\UserClient\Domain\UserClient->getUser()
ResultSetMapping.php:327, Doctrine\ORM\Query\ResultSetMapping->addFieldResult()
BasicEntityPersister.php:1505, Doctrine\ORM\Persisters\Entity\BasicEntityPersister->getSelectColumnSQL()
BasicEntityPersister.php:1243, Doctrine\ORM\Persisters\Entity\BasicEntityPersister->getSelectColumnsSQL()
BasicEntityPersister.php:1119, Doctrine\ORM\Persisters\Entity\BasicEntityPersister->getSelectSQL()
BasicEntityPersister.php:1831, Doctrine\ORM\Persisters\Entity\BasicEntityPersister->getOneToManyStatement()
BasicEntityPersister.php:1779, Doctrine\ORM\Persisters\Entity\BasicEntityPersister->loadOneToManyCollection()
UnitOfWork.php:2706, Doctrine\ORM\UnitOfWork->loadCollection()
PersistentCollection.php:636, Doctrine\ORM\PersistentCollection->doInitialize()
PersistentCollection.php:186, Doctrine\ORM\PersistentCollection->initialize()
AbstractLazyCollection.php:138, Doctrine\Common\Collections\AbstractLazyCollection->getValues()
User.php:287, Devision\Auth\Context\User\Domain\User->getUserClientCollection()

And a JSON formatted partial stack trace of the Exception:

            "message": "Doctrine\\Common\\Collections\\ArrayCollection::set(): Argument #1 ($key) must be of type string|int, null given, called in /path/to/project/vendor/doctrine/orm/src/PersistentCollection.php on line 162",
            "code": 0,
            "type": "TypeError",
            "file:line": "/path/to/project/vendor/doctrine/collections/src/ArrayCollection.php:310",
            "previous": null,
            "trace": [
                {
                    "file:line": "/path/to/project/vendor/doctrine/orm/src/PersistentCollection.php:162",
                    "callee": "Doctrine\\Common\\Collections\\ArrayCollection->set",
                    "args": [
                        "NULL",
                        "{instanceof Devision\\Auth\\Context\\UserClient\\Domain\\UserClient}"
                    ]
                },
                {
                    "file:line": "/path/to/project/vendor/doctrine/orm/src/Internal/Hydration/ObjectHydrator.php:501",
                    "callee": "Doctrine\\ORM\\PersistentCollection->hydrateSet",
                    "args": [
                        "NULL",
                        "{instanceof Devision\\Auth\\Context\\UserClient\\Domain\\UserClient}"
                    ]
                },
                {
                    "file:line": "/path/to/project/vendor/doctrine/orm/src/Internal/Hydration/ObjectHydrator.php:143",
                    "callee": "Doctrine\\ORM\\Internal\\Hydration\\ObjectHydrator->hydrateRowData",
                    "args": [
                        "array (  'uuid_9' => '\\'<REDACTED>\\'',  'created_10' => '\\'<REDACTED>\\'',  'modified_11' => '\\'<REDACTED>\\'',  'not_archived_12' => '<REDACTED>',  'archived_13' => 'NULL',  'id_14' => '<REDACTED>',  'auth_user_id_15' => '<REDACTED>',  'auth_client_id_16' => '<REDACTED>',)",
                        "array ()"
                    ]
                },
                {
                    "file:line": "/path/to/project/vendor/doctrine/orm/src/Internal/Hydration/AbstractHydrator.php:168",
                    "callee": "Doctrine\\ORM\\Internal\\Hydration\\ObjectHydrator->hydrateAllData",
                    "args": []
                },
                {
                    "file:line": "/path/to/project/vendor/doctrine/orm/src/Persisters/Entity/BasicEntityPersister.php:1001",
                    "callee": "Doctrine\\ORM\\Internal\\Hydration\\AbstractHydrator->hydrateAll",
                    "args": [
                        "{instanceof Doctrine\\DBAL\\Result}",
                        "{instanceof Doctrine\\ORM\\Query\\ResultSetMapping}",
                        "array (  'deferEagerLoad' => 'true',  'collection' => '{instanceof Doctrine\\\\ORM\\\\PersistentCollection}',)"
                    ]
                },
                {
                    "file:line": "/path/to/project/vendor/doctrine/orm/src/Persisters/Entity/BasicEntityPersister.php:1781",
                    "callee": "Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister->loadCollectionFromStatement",
                    "args": [
                        "{instanceof Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping}",
                        "{instanceof Doctrine\\DBAL\\Result}",
                        "{instanceof Doctrine\\ORM\\PersistentCollection}"
                    ]
                },
                {
                    "file:line": "/path/to/project/vendor/doctrine/orm/src/UnitOfWork.php:2706",
                    "callee": "Doctrine\\ORM\\Persisters\\Entity\\BasicEntityPersister->loadOneToManyCollection",
                    "args": [
                        "{instanceof Doctrine\\ORM\\Mapping\\OneToManyAssociationMapping}",
                        "{instanceof Devision\\Auth\\Context\\User\\Domain\\User}",
                        "{instanceof Doctrine\\ORM\\PersistentCollection}"
                    ]
                },
                {
                    "file:line": "/path/to/project/vendor/doctrine/orm/src/PersistentCollection.php:636",
                    "callee": "Doctrine\\ORM\\UnitOfWork->loadCollection",
                    "args": [
                        "{instanceof Doctrine\\ORM\\PersistentCollection}"
                    ]
                },
                {
                    "file:line": "/path/to/project/vendor/doctrine/orm/src/PersistentCollection.php:186",
                    "callee": "Doctrine\\ORM\\PersistentCollection->doInitialize",
                    "args": []
                },
                {
                    "file:line": "/path/to/project/vendor/doctrine/collections/src/AbstractLazyCollection.php:138",
                    "callee": "Doctrine\\ORM\\PersistentCollection->initialize",
                    "args": []
                },
                {
                    "file:line": "/path/to/project/vendor/devision/devision-auth/src/Context/User/Domain/User.php:287",
                    "callee": "Doctrine\\Common\\Collections\\AbstractLazyCollection->getValues",
                    "args": []
                },
                {
                    "file:line": "/path/to/project/vendor/devision/adex-sales/src/Context/Auth/Context/User/Application/Service/UserService.php:292",
                    "callee": "Devision\\Auth\\Context\\User\\Domain\\User->getUserClientCollection",
                    "args": []
                },

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions