Inheritance does not work properly after "Fix OneToManyPersister::deleteEntityCollection missing discriminator column/value." #7398

Open
opened 2026-01-22 15:51:12 +01:00 by admin · 0 comments
Owner

Originally created by @konrad-czernecki-esky on GitHub (Jul 15, 2024).

Bug Report

Q A
BC Break no
Version 2.19.6

Summary

After this fix https://github.com/doctrine/orm/issues/11500, I have a problem with the SINGLE_TABLE inheritance type, because when I set a new collection (in order to replace the previous one) in my one-to-many relation, the data in the old collection is not deleted.

Current behavior

The collection that is being replaced has not been replaced (old data remains + new appears)

How to reproduce

*Pseudocode

enum ComponentTypeEnum: string
{
    case EXAMPLE_COMPONENT = 'example';
    case EXAMPLE_COMPONENT2 = 'example_2';
}

#[ORM\Entity]
#[ORM\Table(name: 'components')]
#[ORM\InheritanceType('SINGLE_TABLE')]
#[ORM\DiscriminatorColumn(name: 'type', type: 'string')]
#[ORM\DiscriminatorMap(Component::ENTITY_MAP)]
abstract class Component extends AbstractComponent
{
    public const ENTITY_MAP = [
        ComponentTypeEnum::EXAMPLE_COMPONENT->value => ExampleComponent::class,
        ComponentTypeEnum::EXAMPLE_COMPONENT2->value => ExampleComponent2::class,
        //and so on
    ];

    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\ManyToOne(targetEntity: Page::class, inversedBy: 'components')]
    #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
    public ?Page $page = null;

    /**
     * @var mixed[]
     */
    #[ORM\Column(type: 'json', nullable: false)]
    public array $data = [];

    public function getId(): ?int
    {
        return $this->id;
    }
}
#[Entity]
class ExampleComponent extends Component
{
    public ComponentTypeEnum $type = ComponentTypeEnum::ExampleComponent;
}
#[ORM\Entity]
#[ORM\Table(name: 'pages')]
class Page
{
    #[ORM\Id]
    #[ORM\Column(type: 'uuid')]
    private string $id;

    #[ORM\OneToMany(
        mappedBy: 'page',
        targetEntity: Component::class,
        cascade: ['persist', 'remove'],
        orphanRemoval: true
    )]
    #[ORM\OrderBy(['id' => 'ASC'])]
    private Collection $components;

    public function getId(): string
    {
        return $this->id;
    }

    public function setId(string $id): self
    {
        $this->id = $id;

        return $this;
    }

    public function getComponents(): array
    {
        return $this->components->toArray();
    }

    public function addComponent(Component $component): self
    {
        if (!$this->components->contains($component)) {
            $this->components->add($component);
            $component->setPage($this);
        }

        return $this;
    }

    public function setComponents(array $components): self
    {
        $this->components = new ArrayCollection();

        foreach ($components as $component) {
            $this->addComponent($component);
        }

        return $this;
    }
}

Reproduction:

$page = new Page();
$page->setId('eab311cf-9854-4872-a2c6-73376b7b9936');
$page->addComponent(new ExampleComponent());

$entityManger->persist($page);
$entityManager->flush();

$entityManager->clear();

$page = $pageRepository->findOneById('eab311cf-9854-4872-a2c6-73376b7b9936');
$page->setComponents([new ExampleComponent()]);

$entityManger->persist($page);
$entityManager->flush();

$entityManager->clear();

$page = $pageRepository->findOneById('eab311cf-9854-4872-a2c6-73376b7b9936');

assertCount(1, $page->getComponents()) //will fail, there are two components in DB, but should be only one

The reason is that the SQL query generated in \Doctrine\ORM\Persisters\Collection\OneToManyPersister::deleteEntityCollection has a null value in the "type" column.

DELETE FROM components WHERE page_id = eab311cf-9854-4872-a2c6-73376b7b9936 AND type = null

2024-07-15_15-00

2024-07-15_15-15

Expected behavior

