Cannot remove SINGLE_TABLE inheritance entity on postFlush event. #5447

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

Originally created by @matt-halliday on GitHub (Mar 10, 2017).

Not really sure what's going on here. I am cleaning up duplicate entities on postFlush in Symfony 3.2 using Orm v2.5.6. and all is well until I try to remove an entity that uses inheritance. The mapping for the joinTables is null.

An explanation I've posted to stack overflow:

I have a postFlush event subscriber/listener in a Symfony3 project that checks for existing items in the database from a collection, if it finds a match, it reassigns the existing entity to the Document and removes the new one using orphanRemoval=true. The code is working for Tag however when I try to do the same with an entity using inheritance mapping Tool, I get an error:

Warning: Invalid argument supplied for foreach()
------------------------------------------------
in vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php at line 526
foreach ($joinColumns as $joinColumn) {
    $keys[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform);
}

I have a Tool and a Part which share a common table/structure Equipment as a ManyToMany relationship to a Document. They share a common structure and use SINGLE_TABLE inheritance with a discr field.

My Document entity:

/**
 * @ORM\Entity
 */
class Document
{
    /**
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     *
     * @var integer
     */
    private $id;

    /**
     * @ORM\Column(type="string", nullable=false)
     *
     * @var string
     */
    private $title;

    /**
     * @ORM\ManyToMany(targetEntity="Tag", inversedBy="documents", cascade={"persist"}, orphanRemoval=true)
     * @ORM\JoinTable(name="document_tags")
     *
     * @var Collection
     */
    private $tags;

    /**
     * @ORM\ManyToMany(targetEntity="Tool", inversedBy="documents", cascade={"persist"}, orphanRemoval=true)
     * @ORM\JoinTable(name="document_tools")
     *
     * @var Collection
     */
    private $tools;
}

My little Tool entity:

/**
 * @ORM\Entity
 */
class Tool extends Equipment {}

Equipment abstract entity:

/**
 * @ORM\Entity
 * @ORM\Table(name="equipment")
 * @ORM\InheritanceType("SINGLE_TABLE")
 * @ORM\DiscriminatorColumn(name="discr", type="string")
 * @ORM\DiscriminatorMap({"tool" = "Tool", "part" = "Part"})
 */
abstract class Equipment
{
    /**
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     *
     * @var integer
     */
    private $id;

    /**
     * @ORM\Column(type="string", nullable=false)
     *
     * @var string
     */
    private $title;

    /**
     * @ORM\Column(type="string", nullable=true)
     *
     * @var string
     */
    private $partNumber;

    /**
     * @ORM\ManyToMany(targetEntity="KBS\Entity\Document\Document", mappedBy="equipment")
     *
     * @var Collection
     */
    private $documents;
}

Here's the listener code:

class DocumentListener implements EventSubscriber
{
    /**
     * @var array
     */
    private $documents;

    /**
     * @param OnFlushEventArgs $event
     */
    public function onFlush(OnFlushEventArgs $event)
    {
        $this->documents = [];
        $em = $event->getEntityManager();
        $uow = $em->getUnitOfWork();

        foreach ($uow->getScheduledEntityUpdates() as $entity) {
            if ($entity instanceof Document) {
                $this->documents[] = $entity;
            }
        }

        foreach ($uow->getScheduledEntityInsertions() as $entity) {
            if ($entity instanceof Document) {
                $this->documents[] = $entity;
            }
        }
    }

    /**
     * @param PostFlushEventArgs $event
     */
    public function postFlush(PostFlushEventArgs $event)
    {
        $em = $event->getEntityManager();
        if (!empty($this->documents)) {
            foreach ($this->documents as $document) {
                $this->removeTagDuplicates($em, $document);
                $this->removeToolDuplicates($em, $document);
            }

            $this->documents = [];
            $em->flush();
        }
    }

    /**
     * @param EntityManager $em
     * @param Document $document
     */
    private function removeTagDuplicates(EntityManager $em, Document $document): void
    {
        $repository = $em->getRepository(Tag::class);
        foreach ($document->getTags() as $entity) {
            $numExisting = count($repository->findByLower($entity->getTitle()));

            /** @var Tag $firstMatch */
            $firstMatch = $repository->findOneByLower($entity->getTitle());
            if ($numExisting > 1 && $firstMatch->getChecksum() === $entity->getChecksum()) {
                $document->removeTag($entity);
                $document->addTag($firstMatch);
            }
        }
    }

