From 6439c016deba40a9d64d02522ab2097faf69f847 Mon Sep 17 00:00:00 2001 From: Maciej Owczarek Date: Thu, 28 Mar 2024 17:38:04 +0100 Subject: [PATCH] Added configuration option to disable auditing for object associations --- README.md | 8 +- src/AuditConfiguration.php | 12 ++ src/AuditReader.php | 106 +++++++++--------- src/DependencyInjection/Configuration.php | 1 + .../SimpleThingsEntityAuditExtension.php | 1 + src/EventListener/CreateSchemaListener.php | 12 +- src/EventListener/LogRevisionsListener.php | 8 +- src/Resources/config/auditable.php | 5 +- .../SimpleThingsEntityAuditExtensionTest.php | 4 + 9 files changed, 99 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index de2839b4..f35c8965 100755 --- a/README.md +++ b/README.md @@ -116,6 +116,12 @@ simple_things_entity_audit: disable_foreign_keys: true ``` +If you want to disable auditing for object relations, you can use the `disable_associations` parameter: +```yaml +simple_things_entity_audit: + disable_associations: true +``` + ### Creating new tables Call the command below to see the new tables in the update schema queue. @@ -290,5 +296,5 @@ This provides you with a few different routes: ## TODOS * Currently only works with auto-increment databases -* Proper metadata mapping is necessary, allow to disable versioning for fields and associations. +* Proper metadata mapping is necessary, allow to disable versioning for fields. * It does NOT work with Joined-Table-Inheritance (Single Table Inheritance should work, but not tested) diff --git a/src/AuditConfiguration.php b/src/AuditConfiguration.php index 971082a4..43b430f6 100644 --- a/src/AuditConfiguration.php +++ b/src/AuditConfiguration.php @@ -28,6 +28,8 @@ class AuditConfiguration private bool $disableForeignKeys = false; + private bool $disableAssociations = false; + /** * @var string[] */ @@ -102,6 +104,16 @@ public function setDisabledForeignKeys(bool $disabled): void $this->disableForeignKeys = $disabled; } + public function areAssociationsDisabled(): bool + { + return $this->disableAssociations; + } + + public function setDisableAssociations(bool $disabled): void + { + $this->disableAssociations = $disabled; + } + /** * @return string * diff --git a/src/AuditReader.php b/src/AuditReader.php index 97466b84..d11dfdaf 100644 --- a/src/AuditReader.php +++ b/src/AuditReader.php @@ -214,7 +214,7 @@ public function find($className, $id, $revision, array $options = []) $columnName = $idKeys[0]; } elseif (isset($classMetadata->fieldMappings[$idField])) { $columnName = $classMetadata->fieldMappings[$idField]['columnName']; - } elseif (isset($classMetadata->associationMappings[$idField]['joinColumns'])) { + } elseif (!$this->config->areAssociationsDisabled() && isset($classMetadata->associationMappings[$idField]['joinColumns'])) { $columnName = $classMetadata->associationMappings[$idField]['joinColumns'][0]['name']; } else { throw new \RuntimeException('column name not found for'.$idField); @@ -249,23 +249,25 @@ public function find($className, $id, $revision, array $options = []) $columnMap[$field] = $this->getSQLResultCasing($this->platform, $columnName); } - foreach ($classMetadata->associationMappings as $assoc) { - if ( - ($assoc['type'] & ClassMetadata::TO_ONE) === 0 - || false === $assoc['isOwningSide'] - || !isset($assoc['joinColumnFieldNames']) - ) { - continue; - } + if (!$this->getConfiguration()->areAssociationsDisabled()) { + foreach ($classMetadata->associationMappings as $assoc) { + if ( + ($assoc['type'] & ClassMetadata::TO_ONE) === 0 + || false === $assoc['isOwningSide'] + || !isset($assoc['joinColumnFieldNames']) + ) { + continue; + } - foreach ($assoc['joinColumnFieldNames'] as $sourceCol) { - $tableAlias = $classMetadata->isInheritanceTypeJoined() + foreach ($assoc['joinColumnFieldNames'] as $sourceCol) { + $tableAlias = $classMetadata->isInheritanceTypeJoined() && $classMetadata->isInheritedAssociation($assoc['fieldName']) && !$classMetadata->isIdentifier($assoc['fieldName']) ? 're' // root entity : 'e'; - $columnList[] = $tableAlias.'.'.$sourceCol; - $columnMap[$sourceCol] = $this->getSQLResultCasing($this->platform, $sourceCol); + $columnList[] = $tableAlias.'.'.$sourceCol; + $columnMap[$sourceCol] = $this->getSQLResultCasing($this->platform, $sourceCol); + } } } @@ -423,15 +425,17 @@ public function findEntitiesChangedAtRevision($revision) $columnMap[$field] = $this->getSQLResultCasing($this->platform, $columnName); } - foreach ($classMetadata->associationMappings as $assoc) { - if ( - ($assoc['type'] & ClassMetadata::TO_ONE) > 0 - && true === $assoc['isOwningSide'] - && isset($assoc['targetToSourceKeyColumns']) - ) { - foreach ($assoc['targetToSourceKeyColumns'] as $sourceCol) { - $columnList .= ', '.$sourceCol; - $columnMap[$sourceCol] = $this->getSQLResultCasing($this->platform, $sourceCol); + if (!$this->config->areAssociationsDisabled()) { + foreach ($classMetadata->associationMappings as $assoc) { + if ( + ($assoc['type'] & ClassMetadata::TO_ONE) > 0 + && true === $assoc['isOwningSide'] + && isset($assoc['targetToSourceKeyColumns']) + ) { + foreach ($assoc['targetToSourceKeyColumns'] as $sourceCol) { + $columnList .= ', '.$sourceCol; + $columnMap[$sourceCol] = $this->getSQLResultCasing($this->platform, $sourceCol); + } } } } @@ -544,7 +548,7 @@ public function findRevisions($className, $id) $whereSQL .= ' AND '; } $whereSQL .= 'e.'.$classMetadata->fieldMappings[$idField]['columnName'].' = ?'; - } elseif (isset($classMetadata->associationMappings[$idField]['joinColumns'])) { + } elseif (!$this->config->areAssociationsDisabled() && isset($classMetadata->associationMappings[$idField]['joinColumns'])) { if ('' !== $whereSQL) { $whereSQL .= ' AND '; } @@ -606,7 +610,7 @@ public function getCurrentRevision($className, $id) $whereSQL .= ' AND '; } $whereSQL .= 'e.'.$classMetadata->fieldMappings[$idField]['columnName'].' = ?'; - } elseif (isset($classMetadata->associationMappings[$idField]['joinColumns'])) { + } elseif (!$this->config->areAssociationsDisabled() && isset($classMetadata->associationMappings[$idField]['joinColumns'])) { if ('' !== $whereSQL) { $whereSQL .= ' AND '; } @@ -716,14 +720,11 @@ public function getEntityHistory($className, $id) $id = [$classMetadata->identifier[0] => $id]; } - /** @phpstan-var array $whereId */ $whereId = []; foreach ($classMetadata->identifier as $idField) { if (isset($classMetadata->fieldMappings[$idField])) { - /** @phpstan-var literal-string $columnName */ $columnName = $classMetadata->fieldMappings[$idField]['columnName']; - } elseif (isset($classMetadata->associationMappings[$idField]['joinColumns'])) { - /** @phpstan-var literal-string $columnName */ + } elseif (!$this->config->areAssociationsDisabled() && isset($classMetadata->associationMappings[$idField]['joinColumns'])) { $columnName = $classMetadata->associationMappings[$idField]['joinColumns'][0]['name']; } else { continue; @@ -738,40 +739,39 @@ public function getEntityHistory($className, $id) foreach ($classMetadata->fieldNames as $columnName => $field) { $type = Type::getType($classMetadata->fieldMappings[$field]['type']); - /** @phpstan-var literal-string $sqlExpr */ - $sqlExpr = $type->convertToPHPValueSQL( + $columnList[] = $type->convertToPHPValueSQL( $this->quoteStrategy->getColumnName($field, $classMetadata, $this->platform), $this->platform - ); - /** @phpstan-var literal-string $quotedField */ - $quotedField = $this->platform->quoteSingleIdentifier($field); - $columnList[] = $sqlExpr.' AS '.$quotedField; + ).' AS '.$this->platform->quoteSingleIdentifier($field); $columnMap[$field] = $this->getSQLResultCasing($this->platform, $columnName); } - foreach ($classMetadata->associationMappings as $assoc) { - if ( - ($assoc['type'] & ClassMetadata::TO_ONE) === 0 - || false === $assoc['isOwningSide'] - || !isset($assoc['targetToSourceKeyColumns']) - ) { - continue; - } + if (!$this->config->areAssociationsDisabled()) { + foreach ($classMetadata->associationMappings as $assoc) { + if ( + ($assoc['type'] & ClassMetadata::TO_ONE) === 0 + || false === $assoc['isOwningSide'] + || !isset($assoc['targetToSourceKeyColumns']) + ) { + continue; + } - /** @phpstan-var literal-string $sourceCol */ - foreach ($assoc['targetToSourceKeyColumns'] as $sourceCol) { - $columnList[] = $sourceCol; - $columnMap[$sourceCol] = $this->getSQLResultCasing($this->platform, $sourceCol); + foreach ($assoc['targetToSourceKeyColumns'] as $sourceCol) { + $columnList[] = $sourceCol; + $columnMap[$sourceCol] = $this->getSQLResultCasing($this->platform, $sourceCol); + } } } $values = array_values($id); - $query = - 'SELECT '.implode(', ', $columnList) - .' FROM '.$tableName.' e' - .' WHERE '.$whereSQL - .' ORDER BY e.'.$this->config->getRevisionFieldName().' DESC'; + $query = sprintf( + 'SELECT %s FROM %s e WHERE %s ORDER BY e.%s DESC', + implode(', ', $columnList), + $tableName, + $whereSQL, + $this->config->getRevisionFieldName() + ); $stmt = $this->em->getConnection()->executeQuery($query, $values); @@ -893,6 +893,10 @@ private function createEntity($className, array $columnMap, array $data, $revisi } } + if ($this->config->areAssociationsDisabled()) { + return $entity; + } + foreach ($classMetadata->associationMappings as $field => $assoc) { /** @phpstan-var class-string $targetEntity */ $targetEntity = $assoc['targetEntity']; diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 6bf2dc68..dda51278 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -53,6 +53,7 @@ public function getConfigTreeBuilder(): TreeBuilder ->scalarNode('revision_type_field_name')->defaultValue('revtype')->end() ->scalarNode('revision_table_name')->defaultValue('revisions')->end() ->scalarNode('disable_foreign_keys')->defaultValue(false)->end() + ->scalarNode('disable_associations')->defaultValue(false)->end() ->scalarNode('revision_id_field_type') ->defaultValue(Types::INTEGER) // NEXT_MAJOR: Use enumNode() instead. diff --git a/src/DependencyInjection/SimpleThingsEntityAuditExtension.php b/src/DependencyInjection/SimpleThingsEntityAuditExtension.php index 9fecb9ee..2b1a6973 100644 --- a/src/DependencyInjection/SimpleThingsEntityAuditExtension.php +++ b/src/DependencyInjection/SimpleThingsEntityAuditExtension.php @@ -40,6 +40,7 @@ public function load(array $configs, ContainerBuilder $container): void 'revision_id_field_type', 'global_ignore_columns', 'disable_foreign_keys', + 'disable_associations', ]; foreach ($configurables as $key) { diff --git a/src/EventListener/CreateSchemaListener.php b/src/EventListener/CreateSchemaListener.php index fbe5ea0d..ed89d4ec 100644 --- a/src/EventListener/CreateSchemaListener.php +++ b/src/EventListener/CreateSchemaListener.php @@ -107,6 +107,14 @@ public function postGenerateSchemaTable(GenerateSchemaTableEventArgs $eventArgs) $revIndexName = $this->config->getRevisionFieldName().'_'.md5($revisionTable->getName()).'_idx'; $revisionTable->addIndex([$this->config->getRevisionFieldName()], $revIndexName); + if (!$this->config->areForeignKeysDisabled()) { + $this->createForeignKeys($revisionTable, $revisionsTable); + } + + if ($this->config->areAssociationsDisabled()) { + return; + } + foreach ($cm->associationMappings as $associationMapping) { if ($associationMapping['isOwningSide'] && isset($associationMapping['joinTable'])) { if (isset($associationMapping['joinTable']['name'])) { @@ -118,10 +126,6 @@ public function postGenerateSchemaTable(GenerateSchemaTableEventArgs $eventArgs) } } } - - if (!$this->config->areForeignKeysDisabled()) { - $this->createForeignKeys($revisionTable, $revisionsTable); - } } public function postGenerateSchema(GenerateSchemaEventArgs $eventArgs): void diff --git a/src/EventListener/LogRevisionsListener.php b/src/EventListener/LogRevisionsListener.php index 84ef9c71..ba7c4208 100644 --- a/src/EventListener/LogRevisionsListener.php +++ b/src/EventListener/LogRevisionsListener.php @@ -198,6 +198,10 @@ public function postFlush(PostFlushEventArgs $eventArgs): void $em->getConnection()->executeQuery($sql, $params, $types); } + if ($this->config->areAssociationsDisabled()) { + return; + } + foreach ($this->deferredChangedManyToManyEntityRevisionsToPersist as $deferredChangedManyToManyEntityRevisionToPersist) { $this->recordRevisionForManyToManyEntity( $deferredChangedManyToManyEntityRevisionToPersist->getEntity(), @@ -568,7 +572,7 @@ private function saveRevisionEntityData(EntityManagerInterface $em, ClassMetadat $types[] = $targetClass->getTypeOfField($targetClass->getFieldForColumn($targetColumn)); } } - } elseif (($assoc['type'] & ClassMetadata::MANY_TO_MANY) > 0 + } elseif (!$this->config->areAssociationsDisabled() && ($assoc['type'] & ClassMetadata::MANY_TO_MANY) > 0 && isset($assoc['relationToSourceKeyColumns'], $assoc['relationToTargetKeyColumns'])) { $targetClass = $em->getClassMetadata($assoc['targetEntity']); @@ -646,6 +650,8 @@ private function saveRevisionEntityData(EntityManagerInterface $em, ClassMetadat * @param array $entityData * @param ClassMetadata $class * @param ClassMetadata $targetClass + * + * @throws Exception */ private function recordRevisionForManyToManyEntity(object $relatedEntity, EntityManagerInterface $em, string $revType, array $entityData, array $assoc, ClassMetadata $class, ClassMetadata $targetClass): void { diff --git a/src/Resources/config/auditable.php b/src/Resources/config/auditable.php index 00b8b92b..ccdeba4e 100644 --- a/src/Resources/config/auditable.php +++ b/src/Resources/config/auditable.php @@ -49,7 +49,9 @@ ->set('simplethings.entityaudit.revision_id_field_type', null) - ->set('simplethings.entityaudit.disable_foreign_keys', null); + ->set('simplethings.entityaudit.disable_foreign_keys', null) + + ->set('simplethings.entityaudit.disable_associations', false); $containerConfigurator->services() ->set('simplethings_entityaudit.manager', AuditManager::class) @@ -129,6 +131,7 @@ ->call('setRevisionIdFieldType', [param('simplethings.entityaudit.revision_id_field_type')]) ->call('setRevisionFieldName', [param('simplethings.entityaudit.revision_field_name')]) ->call('setRevisionTypeFieldName', [param('simplethings.entityaudit.revision_type_field_name')]) + ->call('setDisableAssociations', [param('simplethings.entityaudit.disable_associations')]) ->call('setUsernameCallable', [service('simplethings_entityaudit.username_callable')]) ->alias(AuditConfiguration::class, 'simplethings_entityaudit.config'); }; diff --git a/tests/DependencyInjection/SimpleThingsEntityAuditExtensionTest.php b/tests/DependencyInjection/SimpleThingsEntityAuditExtensionTest.php index 12dc2db5..98b19636 100644 --- a/tests/DependencyInjection/SimpleThingsEntityAuditExtensionTest.php +++ b/tests/DependencyInjection/SimpleThingsEntityAuditExtensionTest.php @@ -79,6 +79,7 @@ public function testItRegistersDefaultServices(): void $this->assertContainerBuilderHasServiceDefinitionWithMethodCall('simplethings_entityaudit.config', 'setRevisionFieldName', ['%simplethings.entityaudit.revision_field_name%']); $this->assertContainerBuilderHasServiceDefinitionWithMethodCall('simplethings_entityaudit.config', 'setRevisionTypeFieldName', ['%simplethings.entityaudit.revision_type_field_name%']); $this->assertContainerBuilderHasServiceDefinitionWithMethodCall('simplethings_entityaudit.config', 'setDisabledForeignKeys', ['%simplethings.entityaudit.disable_foreign_keys%']); + $this->assertContainerBuilderHasServiceDefinitionWithMethodCall('simplethings_entityaudit.config', 'setDisableAssociations', ['%simplethings.entityaudit.disable_associations%']); $this->assertContainerBuilderHasServiceDefinitionWithMethodCall('simplethings_entityaudit.config', 'setUsernameCallable', ['simplethings_entityaudit.username_callable']); } @@ -121,6 +122,7 @@ public function testItSetsDefaultParameters(): void $this->assertContainerBuilderHasParameter('simplethings.entityaudit.revision_type_field_name', 'revtype'); $this->assertContainerBuilderHasParameter('simplethings.entityaudit.revision_id_field_type', Types::INTEGER); $this->assertContainerBuilderHasParameter('simplethings.entityaudit.disable_foreign_keys', false); + $this->assertContainerBuilderHasParameter('simplethings.entityaudit.disable_associations', false); } public function testItSetsConfiguredParameters(): void @@ -137,6 +139,7 @@ public function testItSetsConfiguredParameters(): void 'revision_field_name' => 'revision', 'revision_type_field_name' => 'action', 'disable_foreign_keys' => false, + 'disable_associations' => true, ]); $this->assertContainerBuilderHasParameter('simplethings.entityaudit.connection', 'my_custom_connection'); @@ -150,6 +153,7 @@ public function testItSetsConfiguredParameters(): void $this->assertContainerBuilderHasParameter('simplethings.entityaudit.revision_field_name', 'revision'); $this->assertContainerBuilderHasParameter('simplethings.entityaudit.revision_type_field_name', 'action'); $this->assertContainerBuilderHasParameter('simplethings.entityaudit.disable_foreign_keys', false); + $this->assertContainerBuilderHasParameter('simplethings.entityaudit.disable_associations', true); foreach ([Events::onFlush, Events::postPersist, Events::postUpdate, Events::postFlush, Events::onClear] as $event) { $this->assertContainerBuilderHasServiceDefinitionWithTag('simplethings_entityaudit.log_revisions_listener', 'doctrine.event_listener', ['event' => $event, 'connection' => 'my_custom_connection']);