Skip to content

Commit 32abfe6

Browse files
authored
Merge pull request #1861 from babeou/feature/add-harddelete-option
Add an option to enable/disable hard delete
2 parents 8f20800 + 18dc8a2 commit 32abfe6

File tree

8 files changed

+126
-4
lines changed

8 files changed

+126
-4
lines changed

doc/softdeleteable.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Features:
1111
- Can be nested with other behaviors
1212
- Annotation, Yaml and Xml mapping support for extensions
1313
- Support for 'timeAware' option: When creating an entity set a date of deletion in the future and never worry about cleaning up at expiration time.
14+
- Support for 'hardDelete' option: When deleting a second time it allows to disable hard delete.
1415

1516
Content:
1617

@@ -80,6 +81,8 @@ Available configuration options:
8081
- **fieldName** - The name of the field that will be used to determine if the object is removed or not (NULL means
8182
it's not removed. A date value means it was removed). NOTE: The field MUST be nullable.
8283

84+
- **hardDelete** - A boolean to enable or disable hard delete after soft delete has already been done. NOTE: Set to true by default.
85+
8386
**Note:** that SoftDeleteable interface is not necessary, except in cases where
8487
you need to identify entity as being SoftDeleteable. The metadata is loaded only once then
8588
cache is activated.
@@ -93,7 +96,7 @@ use Doctrine\ORM\Mapping as ORM;
9396

9497
/**
9598
* @ORM\Entity
96-
* @Gedmo\SoftDeleteable(fieldName="deletedAt", timeAware=false)
99+
* @Gedmo\SoftDeleteable(fieldName="deletedAt", timeAware=false, hardDelete=true)
97100
*/
98101
class Article
99102
{
@@ -156,6 +159,7 @@ Entity\Article:
156159
soft_deleteable:
157160
field_name: deletedAt
158161
time_aware: false
162+
hard_delete: true
159163
id:
160164
id:
161165
type: integer
@@ -187,7 +191,7 @@ Entity\Article:
187191

188192
<field name="deletedAt" type="datetime" nullable="true" />
189193

190-
<gedmo:soft-deleteable field-name="deletedAt" time-aware="false" />
194+
<gedmo:soft-deleteable field-name="deletedAt" time-aware="false" hard-delete="true" />
191195
</entity>
192196

193197
</doctrine-mapping>
@@ -254,7 +258,7 @@ use Gedmo\SoftDeleteable\Traits\SoftDeleteableEntity;
254258

255259
/**
256260
* @ORM\Entity
257-
* @Gedmo\SoftDeleteable(fieldName="deletedAt", timeAware=false)
261+
* @Gedmo\SoftDeleteable(fieldName="deletedAt", timeAware=false, hardDelete=true)
258262
*/
259263
class UsingTrait
260264
{

lib/Gedmo/Mapping/Annotation/SoftDeleteable.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,7 @@ final class SoftDeleteable extends Annotation
2020

2121
/** @var bool */
2222
public $timeAware = false;
23+
24+
/** @var bool */
25+
public $hardDelete = true;
2326
}

lib/Gedmo/SoftDeleteable/Mapping/Driver/Annotation.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ public function readExtendedMetadata($meta, array &$config)
4444
}
4545
$config['timeAware'] = $annot->timeAware;
4646
}
47+
48+
$config['hardDelete'] = false;
49+
if (isset($annot->hardDelete)) {
50+
if (!is_bool($annot->hardDelete)) {
51+
throw new InvalidMappingException("hardDelete must be boolean. ".gettype($annot->hardDelete)." provided.");
52+
}
53+
$config['hardDelete'] = $annot->hardDelete;
54+
}
4755
}
4856

4957
$this->validateFullMetadata($meta, $config);

lib/Gedmo/SoftDeleteable/Mapping/Driver/Xml.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ public function readExtendedMetadata($meta, array &$config)
4848
if ($this->_isAttributeSet($xml->{'soft-deleteable'}, 'time-aware')) {
4949
$config['timeAware'] = $this->_getBooleanAttribute($xml->{'soft-deleteable'}, 'time-aware');
5050
}
51+
52+
$config['hardDelete'] = false;
53+
if ($this->_isAttributeSet($xml->{'soft-deleteable'}, 'hard-delete')) {
54+
$config['hardDelete'] = $this->_getBooleanAttribute($xml->{'soft-deleteable'}, 'hard-delete');
55+
}
5156
}
5257
}
5358
}

lib/Gedmo/SoftDeleteable/Mapping/Driver/Yaml.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ public function readExtendedMetadata($meta, array &$config)
5454
}
5555
$config['timeAware'] = $classMapping['soft_deleteable']['time_aware'];
5656
}
57+
58+
$config['hardDelete'] = false;
59+
if (isset($classMapping['soft_deleteable']['hard_delete'])) {
60+
if (!is_bool($classMapping['soft_deleteable']['hard_delete'])) {
61+
throw new InvalidMappingException("hardDelete must be boolean. ".gettype($classMapping['soft_deleteable']['hard_delete'])." provided.");
62+
}
63+
$config['hardDelete'] = $classMapping['soft_deleteable']['hard_delete'];
64+
}
5765
}
5866
}
5967
}