    /**
     * @param EntityManager $em
     * @param Document $document
     */
    private function removeToolDuplicates(EntityManager $em, Document $document): void
    {
        $repository = $em->getRepository(Tool::class);
        foreach ($document->getTools() as $entity) {
            $numExisting = count($repository->findByLower($entity->getTitle()));

            /** @var Tool $firstMatch */
            $firstMatch = $repository->findOneByLower($entity->getTitle());
            if ($numExisting > 1 && $firstMatch->getChecksum() === $entity->getChecksum()) {
                $document->removeTool($entity);
                $document->addTool($firstMatch);
            }
        }
    }
}

Is there a reason why the mapping is being lost when we try to remove the orphaned Tool? When I var_dump($joinColumns) on line 526 in BasicEntityPersister.php it returns null.

Full stack trace:

[1] Symfony\Component\Debug\Exception\ContextErrorException: Warning: Invalid argument supplied for foreach()
at n/a
    in /var/www/html/vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php line 526

at Doctrine\ORM\Persisters\Entity\BasicEntityPersister->deleteJoinTableRecords(array('id' => 4))
    in /var/www/html/vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php line 578

at Doctrine\ORM\Persisters\Entity\BasicEntityPersister->delete(object(Tool))
    in /var/www/html/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php line 1094

at Doctrine\ORM\UnitOfWork->executeDeletions(object(ClassMetadata))
    in /var/www/html/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php line 406

at Doctrine\ORM\UnitOfWork->commit(null)
    in /var/www/html/vendor/doctrine/orm/lib/Doctrine/ORM/EntityManager.php line 356

at Doctrine\ORM\EntityManager->flush()
    in /var/www/html/src/Service/DocumentService.php line 189

at KBS\Service\DocumentService->persistAndFlush(object(Document))
    in /var/www/html/src/Service/DocumentService.php line 73

at KBS\Service\DocumentService->createDocument(object(Document))
    in /var/www/html/src/Controller/DocumentController.php line 92

at KBS\Controller\DocumentController->formAction(object(Request), 3)
    in  line 

at call_user_func_array(array(object(DocumentController), 'formAction'), array(object(Request), '3'))
    in /var/www/html/data/cache/dev/classes.php line 5360

at Symfony\Component\HttpKernel\HttpKernel->handleRaw(object(Request), 1)
    in /var/www/html/data/cache/dev/classes.php line 5315

at Symfony\Component\HttpKernel\HttpKernel->handle(object(Request), 1, true)
    in /var/www/html/vendor/symfony/http-kernel/Kernel.php line 168

at Symfony\Component\HttpKernel\Kernel->handle(object(Request))
    in /var/www/html/web/index.php line 20
