Doctrine 2 - ORM
  1. Doctrine 2 - ORM
  2. DDC-2411

Null values get reset when rehydrating an already managed entity

    Details

    • Type: Bug Bug
    • Status: Open
    • Priority: Major Major
    • Resolution: Unresolved
    • Affects Version/s: 2.3
    • Fix Version/s: None
    • Component/s: ORM
    • Security Level: All
    • Labels:

      Description

      Scenario:

      1) You have an entity with a ManyToOne relation (and probably other kinds too, but this is all I have tested) to another entity which is nullable. For example, let's say you have a Book entity which has an "illustrator" field which refers to a Person entity, representing the person who illustrated the book. If the book is not illustrated then you set the field to null.

      2) You fetch a Book (by ID) which has its illustrator set to a particular Person.

      3) You set that Book's illustrator to null.

      4) Without flushing, you fetch the Book again, using different criteria: for example, by title. Because entities are Identity Mapped, this will run a query but then locate the same instance in memory, and try to hydrate that instance with the old data it just fetched.

      5) Any fields on the instance that have modified values retain their new values (for example, if we changed the illustrator to a different Person, this would be retained), BUT any fields on the instance which are null get overwritten with the old data (so if we previously set the illustrator to null, without flushing, it would now be reset to the Person value that it had before).

      There seems to be a mistaken assumption here that null values are fields that have not been hydrated, when this is not necessarily the case. Is this the intended behaviour?

      The code that causes this behaviour is here: https://github.com/doctrine/doctrine2/blob/e561f47cb2205565eb873f0643637477bfcfc2ff/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php#L471

      If you are wondering why anybody would want to fetch the entity again in step 4, my use case for this is the Symfony Validator (but I presume there could be others).

      If there are any unique constraints (Symfony ones, not Doctrine ones) on the entity, e.g. if we had a unique constraint on the Book title field, then when validating the Book the Symfony Validator would check if there are already any Book entities with the same title as the Book we're validating. It will find the Book that we are working with, and because entities are identity mapped, it will act upon the same instance, and the situation above occurs.

      Code example:

      <?php

      // Create some entities

      $john = new Person();
      $john->setName('John Smith');

      $jane = new Person();
      $jane->setName('Jane Jones');

      $joe = new Person();
      $joe->setName('Joe Bloggs');

      $book = new Book();
      $book->setId(123);
      $book->setTitle('Book Title');
      $book->setIllustrator($john);
      $book->setAuthor($jane);

      $em->persist($john);
      $em->persist($jane);
      $em->persist($joe);
      $em->persist($book);
      $em->flush();

      // Now let's try modifying the book

      $book = $bookRepository->find(123);
      $book->getIllustrator(); // returns Person "John Smith"
      $book->getAuthor(); // returns Person "Jane Jones"

      // make some changes
      $book->setIllustrator(null); // illustrator is now null
      $book->setAuthor($joe); // author is now "Joe Bloggs"

      // now validate our changes with Symfony Validator
      // note: the same effect can also be observed with
      // $test = $bookRepository->findBy('title', 'Book Title');
      $validator->validate($book);

      // what happened to our book??
      $book->getIllustrator(); // returns Person "John Smith" <- should be null
      $book->getAuthor(); // returns Person "Joe Bloggs" <- correctly retains the new value

        Activity

        Hide
        Benjamin Eberlei added a comment -

        Verified by code review that this issue exists, but it will be very tricky to fix, because the null check is there for other reasons as well.

        Show
        Benjamin Eberlei added a comment - Verified by code review that this issue exists, but it will be very tricky to fix, because the null check is there for other reasons as well.
        Hide
        Fabio B. Silva added a comment -

        Hi Simon,

        Could you please try to write a failing test case or paste your entities ?

        Cheers

        Show
        Fabio B. Silva added a comment - Hi Simon, Could you please try to write a failing test case or paste your entities ? Cheers

          People

          • Assignee:
            Benjamin Eberlei
            Reporter:
            Simon Garner
          • Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

            Dates

            • Created:
              Updated: