phpdoctrine-ormdoctrine

Doctrine returns Entity deleted via onDelete: 'cascade'


I'm struggling with a strange issue using a relation having onDelete: 'cascade'. In a nutshell, I delete the parent object, the cascade works fine in the database, deleting the children too. Then if I read again the objects via entityManager->find(), the parent entity cannot be found, while the children is found by the ORM, even though there is no such object in the DB.

I would really appreciate any ideas, as I have tried so many different things already with no luck... but I guess I'm not finding some obvious error.

This is my code:

<?php
require_once "bootstrap.php";

// create objects
$s1 = new Shelf();
$s1->setCategory("Science fiction");
$entityManager->persist($s1);

$b1 = new Book();
$b1->setAuthor("Isaac Asimov");
$b1->setTitle("End of Eternity");
$b1->setShelf($s1);
$entityManager->persist($b1);
$entityManager->flush();

$shelfId = $s1->getId();
$bookId = $b1->getId();

// delete objects
$entityManager->remove($s1);
$entityManager->flush();
// --> this deletes on the DB both the shelf and the book

// read the objects again
$shelfAfterDel = $entityManager->find(Shelf::class, $shelfId);
$bookAfterDel = $entityManager->find(Book::class, $bookId);

echo "shelf: " . ($shelfAfterDel!=null? $shelfAfterDel->getCategory() : "null") . "\n"; 
// --> this prints 'null' (OK)
echo "book: " . ($bookAfterDel!=null? $bookAfterDel->getTitle() : "null") . "\n"; 
// --> this prints 'End of Eternity' (NOT OK)

Shelf.php

<?php
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;

#[ORM\Entity]
#[ORM\Table(name: 'shelf')]
class Shelf
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column]
    private string $category;

    #[ORM\OneToMany(targetEntity: Book::class, mappedBy: 'shelf')]
    private Collection $books;

    public function __construct()
    {
        $this->books = new ArrayCollection();
    }

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

    public function getCategory(): string
    {
        return $this->category;
    }

    public function setCategory(string $category): void
    {
        $this->category = $category;
    }

    public function addBook(Book $book): void
    {
        $this->books[] = $book;
    }
}

Book.php

<?php
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[ORM\Table(name: 'book')]
class Book
{
    #[ORM\Id]
    #[ORM\Column]
    #[ORM\GeneratedValue]
    private ?int $id = null;

    #[ORM\Column]
    private string $author;

    #[ORM\Column]
    private string $title;

    #[ORM\ManyToOne(targetEntity: Shelf::class, inversedBy: 'books')]
    #[ORM\JoinColumn(onDelete: 'CASCADE', nullable: false)]
    private Shelf $shelf;

    public function setShelf(Shelf $shelf): void
    {
        $this->shelf = $shelf;
    }

    public function getShelf(): Shelf
    {
        return $this->shelf;
    }

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

    public function getAuthor(): string
    {
        return $this->author;
    }

    public function setAuthor(string $author): void
    {
        $this->author = $author;
    }

    public function setTitle($title): void
    {
        $this->title = $title;
    }

    public function getTitle():string
    {
        return $this->title;
    }
}

bootstrap.php

require_once "vendor/autoload.php";

$config = ORMSetup::createAttributeMetadataConfiguration(
    paths: [__DIR__ . '/src'],
    isDevMode: true,
);
$connection = DriverManager::getConnection([
    'driver'   => 'pdo_mysql',
    'host'     => '127.0.0.1',
    'port'     => '3306',
    'user'     => 'root',
    'password' => 'XXXXXX',
    'dbname'   => 'phpunit',
], $config);

$entityManager = new EntityManager($connection, $config);

composer.json

{
    "require": {
        "php": ">=8.0.0",
        "monolog/monolog": "^3.8",
        "doctrine/orm": "^3",
        "doctrine/dbal": "^4",
        "symfony/cache": "^7"
    },
    "autoload": {
        "psr-4": {
          "": "src/"
        }
      },
      "archive": {
        "exclude": [
          "vendor",
          ".DS_Store",
          "*.log"
        ]
      }
}

Solution

  • Generally doctrine isn't aware of what happened at the DB level after flush as entities are managed in memory, Calling $entityManager->clear(); resolves this issue by clearing the identity map, forcing Doctrine to fetch fresh data from the database for subsequent operations. https://stackoverflow.com/a/59970767/16742310