Skip to content

Commit a9e980b

Browse files
phansysfranmomu
authored andcommittedJul 20, 2021
Add foreign key constraint for the relation between the entity audit tables and the revisions index
1 parent ac9b172 commit a9e980b

File tree

4 files changed

+97
-10
lines changed

4 files changed

+97
-10
lines changed
 

‎UPGRADE.md

+11
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,13 @@
11
UPGRADE 1.x
22
===========
3+
4+
UPGRADE FROM 1.x to 1.x
5+
=======================
6+
7+
### `SimpleThings\EntityAudit\EventListener\CreateSchemaListener`
8+
9+
"postGenerateSchema" event is not listen anymore. The table responsible for storing
10+
the revisions index is created on the "postGenerateSchemaTable" event.
11+
A foreign key constraint was added for the relation between the revisions index and
12+
the audit tables, disallowing to delete the records in the index if their referenced
13+
values exist in the audit tables.

‎src/AuditReader.php

+9-9
Original file line numberDiff line numberDiff line change
@@ -580,26 +580,26 @@ public function getCurrentRevision($className, $id)
580580
throw new NotAuditedException($className);
581581
}
582582

583-
/** @var ClassMetadataInfo|ClassMetadata $class */
584-
$class = $this->em->getClassMetadata($className);
585-
$tableName = $this->config->getTableName($class);
583+
/** @var ClassMetadataInfo|ClassMetadata $classMetadata */
584+
$classMetadata = $this->em->getClassMetadata($className);
585+
$tableName = $this->config->getTableName($classMetadata);
586586

587587
if (!\is_array($id)) {
588-
$id = [$class->identifier[0] => $id];
588+
$id = [$classMetadata->identifier[0] => $id];
589589
}
590590

591591
$whereSQL = '';
592-
foreach ($class->identifier as $idField) {
593-
if (isset($class->fieldMappings[$idField])) {
592+
foreach ($classMetadata->identifier as $idField) {
593+
if (isset($classMetadata->fieldMappings[$idField])) {
594594
if ($whereSQL) {
595595
$whereSQL .= ' AND ';
596596
}
597-
$whereSQL .= 'e.'.$class->fieldMappings[$idField]['columnName'].' = ?';
598-
} elseif (isset($class->associationMappings[$idField])) {
597+
$whereSQL .= 'e.'.$classMetadata->fieldMappings[$idField]['columnName'].' = ?';
598+
} elseif (isset($classMetadata->associationMappings[$idField])) {
599599
if ($whereSQL) {
600600
$whereSQL .= ' AND ';
601601
}
602-
$whereSQL .= 'e.'.$class->associationMappings[$idField]['joinColumns'][0].' = ?';
602+
$whereSQL .= 'e.'.$classMetadata->associationMappings[$idField]['joinColumns'][0].' = ?';
603603
}
604604
}
605605

‎src/EventListener/CreateSchemaListener.php

+31-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
namespace SimpleThings\EntityAudit\EventListener;
1515

1616
use Doctrine\Common\EventSubscriber;
17+
use Doctrine\DBAL\Schema\Schema;
18+
use Doctrine\DBAL\Schema\Table;
1719
use Doctrine\DBAL\Types\Types;
1820
use Doctrine\ORM\Mapping\ClassMetadataInfo;
1921
use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs;
@@ -45,7 +47,6 @@ public function getSubscribedEvents()
4547
{
4648
return [
4749
ToolEvents::postGenerateSchemaTable,
48-
ToolEvents::postGenerateSchema,
4950
];
5051
}
5152

@@ -70,6 +71,9 @@ public function postGenerateSchemaTable(GenerateSchemaTableEventArgs $eventArgs)
7071
}
7172

7273
$schema = $eventArgs->getSchema();
74+
75+
$revisionsTable = $this->createRevisionsTable($schema);
76+
7377
$entityTable = $eventArgs->getClassTable();
7478
$revisionTable = $schema->createTable(
7579
$this->config->getTablePrefix().$entityTable->getName().$this->config->getTableSuffix()
@@ -92,8 +96,15 @@ public function postGenerateSchemaTable(GenerateSchemaTableEventArgs $eventArgs)
9296
$revisionTable->setPrimaryKey($pkColumns);
9397
$revIndexName = $this->config->getRevisionFieldName().'_'.md5($revisionTable->getName()).'_idx';
9498
$revisionTable->addIndex([$this->config->getRevisionFieldName()], $revIndexName);
99+
$revisionForeignKeyName = $this->config->getRevisionFieldName().'_'.md5($revisionTable->getName()).'_fk';
100+
$revisionTable->addForeignKeyConstraint($revisionsTable, [$this->config->getRevisionFieldName()], $revisionsTable->getPrimaryKeyColumns(), [], $revisionForeignKeyName);
95101
}
96102

103+
/**
104+
* NEXT_MAJOR: Remove this method.
105+
*
106+
* @deprecated since sonata-project/entity-audit-bundle 1.x, will be removed in 2.0.
107+
*/
97108
public function postGenerateSchema(GenerateSchemaEventArgs $eventArgs): void
98109
{
99110
$schema = $eventArgs->getSchema();
@@ -105,4 +116,23 @@ public function postGenerateSchema(GenerateSchemaEventArgs $eventArgs): void
105116
$revisionsTable->addColumn('username', Types::STRING)->setNotnull(false);
106117
$revisionsTable->setPrimaryKey(['id']);
107118
}
119+
120+
private function createRevisionsTable(Schema $schema): Table
121+
{
122+
$revisionsTableName = $this->config->getRevisionTableName();
123+
124+
if ($schema->hasTable($revisionsTableName)) {
125+
return $schema->getTable($revisionsTableName);
126+
}
127+
128+
$revisionsTable = $schema->createTable($revisionsTableName);
129+
$revisionsTable->addColumn('id', $this->config->getRevisionIdFieldType(), [
130+
'autoincrement' => true,
131+
]);
132+
$revisionsTable->addColumn('timestamp', Types::DATETIME_MUTABLE);
133+
$revisionsTable->addColumn('username', Types::STRING)->setNotnull(false);
134+
$revisionsTable->setPrimaryKey(['id']);
135+
136+
return $revisionsTable;
137+
}
108138
}

