8000 Issue in collections with Symfony 2.6 (maybe in the other versions too) & Doctrine 2.5 · Issue #15797 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content
Issue in collections with Symfony 2.6 (maybe in the other versions too) & Doctrine 2.5 #15797
Closed
@BboyKeen

Description

@BboyKeen

Hey Symfony guys,

After upgrading Doctrine from 2.4 to 2.5, I've been facing a new problem with my collections which were not working well.
The entity association is simple : OneToMany; in my case one User linked to many Skills.
At first sight, this message appeared " 'spl_object_hash() expects parameter 1 to be object, null given' " with no reason.

Here is the stack trace :

 in vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php at line 2445  -

         */
        public function cancelOrphanRemoval($entity)
        {
            unset($this->orphanRemovals[spl_object_hash($entity)]);
        }
        /**

at ErrorHandler ->handleError ('2', 'spl_object_hash() expects parameter 1 to be object, null given', '/home/kevin/Dev/Yogosha/Web/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php', '2445', array('entity' => null))
at spl_object_hash (null)
in vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php at line 2445  +
at UnitOfWork ->cancelOrphanRemoval (null)
in vendor/doctrine/orm/lib/Doctrine/ORM/PersistentCollection.php at line 475  +
at PersistentCollection ->set ('9', null)
in vendor/doctrine/orm/lib/Doctrine/ORM/PersistentCollection.php at line 522  +
at PersistentCollection ->offsetSet ('9', null)
in vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php at line 226  +
at PropertyAccessor ->readPropertiesUntil (object(PersistentCollection), object(PropertyPath), '1', true)
in vendor/symfony/symfony/src/Symfony/Component/PropertyAccess/PropertyAccessor.php at line 58  +
at PropertyAccessor ->getValue (object(PersistentCollection), object(PropertyPath))
in vendor/symfony/symfony/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php at line 57  +
at PropertyPathMapper ->mapDataToForms (object(PersistentCollection), object(RecursiveIteratorIterator))
in vendor/symfony/symfony/src/Symfony/Component/Form/Form.php at line 921  +
at Form ->add ('9', object(RatedSkillType), array('property_path' => '[9]', 'block_name' => 'entry'))
in vendor/symfony/symfony/src/Symfony/Component/Form/Extension/Core/EventListener/ResizeFormListener.php at line 128  +
at ResizeFormListener ->preSubmit (object(FormEvent), 'form.pre_bind', object(EventDispatcher))
at call_user_func (array(object(ResizeFormListener), 'preSubmit'), object(FormEvent), 'form.pre_bind', object(EventDispatcher))
in app/cache/dev/classes.php at line 1791  +
at EventDispatcher ->doDispatch (array(array(object(BindRequestListener), 'preBind'), array(object(TrimListener), 'preSubmit'), array(object(CsrfValidationListener), 'preSubmit'), array(object(ResizeFormListener), 'preSubmit')), 'form.pre_bind', object(FormEvent))
in app/cache/dev/classes.php at line 1724  +
at EventDispatcher ->dispatch ('form.pre_bind', object(FormEvent))
in vendor/symfony/symfony/src/Symfony/Component/EventDispatcher/ImmutableEventDispatcher.php at line 43  +
at ImmutableEventDispatcher ->dispatch ('form.pre_bind', object(FormEvent))
in vendor/symfony/symfony/src/Symfony/Component/Form/Form.php at line 551  +

After looking at the Doctrine and Symfony code, I think I've spotted the problem.

If my understandings are good, when working with collections, Symfony has to prepare the field of the data_class object to receive potential new elements. This preparation is done at PRE_BIND state by the method "readPropertiesUntil" of PropertyAccessor class (line 191) by initializing new column with "null" value.

// Create missing nested arrays on demand
            if ($isIndex &&
                (
                    ($objectOrArray instanceof \ArrayAccess && !isset($objectOrArray[$property])) ||
                    (is_array($objectOrArray) && !array_key_exists($property, $objectOrArray))
                )
            ) {
                if (!$ignoreInvalidIndices) {
                    if (!is_array($objectOrArray)) {
                        if (!$objectOrArray instanceof \Traversable) {
                            throw new NoSuchIndexException(sprintf(
                                'Cannot read index "%s" while trying to traverse path "%s".',
                                $property,
                                (string) $propertyPath
                            ));
                        }

                        $objectOrArray = iterator_to_array($objectOrArray);
                    }

                    throw new NoSuchIndexException(sprintf(
                        'Cannot read index "%s" while trying to traverse path "%s". Available indices are "%s".',
                        $property,
                        (string) $propertyPath,
                        print_r(array_keys($objectOrArray), true)
                    ));
                }
                $objectOrArray[$property] = $i + 1 < $propertyPath->getLength() ? array() : null;
            }

As we can see, the missing columns in the array are added thanks to this line :
$objectOrArray[$property] = $i + 1 < $propertyPath->getLength() ? array() : null;

As Doctrine uses ArrayAccess implementation for the PersistentCollection, the use of $objectOrArray[$property] is equivalent to a call to the offsetSet method located at line 522 in the PersistentCollection class.

public function offsetSet($offset, $value)
    {
        if ( ! isset($offset)) {
            return $this->add($value);
        }

        return $this->set($offset, $value);
    }

Then a call to the set method is issued :

public function set($key, $value)
    {
        parent::set($key, $value);

        $this->changed();

        if ($this->em && $value != null) {
            $this->em->getUnitOfWork()->cancelOrphanRemoval($value);
        }
    }

And in my opinion, the problem comes here.

In Doctrine 2.4, the set method was :

public function set($key, $value)
    {
        $this->initialize();

        $this->coll->set($key, $value);

        $this->changed();
    }

Between these two versions, we can see the introduction of this piece of code :

if ($this->em) {
            $this->em->getUnitOfWork()->cancelOrphanRemoval($value);
        }

And cancelOrphanRemoval results in :

public function cancelOrphanRemoval($entity)
    {
        unset($this->orphanRemovals[spl_object_hash($entity)]);
    }

So, when the PropertyAccessor initializes the object to prepare it to receive the new value which will be inserted during the write phase, it issues unset($this->orphanRemovals[spl_object_hash(null)]) indirectly through the cancelOrphanRemoval method. That's the reason why the exception is produced. This call was not issued in Doctrine 2.4 which explains why there was no problem.

As a quick workaround, I've used this

if ($this->em && $value != null) {
            $this->em->getUnitOfWork()->cancelOrphanRemoval($value);
        }

because I don't know if it's the role of Doctrine to restrict null values or the role of Symfony to initialize the PersistentCollection in another way.

Sorry for this very long bugreport,

Keen

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

      0