Old collection should be deleted and only new data should remain. Before v2.19.6 everything works well.

Originally created by @konrad-czernecki-esky on GitHub (Jul 15, 2024). ### Bug Report | Q | A |------------ | ------ | BC Break | no | Version | 2.19.6 #### Summary After this fix https://github.com/doctrine/orm/issues/11500, I have a problem with the SINGLE_TABLE inheritance type, because when I set a new collection (in order to replace the previous one) in my one-to-many relation, the data in the old collection is not deleted. #### Current behavior The collection that is being replaced has not been replaced (old data remains + new appears) #### How to reproduce *Pseudocode ``` enum ComponentTypeEnum: string { case EXAMPLE_COMPONENT = 'example'; case EXAMPLE_COMPONENT2 = 'example_2'; } ``` ``` #[ORM\Entity] #[ORM\Table(name: 'components')] #[ORM\InheritanceType('SINGLE_TABLE')] #[ORM\DiscriminatorColumn(name: 'type', type: 'string')] #[ORM\DiscriminatorMap(Component::ENTITY_MAP)] abstract class Component extends AbstractComponent { public const ENTITY_MAP = [ ComponentTypeEnum::EXAMPLE_COMPONENT->value => ExampleComponent::class, ComponentTypeEnum::EXAMPLE_COMPONENT2->value => ExampleComponent2::class, //and so on ]; #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] private ?int $id = null; #[ORM\ManyToOne(targetEntity: Page::class, inversedBy: 'components')] #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')] public ?Page $page = null; /** * @var mixed[] */ #[ORM\Column(type: 'json', nullable: false)] public array $data = []; public function getId(): ?int { return $this->id; } } ``` ``` #[Entity] class ExampleComponent extends Component { public ComponentTypeEnum $type = ComponentTypeEnum::ExampleComponent; } ``` ``` #[ORM\Entity] #[ORM\Table(name: 'pages')] class Page { #[ORM\Id] #[ORM\Column(type: 'uuid')] private string $id; #[ORM\OneToMany( mappedBy: 'page', targetEntity: Component::class, cascade: ['persist', 'remove'], orphanRemoval: true )] #[ORM\OrderBy(['id' => 'ASC'])] private Collection $components; public function getId(): string { return $this->id; } public function setId(string $id): self { $this->id = $id; return $this; } public function getComponents(): array { return $this->components->toArray(); } public function addComponent(Component $component): self { if (!$this->components->contains($component)) { $this->components->add($component); $component->setPage($this); } return $this; } public function setComponents(array $components): self { $this->components = new ArrayCollection(); foreach ($components as $component) { $this->addComponent($component); } return $this; } } ``` Reproduction: ``` $page = new Page(); $page->setId('eab311cf-9854-4872-a2c6-73376b7b9936'); $page->addComponent(new ExampleComponent()); $entityManger->persist($page); $entityManager->flush(); $entityManager->clear(); $page = $pageRepository->findOneById('eab311cf-9854-4872-a2c6-73376b7b9936'); $page->setComponents([new ExampleComponent()]); $entityManger->persist($page); $entityManager->flush(); $entityManager->clear(); $page = $pageRepository->findOneById('eab311cf-9854-4872-a2c6-73376b7b9936'); assertCount(1, $page->getComponents()) //will fail, there are two components in DB, but should be only one ``` The reason is that the SQL query generated in `\Doctrine\ORM\Persisters\Collection\OneToManyPersister::deleteEntityCollection ` has a null value in the "type" column. `DELETE FROM components WHERE page_id = eab311cf-9854-4872-a2c6-73376b7b9936 AND type = null ` ![2024-07-15_15-00](https://github.com/user-attachments/assets/7e4c3ba8-278b-4e86-836f-604eb5787383) ![2024-07-15_15-15](https://github.com/user-attachments/assets/8fab06b6-5e28-4b14-a5fc-b2a5d5210e86) #### Expected behavior Old collection should be deleted and only new data should remain. Before v2.19.6 everything works well.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#7398