‎tests/CoreTest.php

+46
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
namespace SimpleThings\EntityAudit\Tests;
1515

16+
use Doctrine\DBAL\Exception\DriverException;
1617
use SimpleThings\EntityAudit\ChangedEntity;
1718
use SimpleThings\EntityAudit\Exception\NoRevisionFoundException;
1819
use SimpleThings\EntityAudit\Exception\NotAuditedException;
@@ -412,4 +413,49 @@ public function testUsernameResolvingIsDynamic(): void
412413

413414
$this->assertNotSame($revisions[0]->getUsername(), $revisions[1]->getUsername());
414415
}
416+
417+
public function testRevisionForeignKeys(): void
418+
{
419+
$isSqlitePlatform = 'sqlite' === $this->em->getConnection()->getDatabasePlatform()->getName();
420+
$updateForeignKeysConfig = false;
421+
422+
if ($isSqlitePlatform) {
423+
$updateForeignKeysConfig = '0' === $this->em->getConnection()->executeQuery('PRAGMA foreign_keys;')->fetchOne();
424+
425+
if ($updateForeignKeysConfig) {
426+
// Enable the "foreign_keys" pragma.
427+
$this->em->getConnection()->executeQuery('PRAGMA foreign_keys = ON;');
428+
}
429+
}
430+
431+
$user = new UserAudit('phansys');
432+
433+
$this->em->persist($user);
434+
$this->em->flush();
435+
436+
$reader = $this->auditManager->createAuditReader($this->em);
437+
438+
$revisions = $reader->findRevisions(\get_class($user), $user->getId());
439+
440+
$this->assertCount(1, $revisions);
441+
442+
$revision = $reader->getCurrentRevision(\get_class($user), $user->getId());
443+
$this->assertSame('1', $revision);
444+
445+
$revisionsTableName = $this->auditManager->getConfiguration()->getRevisionTableName();
446+
447+
$this->expectException(DriverException::class);
448+
$this->expectExceptionMessage('SQLSTATE[23000]: Integrity constraint violation: 19 FOREIGN KEY constraint failed');
449+
450+
try {
451+
$this->em->getConnection()->delete($revisionsTableName, ['id' => $revision]);
452+
} catch (DriverException $e) {
453+
throw $e;
454+
} finally {
455+
if ($updateForeignKeysConfig) {
456+
// Restore the original value for the "foreign_keys" pragma.
457+
$this->em->getConnection()->executeQuery('PRAGMA foreign_keys = OFF;');
458+
}
459+
}
460+
}
415461
}

0 commit comments

Comments
 (0)
Please sign in to comment.