SoftDeletable

SoftDeletable

Soft delete: remove() does not delete the row but stores the deletion time in deletedAt.

Entity

use Doctrine\ORM\Mapping as ORM;
use Ufo\DoctrineBehaviors\Contract\Entity\SoftDeletableInterface;
use Ufo\DoctrineBehaviors\Model\SoftDeletable\SoftDeletableTrait;

#[ORM\Entity]
class Category implements SoftDeletableInterface
{
    use SoftDeletableTrait;

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

The bundle maps deletedAt (datetime, nullable).

How it works

In onFlush every SoftDeletableInterface entity scheduled for deletion gets delete() (the current time in deletedAt) and is persisted again — an UPDATE runs instead of DELETE.

There is no query filter: find(), repositories and DQL return "deleted" entities too. Filter them out yourself — with a deletedAt IS NULL condition or an isDeleted() check.

Doctrine performs cascade removal of related entities as usual: only those that also implement SoftDeletableInterface are soft-deleted.

Usage

$entityManager->remove($category);
$entityManager->flush();

$category = $categoryRepository->find($id); // the entity is still there
$category->isDeleted();                     // true

Restore

$category->restore();          // deletedAt = null
$category->isDeleted();        // false
$entityManager->flush();

Delete at a given time

$category->setDeletedAt(new \DateTime('+1 day'));
$category->isDeleted();        // false — until the date comes
$category->willBeDeleted();    // true
$entityManager->flush();
Method Returns
isDeleted() true if deletedAt is set and has already come
willBeDeleted(?DateTimeInterface $at = null) true if deletedAt is set (and, when $at is passed, not later than it)
getDeletedAt() ?DateTimeInterface