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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
BboyKeen opened this issue Sep 14, 2015 · 9 comments

Comments

@BboyKeen
Copy link

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

@h3llr4iser
Copy link

I have symfony 2.7.3 and I have the same exception after upgrading doctrine to 2.5

@ZCJ
Copy link
ZCJ commented Oct 21, 2015

@BboyKeen @h3llr4iser - I am facing the same issue in the 'unscheduleOrphanRemoval' and applied a similar workaround. Is this issue resolved ?

@ashatp
Copy link
ashatp commented Oct 21, 2015

+1

@BboyKeen
Copy link
Author

No response from the teams so I don't know.

I'm still using the workaround I've provided.

@kriswallsmith
Copy link
Contributor

This is fixed by #16090.

@fabpot fabpot closed this as completed Oct 22, 2015
@stof
Copy link
Member
stof commented Oct 22, 2015

note that the fix is not yet released. It will be part of the next 2.3.x and 2.7.x releases

@ZCJ
Copy link
ZCJ commented Oct 26, 2015

Thank you.
Is there any way to know when 2.7.6 is getting released?

@xabbuh
Copy link
Member
xabbuh commented Oct 26, 2015

@ZCJ you can subscribe to the Symfony Roadmap Notifications

@BboyKeen
Copy link
Author
BboyKeen commented Nov 4, 2015

Thanks for the fix !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants
0