doctrine / orm

Doctrine Object Relational Mapper (ORM)
https://www.doctrine-project.org/projects/orm.html
MIT License
9.91k stars 2.5k forks source link

Level 2-cached entities with OneToOne marked as READ_ONLY cannot be persisted #10410

Open kiler129 opened 1 year ago

kiler129 commented 1 year ago

Bug Report

Q A
BC Break no
Version 2.13.0 & 2.14.0 (possibly earlier too)

Summary

Given the following entity:

<?php
declare(strict_types=1);

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Ramsey\Uuid\Uuid;

#[ORM\Entity(readOnly: true)]
#[ORM\Cache(usage: 'READ_ONLY', region: 'SYS_DEFINITIONS')]
class TestEntity
{
    #[ORM\Id]
    #[ORM\Column(type: "uuid", unique: true)]
    public string $id;

    #[ORM\Column]
    public string $name;

    #[ORM\OneToOne]
    public ?TestEntity $parent;

    public function __construct(string $name)
    {
        $this->id = Uuid::uuid4()->toString();
        $this->name = $name;
    }
}

I attempted to create two instances and assign one as a parent to the other:

$parent = new TestEntity('parent');
$child = new TestEntity('child');
$child->parent = $parent;

$om->persist($parent);
$om->persist($child);
$om->flush();

Current behavior

In CannotUpdateReadOnlyEntity.php line 13:

  [Doctrine\ORM\Cache\Exception\CannotUpdateReadOnlyEntity]  
  Cannot update a readonly entity "App\Entity\TestEntity"    

Exception trace:
  at /app/vendor/doctrine/orm/lib/Doctrine/ORM/Cache/Exception/CannotUpdateReadOnlyEntity.php:13
 Doctrine\ORM\Cache\Exception\CannotUpdateReadOnlyEntity::fromEntity() at /app/vendor/doctrine/orm/lib/Doctrine/ORM/Cache/Persister/Entity/ReadOnlyCachedEntityPersister.php:20
 Doctrine\ORM\Cache\Persister\Entity\ReadOnlyCachedEntityPersister->update() at /app/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php:588
 Doctrine\ORM\UnitOfWork->executeExtraUpdates() at /app/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php:442
 Doctrine\ORM\UnitOfWork->commit() at /app/vendor/doctrine/orm/lib/Doctrine/ORM/EntityManager.php:403
 Doctrine\ORM\EntityManager->flush() at /app/src/Command/SandboxCommand.php:44
 App\Command\SandboxCommand->execute() at /app/vendor/symfony/console/Command/Command.php:312
 Symfony\Component\Console\Command\Command->run() at /app/vendor/symfony/console/Application.php:1040
 Symfony\Component\Console\Application->doRunCommand() at /app/vendor/symfony/framework-bundle/Console/Application.php:88
 Symfony\Bundle\FrameworkBundle\Console\Application->doRunCommand() at /app/vendor/symfony/console/Application.php:314
 Symfony\Component\Console\Application->doRun() at /app/vendor/symfony/framework-bundle/Console/Application.php:77
 Symfony\Bundle\FrameworkBundle\Console\Application->doRun() at /app/vendor/symfony/console/Application.php:168
 Symfony\Component\Console\Application->run() at /app/vendor/symfony/runtime/Runner/Symfony/ConsoleApplicationRunner.php:54
 Symfony\Component\Runtime\Runner\Symfony\ConsoleApplicationRunner->run() at /app/vendor/autoload_runtime.php:29
 require_once() at /app/bin/console:11

How to reproduce

The code above is a minimal example. I also tested with ManyToOne and the behavior is the same. Interestingly changing Entity(readOnly: true) to Entity(readOnly: false) doesn't affect the problem. What does thou is putting a second flush like so:

$om->persist($parent);
$om->flush();
$om->persist($child);
$om->flush();

Expected behavior

Entities should persist, especially given that keys for the relationship are known. I'm suspecting (thou I didn't debug as it's 3am ;)) it may try to re-persist the $parent after getting $child into the cache/db, in order to fill-in the relationship.

mpdude commented 1 year ago

What if you make the entities not be readOnly: false, and allow the parent association to be nullable?

mpdude commented 1 year ago

IMHO, with readonly: true you should not be able to persist in the first place, or that should be a no-op

kiler129 commented 1 year ago

@mpdude I didn't try the nullable yet. However, it's not a no-op since otherwise you wouldn't be able to add the entity at all. A better name would probably be something akin to wrom.

The docs eexplicitly describe that (https://www.doctrine-project.org/projects/doctrine-orm/en/2.14/reference/improving-performance.html#read-only-entities):

Read-Only allows to persist new entities of a kind and remove existing ones, they are just not considered for updates.

mpdude commented 1 year ago

Oh! TIL