lib/Gedmo/SoftDeleteable/SoftDeleteableListener.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ public function onFlush(EventArgs $args)
6363
if (isset($config['softDeleteable']) && $config['softDeleteable']) {
6464
$reflProp = $meta->getReflectionProperty($config['fieldName']);
6565
$oldValue = $reflProp->getValue($object);
66-
if ($oldValue instanceof \Datetime) {
66+
67+
if (isset($config['hardDelete']) && $config['hardDelete'] && $oldValue instanceof \Datetime) {
6768
continue; // want to hard delete
6869
}
6970

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
namespace SoftDeleteable\Fixture\Entity;
3+
4+
use Gedmo\Mapping\Annotation as Gedmo;
5+
use Doctrine\ORM\Mapping as ORM;
6+
7+
/**
8+
* @ORM\Entity
9+
* @Gedmo\SoftDeleteable(fieldName="deletedAt", hardDelete=false)
10+
*/
11+
class UserNoHardDelete
12+
{
13+
/**
14+
* @ORM\Column(name="id", type="integer")
15+
* @ORM\Id
16+
* @ORM\GeneratedValue(strategy="IDENTITY")
17+
*/
18+
private $id;
19+
20+
/**
21+
* @ORM\Column(name="title", type="string")
22+
*/
23+
private $username;
24+
25+
/**
26+
* @ORM\Column(name="deleted_time", type="datetime", nullable=true)
27+
*/
28+
private $deletedAt;
29+
30+
public function getId()
31+
{
32+
return $this->id;
33+
}
34+
35+
public function setUsername($username)
36+
{
37+
$this->username = $username;
38+
}
39+
40+
public function getUsername()
41+
{
42+
return $this->username;
43+
}
44+
45+
public function setDeletedAt($deletedAt)
46+
{
47+
$this->deletedAt = $deletedAt;
48+
}
49+
50+
public function getDeletedAt()
51+
{
52+
return $this->deletedAt;
53+
}
54+
}

tests/Gedmo/SoftDeleteable/SoftDeleteableEntityTest.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Gedmo\SoftDeleteable;
44

5+
use SoftDeleteable\Fixture\Entity\UserNoHardDelete;
56
use Tool\BaseTestCaseORM;
67
use Doctrine\Common\EventManager;
78
use SoftDeleteable\Fixture\Entity\Article;
@@ -35,6 +36,7 @@ class SoftDeleteableEntityTest extends BaseTestCaseORM
3536
const USER_CLASS = 'SoftDeleteable\Fixture\Entity\User';
3637
const MAPPED_SUPERCLASS_CHILD_CLASS = 'SoftDeleteable\Fixture\Entity\Child';
3738
const SOFT_DELETEABLE_FILTER_NAME = 'soft-deleteable';
39+
const USER_NO_HARD_DELETE_CLASS = 'SoftDeleteable\Fixture\Entity\UserNoHardDelete';
3840

3941
private $softDeleteableListener;
4042

@@ -370,6 +372,42 @@ public function testPostSoftDeleteEventIsDispatched()
370372
$this->em->flush();
371373
}
372374

375+
/**
376+
* @test
377+
*/
378+
public function shouldNotDeleteIfColumnNameDifferFromPropertyName()
379+
{
380+
$repo = $this->em->getRepository(self::USER_NO_HARD_DELETE_CLASS);
381+
382+
$newUser = new UserNoHardDelete();
383+
$username = 'test_user';
384+
$newUser->setUsername($username);
385+
386+
$this->em->persist($newUser);
387+
$this->em->flush();
388+
389+
$user = $repo->findOneBy(array('username' => $username));
390+
391+
$this->assertNull($user->getDeletedAt());
392+
393+
$this->em->remove($user);
394+
$this->em->flush();
395+
396+
$user = $repo->findOneBy(array('username' => $username));
397+
$this->assertNull($user, "User should be filtered out");
398+
399+
// now deactivate filter and attempt to hard delete
400+
$this->em->getFilters()->disable(self::SOFT_DELETEABLE_FILTER_NAME);
401+
$user = $repo->findOneBy(array('username' => $username));
402+
$this->assertNotNull($user, "User should be fetched when filter is disabled");
403+
404+
$this->em->remove($user);
405+
$this->em->flush();
406+
407+
$user = $repo->findOneBy(array('username' => $username));
408+
$this->assertNotNull($user, "User is still available, hard delete done");
409+
}
410+
373411
protected function getUsedEntityFixtures()
374412
{
375413
return array(
@@ -382,6 +420,7 @@ protected function getUsedEntityFixtures()
382420
self::OTHER_ARTICLE_CLASS,
383421
self::OTHER_COMMENT_CLASS,
384422
self::MAPPED_SUPERCLASS_CHILD_CLASS,
423+
self::USER_NO_HARD_DELETE_CLASS,
385424
);
386425
}
387426
}

0 commit comments

Comments
 (0)