Originally created by @matt-halliday on GitHub (Mar 10, 2017). Not really sure what's going on here. I am cleaning up duplicate entities on postFlush in Symfony 3.2 using Orm v2.5.6. and all is well until I try to remove an entity that uses inheritance. The mapping for the joinTables is null. An explanation I've posted to stack overflow: I have a `postFlush` event subscriber/listener in a Symfony3 project that checks for existing items in the database from a collection, if it finds a match, it reassigns the existing entity to the `Document` and removes the new one using `orphanRemoval=true`. The code is working for `Tag` however when I try to do the same with an entity using inheritance mapping `Tool`, I get an error: Warning: Invalid argument supplied for foreach() ------------------------------------------------ in vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php at line 526 foreach ($joinColumns as $joinColumn) { $keys[] = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform); } I have a `Tool` and a `Part` which share a common table/structure `Equipment` as a `ManyToMany` relationship to a `Document`. They share a common structure and use SINGLE_TABLE inheritance with a `discr` field. My `Document` entity: /** * @ORM\Entity */ class Document { /** * @ORM\Column(type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") * * @var integer */ private $id; /** * @ORM\Column(type="string", nullable=false) * * @var string */ private $title; /** * @ORM\ManyToMany(targetEntity="Tag", inversedBy="documents", cascade={"persist"}, orphanRemoval=true) * @ORM\JoinTable(name="document_tags") * * @var Collection */ private $tags; /** * @ORM\ManyToMany(targetEntity="Tool", inversedBy="documents", cascade={"persist"}, orphanRemoval=true) * @ORM\JoinTable(name="document_tools") * * @var Collection */ private $tools; } My little `Tool` entity: /** * @ORM\Entity */ class Tool extends Equipment {} `Equipment` abstract entity: /** * @ORM\Entity * @ORM\Table(name="equipment") * @ORM\InheritanceType("SINGLE_TABLE") * @ORM\DiscriminatorColumn(name="discr", type="string") * @ORM\DiscriminatorMap({"tool" = "Tool", "part" = "Part"}) */ abstract class Equipment { /** * @ORM\Column(type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") * * @var integer */ private $id; /** * @ORM\Column(type="string", nullable=false) * * @var string */ private $title; /** * @ORM\Column(type="string", nullable=true) * * @var string */ private $partNumber; /** * @ORM\ManyToMany(targetEntity="KBS\Entity\Document\Document", mappedBy="equipment") * * @var Collection */ private $documents; } Here's the listener code: class DocumentListener implements EventSubscriber { /** * @var array */ private $documents; /** * @param OnFlushEventArgs $event */ public function onFlush(OnFlushEventArgs $event) { $this->documents = []; $em = $event->getEntityManager(); $uow = $em->getUnitOfWork(); foreach ($uow->getScheduledEntityUpdates() as $entity) { if ($entity instanceof Document) { $this->documents[] = $entity; } } foreach ($uow->getScheduledEntityInsertions() as $entity) { if ($entity instanceof Document) { $this->documents[] = $entity; } } } /** * @param PostFlushEventArgs $event */ public function postFlush(PostFlushEventArgs $event) { $em = $event->getEntityManager(); if (!empty($this->documents)) { foreach ($this->documents as $document) { $this->removeTagDuplicates($em, $document); $this->removeToolDuplicates($em, $document); } $this->documents = []; $em->flush(); } } /** * @param EntityManager $em * @param Document $document */ private function removeTagDuplicates(EntityManager $em, Document $document): void { $repository = $em->getRepository(Tag::class); foreach ($document->getTags() as $entity) { $numExisting = count($repository->findByLower($entity->getTitle())); /** @var Tag $firstMatch */ $firstMatch = $repository->findOneByLower($entity->getTitle()); if ($numExisting > 1 && $firstMatch->getChecksum() === $entity->getChecksum()) { $document->removeTag($entity); $document->addTag($firstMatch); } } } /** * @param EntityManager $em * @param Document $document */ private function removeToolDuplicates(EntityManager $em, Document $document): void { $repository = $em->getRepository(Tool::class); foreach ($document->getTools() as $entity) { $numExisting = count($repository->findByLower($entity->getTitle())); /** @var Tool $firstMatch */ $firstMatch = $repository->findOneByLower($entity->getTitle()); if ($numExisting > 1 && $firstMatch->getChecksum() === $entity->getChecksum()) { $document->removeTool($entity); $document->addTool($firstMatch); } } } } Is there a reason why the mapping is being lost when we try to remove the orphaned `Tool`? When I `var_dump($joinColumns)` on line 526 in `BasicEntityPersister.php` it returns null. Full stack trace: [1] Symfony\Component\Debug\Exception\ContextErrorException: Warning: Invalid argument supplied for foreach() at n/a in /var/www/html/vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php line 526 at Doctrine\ORM\Persisters\Entity\BasicEntityPersister->deleteJoinTableRecords(array('id' => 4)) in /var/www/html/vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php line 578 at Doctrine\ORM\Persisters\Entity\BasicEntityPersister->delete(object(Tool)) in /var/www/html/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php line 1094 at Doctrine\ORM\UnitOfWork->executeDeletions(object(ClassMetadata)) in /var/www/html/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php line 406 at Doctrine\ORM\UnitOfWork->commit(null) in /var/www/html/vendor/doctrine/orm/lib/Doctrine/ORM/EntityManager.php line 356 at Doctrine\ORM\EntityManager->flush() in /var/www/html/src/Service/DocumentService.php line 189 at KBS\Service\DocumentService->persistAndFlush(object(Document)) in /var/www/html/src/Service/DocumentService.php line 73 at KBS\Service\DocumentService->createDocument(object(Document)) in /var/www/html/src/Controller/DocumentController.php line 92 at KBS\Controller\DocumentController->formAction(object(Request), 3) in line at call_user_func_array(array(object(DocumentController), 'formAction'), array(object(Request), '3')) in /var/www/html/data/cache/dev/classes.php line 5360 at Symfony\Component\HttpKernel\HttpKernel->handleRaw(object(Request), 1) in /var/www/html/data/cache/dev/classes.php line 5315 at Symfony\Component\HttpKernel\HttpKernel->handle(object(Request), 1, true) in /var/www/html/vendor/symfony/http-kernel/Kernel.php line 168 at Symfony\Component\HttpKernel\Kernel->handle(object(Request)) in /var/www/html/web/index.php line 20
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#5447