|
| 1 | +# Soft Deleteable Behavior Extension for Doctrine |
| 2 | + |
| 3 | +The **Soft Deleteable** behavior allows you to "soft delete" objects by marking them as deleted with a timestamp instead |
| 4 | +of removing them from the database. |
| 5 | + |
| 6 | +## Index |
| 7 | + |
| 8 | +- [Getting Started](#getting-started) |
| 9 | +- [Configuring Soft Deleteable Objects](#configuring-soft-deleteable-objects) |
| 10 | +- [Using Traits](#using-traits) |
| 11 | +- [Working with Filters](#working-with-filters) |
| 12 | +- [Bulk Delete Support](#bulk-delete-support) |
| 13 | +- [Time-Aware Soft Deletion](#time-aware-soft-deletion) |
| 14 | +- ["Hard Delete" Soft Deleted Records](#hard-delete-soft-deleted-records) |
| 15 | + |
| 16 | +## Getting Started |
| 17 | + |
| 18 | +The soft deleteable behavior can be added to a supported Doctrine object manager by registering its event subscriber |
| 19 | +when creating the manager. |
| 20 | + |
| 21 | +```php |
| 22 | +use Gedmo\SoftDeleteable\SoftDeleteableListener; |
| 23 | + |
| 24 | +$listener = new SoftDeleteableListener(); |
| 25 | + |
| 26 | +// The $om is either an instance of the ORM's entity manager or the MongoDB ODM's document manager |
| 27 | +$om->getEventManager()->addEventSubscriber($listener); |
| 28 | +``` |
| 29 | + |
| 30 | +### Configuring Filters |
| 31 | + |
| 32 | +To automatically filter out soft-deleted records from all queries, you need to register and enable the appropriate filter for your object manager. |
| 33 | + |
| 34 | +#### For Doctrine ORM |
| 35 | + |
| 36 | +```php |
| 37 | +use Doctrine\ORM\Configuration; |
| 38 | +use Gedmo\SoftDeleteable\Filter\SoftDeleteableFilter; |
| 39 | + |
| 40 | +// Register the filter during configuration |
| 41 | +$config = new Configuration(); |
| 42 | +$config->addFilter('soft-deleteable', SoftDeleteableFilter::class); |
| 43 | + |
| 44 | +// Enable the filter (usually in your application bootstrap) |
| 45 | +$em->getFilters()->enable('soft-deleteable'); |
| 46 | +``` |
| 47 | + |
| 48 | +#### For MongoDB ODM |
| 49 | + |
| 50 | +```php |
| 51 | +use Doctrine\ODM\MongoDB\Configuration; |
| 52 | +use Gedmo\SoftDeleteable\Filter\ODM\SoftDeleteableFilter; |
| 53 | + |
| 54 | +// Register the filter during configuration |
| 55 | +$config = new Configuration(); |
| 56 | +$config->addFilter('soft-deleteable', SoftDeleteableFilter::class); |
| 57 | + |
| 58 | +// Enable the filter (usually in your application bootstrap) |
| 59 | +$dm->getFilterCollection()->enable('soft-deleteable'); |
| 60 | +``` |
| 61 | + |
| 62 | +## Configuring Soft Deleteable Objects |
| 63 | + |
| 64 | +The soft deleteable extension can be configured with [annotations](./annotations.md#soft-deleteable-extension), |
| 65 | +[attributes](./attributes.md#soft-deleteable-extension), or XML configuration (matching the mapping of |
| 66 | +your domain models). The full configuration for annotations and attributes can be reviewed in |
| 67 | +the linked documentation. |
| 68 | + |
| 69 | +The below examples show the basic configuration for the extension, marking a class as soft deleteable. |
| 70 | + |
| 71 | +### Attribute Configuration |
| 72 | + |
| 73 | +```php |
| 74 | +<?php |
| 75 | +namespace App\Entity; |
| 76 | + |
| 77 | +use Doctrine\DBAL\Types\Types; |
| 78 | +use Doctrine\ORM\Mapping as ORM; |
| 79 | +use Gedmo\Mapping\Annotation as Gedmo; |
| 80 | + |
| 81 | +#[ORM\Entity] |
| 82 | +#[Gedmo\SoftDeleteable] |
| 83 | +class Article |
| 84 | +{ |
| 85 | + #[ORM\Id] |
| 86 | + #[ORM\GeneratedValue] |
| 87 | + #[ORM\Column(type: Types::INTEGER)] |
| 88 | + public ?int $id = null; |
| 89 | + |
| 90 | + #[ORM\Column(type: Types::STRING)] |
| 91 | + public ?string $title = null; |
| 92 | + |
| 93 | + #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)] |
| 94 | + public ?\DateTimeImmutable $deletedAt = null; |
| 95 | +} |
| 96 | +``` |
| 97 | + |
| 98 | +### XML Configuration |
| 99 | + |
| 100 | +```xml |
| 101 | +<?xml version="1.0" encoding="UTF-8"?> |
| 102 | +<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" |
| 103 | + xmlns:gedmo="http://gediminasm.org/schemas/orm/doctrine-extensions-mapping"> |
| 104 | + |
| 105 | + <entity name="App\Model\Article" table="articles"> |
| 106 | + <id name="id" type="integer" column="id"> |
| 107 | + <generator strategy="AUTO"/> |
| 108 | + </id> |
| 109 | + |
| 110 | + <field name="title" type="string"/> |
| 111 | + <field name="deletedAt" type="datetime_immutable" nullable="true"/> |
| 112 | + |
| 113 | + <gedmo:soft-deleteable field-name="deletedAt"/> |
| 114 | + </entity> |
| 115 | +</doctrine-mapping> |
| 116 | +``` |
| 117 | + |
| 118 | +### Annotation Configuration |
| 119 | + |
| 120 | +> [!NOTE] |
| 121 | +> Support for annotations is deprecated and will be removed in 4.0. |
| 122 | +
|
| 123 | +```php |
| 124 | +<?php |
| 125 | +namespace App\Entity; |
| 126 | + |
| 127 | +use Doctrine\ORM\Mapping as ORM; |
| 128 | +use Gedmo\Mapping\Annotation as Gedmo; |
| 129 | + |
| 130 | +/** |
| 131 | + * @ORM\Entity |
| 132 | + * @Gedmo\SoftDeleteable |
| 133 | + */ |
| 134 | +class Article |
| 135 | +{ |
| 136 | + /** |
| 137 | + * @ORM\Id |
| 138 | + * @ORM\GeneratedValue |
| 139 | + * @ORM\Column(type="integer") |
| 140 | + */ |
| 141 | + public ?int $id = null; |
| 142 | + |
| 143 | + /** |
| 144 | + * @ORM\Column(type="string") |
| 145 | + */ |
| 146 | + public ?string $title = null; |
| 147 | + |
| 148 | + /** |
| 149 | + * @ORM\Column(type="datetime_immutable", nullable=true) |
| 150 | + */ |
| 151 | + public ?\DateTimeImmutable $deletedAt = null; |
| 152 | +} |
| 153 | +``` |
| 154 | + |
| 155 | +### Supported Field Types |
| 156 | + |
| 157 | +The soft deleteable extension supports the following field types for the deletion timestamp field: |
| 158 | + |
| 159 | +- Date (`date` and `date_immutable`) |
| 160 | +- Time (`time` and `time_immutable`) |
| 161 | +- Date/Time (`datetime` and `datetime_immutable`) |
| 162 | +- Date/Time with timezone (`datetimetz` and `datetimetz_immutable`) |
| 163 | +- Timestamp (`timestamp`) |
| 164 | + |
| 165 | +## Using Traits |
| 166 | + |
| 167 | +The soft deleteable extension provides traits which can be used to quickly add a deletion timestamp field, and |
| 168 | +optionally the mapping configuration, to your models. This trait is provided as a convenience for common configurations. |
| 169 | + |
| 170 | +- `Gedmo\SoftDeleteable\Traits\SoftDeleteable` adds a `$deletedAt` property with getter and setter |
| 171 | +- `Gedmo\SoftDeleteable\Traits\SoftDeleteableDocument` adds a `$deletedAt` property with getters and setters |
| 172 | + and mapping annotations and attributes for the MongoDB ODM |
| 173 | +- `Gedmo\SoftDeleteable\Traits\SoftDeleteableEntity` adds a `$deletedAt` property with getters and setters |
| 174 | + and mapping annotations and attributes for the ORM |
| 175 | + |
| 176 | +## Working with Filters |
| 177 | + |
| 178 | +Once you have configured your soft deleteable objects and registered the appropriate filter, you can control the |
| 179 | +visibility of soft-deleted records using the filter system. |
| 180 | + |
| 181 | +### Basic Filter Operations |
| 182 | + |
| 183 | +```php |
| 184 | +// Enable the filter to hide soft-deleted records (recommended for most use cases) |
| 185 | +$em->getFilters()->enable('soft-deleteable'); |
| 186 | + |
| 187 | +// Disable the filter to show all records, including soft-deleted ones |
| 188 | +$em->getFilters()->disable('soft-deleteable'); |
| 189 | + |
| 190 | +// Check if the filter is enabled |
| 191 | +$isEnabled = $em->getFilters()->isEnabled('soft-deleteable'); |
| 192 | +``` |
| 193 | + |
| 194 | +### Per-Object Filter Control |
| 195 | + |
| 196 | +You can enable or disable the filter for specific object types using the enable and disable methods on the filter classes. |
| 197 | +For example, when using the ORM: |
| 198 | + |
| 199 | +```php |
| 200 | +// Get the filter instance |
| 201 | +$filter = $em->getFilters()->enable('soft-deleteable'); |
| 202 | + |
| 203 | +// Disable filtering for a specific entity (show all records, including soft-deleted) |
| 204 | +$filter->disableForEntity(Article::class); |
| 205 | + |
| 206 | +// Re-enable filtering for a specific entity |
| 207 | +$filter->enableForEntity(Article::class); |
| 208 | +``` |
| 209 | + |
| 210 | +For MongoDB ODM users, replace "Entity" with "Document" in the method names (i.e. `enableForDocument` and `disableForDocument`). |
| 211 | + |
| 212 | +## Bulk DELETE Support |
| 213 | + |
| 214 | +> [!NOTE] |
| 215 | +> This feature is only available with the ORM. |
| 216 | +
|
| 217 | +The soft deleteable extension includes a query walker that automatically converts DQL DELETE statements into UPDATE |
| 218 | +statements that set the deletion timestamp, allowing you to perform bulk soft-deletion operations. |
| 219 | + |
| 220 | +### Using the Query Walker |
| 221 | + |
| 222 | +To use DQL DELETE queries with soft deleteable entities, you need to specify the `SoftDeleteableWalker` as a custom output walker: |
| 223 | + |
| 224 | +```php |
| 225 | +use Doctrine\ORM\Query; |
| 226 | +use Gedmo\SoftDeleteable\Query\TreeWalker\SoftDeleteableWalker; |
| 227 | + |
| 228 | +// Create a DQL DELETE query |
| 229 | +$query = $em->createQuery('DELETE FROM App\Entity\Article a WHERE a.category = :category'); |
| 230 | +$query->setParameter('category', $category); |
| 231 | + |
| 232 | +// Set the query walker to convert the DELETE query to UPDATE |
| 233 | +$query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, SoftDeleteableWalker::class); |
| 234 | + |
| 235 | +// Execute the query |
| 236 | +$query->execute(); |
| 237 | +``` |
| 238 | + |
| 239 | +## Time-Aware Soft Deletion |
| 240 | + |
| 241 | +The soft deleteable extension supports "time-aware" deletion, where you can schedule objects for deletion at a future time. |
| 242 | + |
| 243 | +### Enabling Time-Aware Support |
| 244 | + |
| 245 | +```php |
| 246 | +#[ORM\Entity] |
| 247 | +#[Gedmo\SoftDeleteable(timeAware: true)] |
| 248 | +class Article |
| 249 | +{ |
| 250 | + #[ORM\Id] |
| 251 | + #[ORM\GeneratedValue] |
| 252 | + #[ORM\Column(type: Types::INTEGER)] |
| 253 | + public ?int $id = null; |
| 254 | + |
| 255 | + #[ORM\Column(type: Types::STRING)] |
| 256 | + public ?string $title = null; |
| 257 | + |
| 258 | + #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)] |
| 259 | + public ?\DateTimeImmutable $deletedAt = null; |
| 260 | +} |
| 261 | +``` |
| 262 | + |
| 263 | +### Usage Example |
| 264 | + |
| 265 | +```php |
| 266 | +// Schedule an article for deletion in the future |
| 267 | +$article = new Article(); |
| 268 | +$article->setTitle('Scheduled for deletion'); |
| 269 | +$article->setDeletedAt(new \DateTimeImmutable('+1 week')); // Delete in 1 week |
| 270 | +$em->persist($article); |
| 271 | +$em->flush(); |
| 272 | + |
| 273 | +// The article will be visible now (deletion time hasn't passed) |
| 274 | +$found = $em->getRepository(Article::class)->findOneBy(['title' => 'Scheduled for deletion']); |
| 275 | +assert($found !== null); // Found because deletion time is in the future |
| 276 | + |
| 277 | +// After the scheduled time passes, the article will be automatically filtered out |
| 278 | +// (without needing to run any cleanup processes) |
| 279 | +``` |
| 280 | + |
| 281 | +## "Hard Delete" Soft Deleted Records |
| 282 | + |
| 283 | +By default, the soft deleteable extension allows soft deleted records to be "hard deleted" (fully removed from the database) |
| 284 | +by deleting them a second time. However, by setting the `hardDelete` parameter in the configuration to `false`, you can |
| 285 | +prevent soft deleted records from being deleted at all. |
0 commit comments