Skip to content
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

Doctrine 2.5 - Entity Update Bug #7035

Closed
nepster-web opened this issue Feb 2, 2018 · 7 comments
Closed

Doctrine 2.5 - Entity Update Bug #7035

nepster-web opened this issue Feb 2, 2018 · 7 comments
Assignees

Comments

@nepster-web
Copy link

nepster-web commented Feb 2, 2018

I have the entity (this is simple example):

class Record
{
    /**
     * @ORM\Column(type="ContentContext_RecordTranslationCollection")
     * @var RecordTranslationCollection
     */
    private $translations;

    public function __construct()
    {
        $this->translations = new RecordTranslationCollection();
    }

    public function getTranslations(): RecordTranslationCollection
    {
        return $this->translations;
    }

    public function addTranslation(RecordTranslation $translation): void
    {
        /** @var RecordTranslation $translation */
        foreach ($this->translations as $currentTranslation) {
            if ($currentTranslation->getLocale() === $translation->getLocale()) {
                $this->translations->removeElement($currentTranslation);
                break;
            }
        }
        $this->translations->add($translation);
    }
}

My RecordTranslationCollection extends \Doctrine\Common\Collections\ArrayCollection

And my ContentContext_RecordTranslationCollection type looks like this:

class RecordTranslationCollectionType extends JsonType
{
    private $hydrator;

    public function setHydrator(Hydrator $hydrator)
    {
        $this->hydrator = $hydrator;
    }

    public function convertToDatabaseValue($collection, AbstractPlatform $platform)
    {
        $result = [];
        foreach ($collection as $entity) {
            $result[] = $this->hydrator->extract($entity);
        }

        return json_encode($result);
    }

    public function convertToPHPValue($json, AbstractPlatform $platform)
    {
        $data = json_decode($json, true) ?? [];
        $collection = new RecordTranslationCollection();
        foreach ($data as $translate) {
            $collection->add($this->hydrator->hydrate($translate, RecordTranslation::class));
        }
        return $collection;
    }

}

Ok, it all works when you create and save a new entity! But there is a problem when updating.
When I get the entity Record from the database to update the collection of translations, for example:

$record = // query to db ;

$translate1 = new RecordTranslation(); // for convenience, let's skip the data
$translate2 = new RecordTranslation(); // for convenience, let's skip the data

$record->addTranslation($translate1);
$record->addTranslation($translate2);

$this->entityManager->persist($record);
$this->entityManager->flush();

And my new collection does not save.
Doctrine does not understand that the object RecordTranslationCollection has changed.
And the method convertToDatabaseValue is not called.

If you use a spike, for example:

$r = new ReflectionProperty(get_class($record), 'translations');
$r->setAccessible(true);
$r->setValue($object, clone $record->getTranslations());

$this->entityManager->persist($record);
$this->entityManager->flush();

The entity will be saved correctly. So doctrine does not see the changes inside the object, but only see the object itself.

@Majkl578
Copy link
Contributor

Majkl578 commented Feb 2, 2018

Changes to object properties are detected by reference so if the object (instance) does not change, it's deemed to be same. See description fo DateTime, it applies to objects in general: https://doctrine-orm.readthedocs.io/projects/doctrine-orm/en/latest/cookbook/working-with-datetime.html#datetime-changes-are-detected-by-reference

@Ocramius
Copy link
Member

Ocramius commented Feb 3, 2018

@nepster-web
Copy link
Author

I think littering in entities is doubtful. Maybe it's better to use a setting for example:

    /**
     * @ORM\Column(type="ContentContext_RecordTranslationCollection", updatable=true)
     * @var RecordTranslationCollection
     */
    private $translations;

In my case it will be easier to extend method persist, check properties of the entity on the availability of necessary objects and use clone there where it is should be.

@Ocramius
Copy link
Member

Ocramius commented Feb 4, 2018

"littering in entities"?

@nepster-web
Copy link
Author

Sorry for my beginner's English.

I mean it's trash into entities.

@Ocramius
Copy link
Member

Ocramius commented Feb 4, 2018

No, it's just good practice to avoid sharing referentially kept mutable state by de-referencing via clone. See also https://ocramius.github.io/extremely-defensive-php/ and https://ocramius.github.io/doctrine-best-practices/ for further tips ;-)

@nepster-web
Copy link
Author

Thank you very much!

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

3 participants