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.
// Create some entities
$john = new Person();
$jane = new Person();
$joe = new Person();
$book = new Book();
// 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');
// 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