From 06f4715738f38742c77c1bf661b65862e4c3ff03 Mon Sep 17 00:00:00 2001 From: Fran Moreno Date: Sun, 19 Nov 2023 16:16:10 +0100 Subject: [PATCH 01/10] Replace usage of ClassMetadataInfo for ClassMetadata --- src/AuditConfiguration.php | 6 +++--- src/Collection/AuditedCollection.php | 8 ++++---- src/EventListener/CreateSchemaListener.php | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/AuditConfiguration.php b/src/AuditConfiguration.php index 971082a4..b66f677c 100644 --- a/src/AuditConfiguration.php +++ b/src/AuditConfiguration.php @@ -14,7 +14,7 @@ namespace SimpleThings\EntityAudit; use Doctrine\DBAL\Types\Types; -use Doctrine\ORM\Mapping\ClassMetadataInfo; +use Doctrine\ORM\Mapping\ClassMetadata; use SimpleThings\EntityAudit\Metadata\MetadataFactory; class AuditConfiguration @@ -71,7 +71,7 @@ public static function forEntities(array $classes) } /** - * @param ClassMetadataInfo $metadata + * @param ClassMetadata $metadata * * @return string * @@ -79,7 +79,7 @@ public static function forEntities(array $classes) * * @psalm-suppress MoreSpecificReturnType,LessSpecificReturnStatement https://github.com/vimeo/psalm/issues/10910 */ - public function getTableName(ClassMetadataInfo $metadata) + public function getTableName(ClassMetadata $metadata) { /** @var literal-string $tableName */ $tableName = $metadata->getTableName(); diff --git a/src/Collection/AuditedCollection.php b/src/Collection/AuditedCollection.php index fae06870..24258dda 100644 --- a/src/Collection/AuditedCollection.php +++ b/src/Collection/AuditedCollection.php @@ -15,7 +15,7 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; -use Doctrine\ORM\Mapping\ClassMetadataInfo; +use Doctrine\ORM\Mapping\ClassMetadata; use SimpleThings\EntityAudit\AuditConfiguration; use SimpleThings\EntityAudit\AuditReader; use SimpleThings\EntityAudit\Exception\AuditedCollectionException; @@ -64,13 +64,13 @@ class AuditedCollection implements Collection * @param string|int $revision * * @phpstan-param class-string $class - * @phpstan-param ClassMetadataInfo $metadata + * @phpstan-param ClassMetadata $metadata */ public function __construct( protected AuditReader $auditReader, protected $class, - protected ClassMetadataInfo $metadata, - protected array $associationDefinition, + protected ClassMetadata $metadata, + protected $associationDefinition, protected array $foreignKeys, protected $revision ) { diff --git a/src/EventListener/CreateSchemaListener.php b/src/EventListener/CreateSchemaListener.php index fbe5ea0d..42e1ca75 100644 --- a/src/EventListener/CreateSchemaListener.php +++ b/src/EventListener/CreateSchemaListener.php @@ -19,7 +19,7 @@ use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types; -use Doctrine\ORM\Mapping\ClassMetadataInfo; +use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; use Doctrine\ORM\Tools\Event\GenerateSchemaTableEventArgs; use Doctrine\ORM\Tools\ToolEvents; @@ -95,7 +95,7 @@ public function postGenerateSchemaTable(GenerateSchemaTableEventArgs $eventArgs) } $revisionTable->addColumn($this->config->getRevisionFieldName(), $this->config->getRevisionIdFieldType()); $revisionTable->addColumn($this->config->getRevisionTypeFieldName(), Types::STRING, ['length' => 4]); - if (!\in_array($cm->inheritanceType, [ClassMetadataInfo::INHERITANCE_TYPE_NONE, ClassMetadataInfo::INHERITANCE_TYPE_JOINED, ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_TABLE], true)) { + if (!\in_array($cm->inheritanceType, [ClassMetadata::INHERITANCE_TYPE_NONE, ClassMetadata::INHERITANCE_TYPE_JOINED, ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE], true)) { throw new \Exception(sprintf('Inheritance type "%s" is not yet supported', $cm->inheritanceType)); } From 4394ef29873c1fa9a056dc14887afccb54c2619a Mon Sep 17 00:00:00 2001 From: Fran Moreno Date: Sun, 19 Nov 2023 17:16:26 +0100 Subject: [PATCH 02/10] Allow doctrine/orm 3 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 1326f5c5..4a71588c 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "doctrine/collections": "^1.8 || ^2.0", "doctrine/dbal": "^3.4", "doctrine/event-manager": "^1.2 || ^2.0", - "doctrine/orm": "^2.14", + "doctrine/orm": "^2.14 || ^3.0", "doctrine/persistence": "^3.0", "psr/clock": "^1.0", "symfony/config": "^5.4 || ^6.2 || ^7.0", From 158c070e0bbce15f03ec6a7541c622969ba4e341 Mon Sep 17 00:00:00 2001 From: Fran Moreno Date: Sun, 19 Nov 2023 17:18:28 +0100 Subject: [PATCH 03/10] Add compatibility with doctrine/orm 3 --- phpstan-baseline.neon | 6 +++++ psalm-baseline.xml | 9 ++++++- src/Collection/AuditedCollection.php | 3 ++- src/EventListener/LogRevisionsListener.php | 29 +++++++++++++++------- 4 files changed, 36 insertions(+), 11 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 394c5050..652f5ea8 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -3,3 +3,9 @@ parameters: # The class "AuditController" is deprecated and extends a class that is only supported in "symfony/http-foundation" < 5. # NEXT_MAJOR: Remove these files - src/Controller/AuditController.php + ignoreErrors: + # For compatibility with doctrine/orm 2 and 3 + - '#.+ has invalid type Doctrine\\ORM\\Mapping\\AssociationMapping\.$#' + - '#^Property SimpleThings\\EntityAudit\\Collection\\AuditedCollection\:\:\$associationDefinition has unknown class Doctrine\\ORM\\Mapping\\AssociationMapping as its type\.$#' + - '#^Cannot access offset ''relationToSourceKey…'' on array\\|Doctrine\\ORM\\Mapping\\AssociationMapping\.$#' + - '#^Cannot access offset ''relationToTargetKey…'' on array\\|Doctrine\\ORM\\Mapping\\AssociationMapping\.$#' diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 5733a34c..efe38aa5 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,9 +1,16 @@ - + Controller + + + + |AssociationMapping]]> + |AssociationMapping]]> + + diff --git a/src/Collection/AuditedCollection.php b/src/Collection/AuditedCollection.php index 24258dda..55152d56 100644 --- a/src/Collection/AuditedCollection.php +++ b/src/Collection/AuditedCollection.php @@ -15,6 +15,7 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; +use Doctrine\ORM\Mapping\AssociationMapping; use Doctrine\ORM\Mapping\ClassMetadata; use SimpleThings\EntityAudit\AuditConfiguration; use SimpleThings\EntityAudit\AuditReader; @@ -70,7 +71,7 @@ public function __construct( protected AuditReader $auditReader, protected $class, protected ClassMetadata $metadata, - protected $associationDefinition, + protected array|AssociationMapping $associationDefinition, protected array $foreignKeys, protected $revision ) { diff --git a/src/EventListener/LogRevisionsListener.php b/src/EventListener/LogRevisionsListener.php index f0eac433..802c02c1 100644 --- a/src/EventListener/LogRevisionsListener.php +++ b/src/EventListener/LogRevisionsListener.php @@ -24,6 +24,7 @@ use Doctrine\ORM\Event\PostPersistEventArgs; use Doctrine\ORM\Event\PostUpdateEventArgs; use Doctrine\ORM\Events; +use Doctrine\ORM\Mapping\AssociationMapping; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Persisters\Entity\EntityPersister; use Doctrine\ORM\UnitOfWork; @@ -481,14 +482,17 @@ private function getInsertRevisionSQL(EntityManagerInterface $em, ClassMetadata /** * @param ClassMetadata $class * @param ClassMetadata $targetClass - * @param array $assoc + * @param array|AssociationMapping $assoc * * @return literal-string * * @psalm-suppress MoreSpecificReturnType,PropertyTypeCoercion,LessSpecificReturnStatement https://github.com/vimeo/psalm/issues/10909 */ - private function getInsertJoinTableRevisionSQL(ClassMetadata $class, ClassMetadata $targetClass, array $assoc): string - { + private function getInsertJoinTableRevisionSQL( + ClassMetadata $class, + ClassMetadata $targetClass, + array|AssociationMapping $assoc + ): string { $cacheKey = $class->name.'.'.$targetClass->name.'.'.$assoc['joinTable']['name']; if ( @@ -642,13 +646,20 @@ private function saveRevisionEntityData(EntityManagerInterface $em, ClassMetadat } /** - * @param array $assoc - * @param array $entityData - * @param ClassMetadata $class - * @param ClassMetadata $targetClass + * @param array|AssociationMapping $assoc + * @param array $entityData + * @param ClassMetadata $class + * @param ClassMetadata $targetClass */ - private function recordRevisionForManyToManyEntity(object $relatedEntity, EntityManagerInterface $em, string $revType, array $entityData, array $assoc, ClassMetadata $class, ClassMetadata $targetClass): void - { + private function recordRevisionForManyToManyEntity( + object $relatedEntity, + EntityManagerInterface $em, + string $revType, + array $entityData, + array|AssociationMapping $assoc, + ClassMetadata $class, + ClassMetadata $targetClass + ): void { $conn = $em->getConnection(); $joinTableParams = [$this->getRevisionId($conn), $revType]; $joinTableTypes = [\PDO::PARAM_INT, \PDO::PARAM_STR]; From 2a12693d725a04a6992219b75af6f07c85563621 Mon Sep 17 00:00:00 2001 From: Fran Moreno Date: Sun, 16 Jun 2024 21:15:30 +0200 Subject: [PATCH 04/10] Remove some deprecations accesing mapping as array --- .github/workflows/test-orm-3.yaml | 63 ++++++++++++ phpstan-baseline.neon | 2 - src/AuditReader.php | 53 +++++----- src/EventListener/CreateSchemaListener.php | 15 +-- src/EventListener/LogRevisionsListener.php | 107 +++++++++++---------- src/Utils/ORMCompatibilityTrait.php | 95 ++++++++++++++++++ tests/BaseTest.php | 2 + 7 files changed, 250 insertions(+), 87 deletions(-) create mode 100644 .github/workflows/test-orm-3.yaml create mode 100644 src/Utils/ORMCompatibilityTrait.php diff --git a/.github/workflows/test-orm-3.yaml b/.github/workflows/test-orm-3.yaml new file mode 100644 index 00000000..34f36dd1 --- /dev/null +++ b/.github/workflows/test-orm-3.yaml @@ -0,0 +1,63 @@ +# TEMPORARILY USED until AuditBundle is compatible with ORM 3 + +name: Test + +on: + pull_request: + +permissions: + contents: read + +jobs: + test: + name: PHP ${{ matrix.php-version }} + ${{ matrix.dependencies }} + ${{ matrix.variant }} (ORM 3) + + runs-on: ubuntu-latest + + continue-on-error: ${{ matrix.allowed-to-fail }} + + env: + SYMFONY_REQUIRE: ${{matrix.symfony-require}} + + strategy: + matrix: + include: + - php-version: '8.3' + dependencies: highest + allowed-to-fail: false + symfony-require: 7.0.* + variant: symfony/symfony:"7.0.*" + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install PHP with extensions + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + coverage: pcov + tools: composer:v2, flex + + - name: Add PHPUnit matcher + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + + - name: Use gedmo-extension main branch + run: composer require "gedmo/doctrine-extensions":"dev-main" --dev --no-update + + - name: Install variant + if: matrix.variant != 'normal' && !startsWith(matrix.variant, 'symfony/symfony') + run: composer require ${{ matrix.variant }} --no-update + + - name: Install Composer dependencies (${{ matrix.dependencies }}) + uses: ramsey/composer-install@v2 + with: + dependency-versions: ${{ matrix.dependencies }} + + - name: Run Tests with coverage + run: make coverage + + - name: Send coverage to Codecov + uses: codecov/codecov-action@v3 + with: + files: build/logs/clover.xml diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 652f5ea8..21e083c1 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -7,5 +7,3 @@ parameters: # For compatibility with doctrine/orm 2 and 3 - '#.+ has invalid type Doctrine\\ORM\\Mapping\\AssociationMapping\.$#' - '#^Property SimpleThings\\EntityAudit\\Collection\\AuditedCollection\:\:\$associationDefinition has unknown class Doctrine\\ORM\\Mapping\\AssociationMapping as its type\.$#' - - '#^Cannot access offset ''relationToSourceKey…'' on array\\|Doctrine\\ORM\\Mapping\\AssociationMapping\.$#' - - '#^Cannot access offset ''relationToTargetKey…'' on array\\|Doctrine\\ORM\\Mapping\\AssociationMapping\.$#' diff --git a/src/AuditReader.php b/src/AuditReader.php index 97466b84..943c21c4 100644 --- a/src/AuditReader.php +++ b/src/AuditReader.php @@ -31,10 +31,12 @@ use SimpleThings\EntityAudit\Exception\NotAuditedException; use SimpleThings\EntityAudit\Metadata\MetadataFactory; use SimpleThings\EntityAudit\Utils\ArrayDiff; +use SimpleThings\EntityAudit\Utils\ORMCompatibilityTrait; use SimpleThings\EntityAudit\Utils\SQLResultCasing; class AuditReader { + use ORMCompatibilityTrait; use SQLResultCasing; private AbstractPlatform $platform; @@ -213,7 +215,7 @@ public function find($className, $id, $revision, array $options = []) $idKeys = array_keys($id); $columnName = $idKeys[0]; } elseif (isset($classMetadata->fieldMappings[$idField])) { - $columnName = $classMetadata->fieldMappings[$idField]['columnName']; + $columnName = self::getMappingValue($classMetadata->fieldMappings[$idField], 'columnName'); } elseif (isset($classMetadata->associationMappings[$idField]['joinColumns'])) { $columnName = $classMetadata->associationMappings[$idField]['joinColumns'][0]['name']; } else { @@ -237,7 +239,7 @@ public function find($className, $id, $revision, array $options = []) ? 're' // root entity : 'e'; - $type = Type::getType($classMetadata->fieldMappings[$field]['type']); + $type = Type::getType(self::getMappingValue($classMetadata->fieldMappings[$field], 'type')); $columnList[] = sprintf( '%s AS %s', $type->convertToPHPValueSQL( @@ -286,7 +288,7 @@ public function find($className, $id, $revision, array $options = []) !$classMetadata->isInheritanceTypeNone() && null !== $classMetadata->discriminatorColumn ) { - $columnList[] = $classMetadata->discriminatorColumn['name']; + $columnList[] = self::getMappingValue($classMetadata->discriminatorColumn, 'name'); if ($classMetadata->isInheritanceTypeSingleTable() && null !== $classMetadata->discriminatorValue) { // Support for single table inheritance sub-classes @@ -298,7 +300,7 @@ public function find($className, $id, $revision, array $options = []) $whereSQL .= sprintf( ' AND %s IN (%s)', - $classMetadata->discriminatorColumn['name'], + self::getMappingValue($classMetadata->discriminatorColumn, 'name'), implode(', ', $queriedDiscrValues) ); } @@ -410,7 +412,7 @@ public function findEntitiesChangedAtRevision($revision) $columnMap = []; foreach ($classMetadata->fieldNames as $columnName => $field) { - $type = Type::getType($classMetadata->fieldMappings[$field]['type']); + $type = Type::getType(self::getMappingValue($classMetadata->fieldMappings[$field], 'type')); $tableAlias = $classMetadata->isInheritanceTypeJoined() && $classMetadata->isInheritedField($field) && !$classMetadata->isIdentifier($field) @@ -441,15 +443,15 @@ public function findEntitiesChangedAtRevision($revision) $classMetadata->isInheritanceTypeSingleTable() && null !== $classMetadata->discriminatorColumn ) { - $columnList .= ', e.'.$classMetadata->discriminatorColumn['name']; - $whereSQL .= ' AND e.'.$classMetadata->discriminatorColumn['fieldName'].' = ?'; + $columnList .= ', e.'.self::getMappingValue($classMetadata->discriminatorColumn, 'name'); + $whereSQL .= ' AND e.'.self::getMappingValue($classMetadata->discriminatorColumn, 'fieldName').' = ?'; $params[] = $classMetadata->discriminatorValue; } elseif ( $classMetadata->isInheritanceTypeJoined() && $classMetadata->rootEntityName !== $classMetadata->name && null !== $classMetadata->discriminatorColumn ) { - $columnList .= ', re.'.$classMetadata->discriminatorColumn['name']; + $columnList .= ', re.'.self::getMappingValue($classMetadata->discriminatorColumn, 'name'); $rootClass = $this->em->getClassMetadata($classMetadata->rootEntityName); $rootTableName = $this->config->getTableName($rootClass); @@ -543,7 +545,7 @@ public function findRevisions($className, $id) if ('' !== $whereSQL) { $whereSQL .= ' AND '; } - $whereSQL .= 'e.'.$classMetadata->fieldMappings[$idField]['columnName'].' = ?'; + $whereSQL .= 'e.'.self::getMappingValue($classMetadata->fieldMappings[$idField], 'columnName').' = ?'; } elseif (isset($classMetadata->associationMappings[$idField]['joinColumns'])) { if ('' !== $whereSQL) { $whereSQL .= ' AND '; @@ -605,12 +607,12 @@ public function getCurrentRevision($className, $id) if ('' !== $whereSQL) { $whereSQL .= ' AND '; } - $whereSQL .= 'e.'.$classMetadata->fieldMappings[$idField]['columnName'].' = ?'; + $whereSQL .= 'e.'.self::getMappingValue($classMetadata->fieldMappings[$idField], 'columnName').' = ?'; } elseif (isset($classMetadata->associationMappings[$idField]['joinColumns'])) { if ('' !== $whereSQL) { $whereSQL .= ' AND '; } - $whereSQL .= 'e.'.$classMetadata->associationMappings[$idField]['joinColumns'][0]['name'].' = ?'; + $whereSQL .= 'e.'.self::getMappingValue($classMetadata->associationMappings[$idField]['joinColumns'][0], 'name').' = ?'; } } @@ -721,10 +723,10 @@ public function getEntityHistory($className, $id) foreach ($classMetadata->identifier as $idField) { if (isset($classMetadata->fieldMappings[$idField])) { /** @phpstan-var literal-string $columnName */ - $columnName = $classMetadata->fieldMappings[$idField]['columnName']; + $columnName = self::getMappingValue($classMetadata->fieldMappings[$idField], 'columnName'); } elseif (isset($classMetadata->associationMappings[$idField]['joinColumns'])) { /** @phpstan-var literal-string $columnName */ - $columnName = $classMetadata->associationMappings[$idField]['joinColumns'][0]['name']; + $columnName = self::getMappingValue($classMetadata->associationMappings[$idField]['joinColumns'][0], 'name'); } else { continue; } @@ -737,7 +739,7 @@ public function getEntityHistory($className, $id) $columnMap = []; foreach ($classMetadata->fieldNames as $columnName => $field) { - $type = Type::getType($classMetadata->fieldMappings[$field]['type']); + $type = Type::getType(self::getMappingValue($classMetadata->fieldMappings[$field], 'type')); /** @phpstan-var literal-string $sqlExpr */ $sqlExpr = $type->convertToPHPValueSQL( $this->quoteStrategy->getColumnName($field, $classMetadata, $this->platform), @@ -845,10 +847,7 @@ private function createEntity($className, array $columnMap, array $data, $revisi !$classMetadata->isInheritanceTypeNone() && null !== $classMetadata->discriminatorColumn ) { - if (!isset($data[$classMetadata->discriminatorColumn['name']])) { - throw new \RuntimeException('Expecting discriminator value in data set.'); - } - $discriminator = $data[$classMetadata->discriminatorColumn['name']]; + $discriminator = $data[self::getMappingValue($classMetadata->discriminatorColumn, 'name')]; if (!isset($classMetadata->discriminatorMap[$discriminator])) { throw new \RuntimeException("No mapping found for [{$discriminator}]."); } @@ -884,7 +883,7 @@ private function createEntity($className, array $columnMap, array $data, $revisi foreach ($data as $field => $value) { if (isset($classMetadata->fieldMappings[$field])) { - $type = Type::getType($classMetadata->fieldMappings[$field]['type']); + $type = Type::getType(self::getMappingValue($classMetadata->fieldMappings[$field], 'type')); $value = $type->convertToPHPValue($value, $this->platform); $reflField = $classMetadata->reflFields[$field]; @@ -963,7 +962,7 @@ private function createEntity($className, array $columnMap, array $data, $revisi $joinColumnValue = $data[$columnMap[$srcColumn]] ?? null; if (null !== $joinColumnValue) { $targetField = $targetClass->fieldNames[$targetColumn]; - $joinColumnType = Type::getType($targetClass->fieldMappings[$targetField]['type']); + $joinColumnType = Type::getType(self::getMappingValue($targetClass->fieldMappings[$targetField], 'type')); $joinColumnValue = $joinColumnType->convertToPHPValue( $joinColumnValue, $this->platform @@ -1031,14 +1030,10 @@ private function createEntity($className, array $columnMap, array $data, $revisi \assert(null !== $reflField); $reflField->setValue($entity, $collection); } elseif (0 !== ($assoc['type'] & ClassMetadata::MANY_TO_MANY)) { - if ($assoc['isOwningSide'] && isset( - $assoc['relationToSourceKeyColumns'], - $assoc['relationToTargetKeyColumns'], - $assoc['joinTable']['name'] - )) { + if (self::isManyToManyOwningSideMapping($assoc)) { $whereId = [$this->config->getRevisionFieldName().' = ?']; $values = [$revision]; - foreach ($assoc['relationToSourceKeyColumns'] as $sourceKeyJoinColumn => $sourceKeyColumn) { + foreach (self::getMappingValue($assoc, 'relationToSourceKeyColumns') as $sourceKeyJoinColumn => $sourceKeyColumn) { $whereId[] = "{$sourceKeyJoinColumn} = ?"; $reflField = $classMetadata->reflFields['id']; @@ -1053,10 +1048,10 @@ private function createEntity($className, array $columnMap, array $data, $revisi $this->config->getRevisionTypeFieldName(), ]; $tableName = $this->config->getTablePrefix() - .$assoc['joinTable']['name'] + .self::getJoinTableName($assoc) .$this->config->getTableSuffix(); - foreach ($assoc['relationToTargetKeyColumns'] as $targetKeyJoinColumn => $targetKeyColumn) { + foreach (self::getMappingValue($assoc, 'relationToTargetKeyColumns') as $targetKeyJoinColumn => $targetKeyColumn) { $columnList[] = $targetKeyJoinColumn; } @@ -1078,7 +1073,7 @@ private function createEntity($className, array $columnMap, array $data, $revisi $id = []; /** @phpstan-var string $targetKeyColumn */ - foreach ($assoc['relationToTargetKeyColumns'] as $targetKeyJoinColumn => $targetKeyColumn) { + foreach (self::getMappingValue($assoc, 'relationToTargetKeyColumns') as $targetKeyJoinColumn => $targetKeyColumn) { $joinKey = $row[$targetKeyJoinColumn]; $id[$targetKeyColumn] = $joinKey; } diff --git a/src/EventListener/CreateSchemaListener.php b/src/EventListener/CreateSchemaListener.php index 42e1ca75..5dcb37e2 100644 --- a/src/EventListener/CreateSchemaListener.php +++ b/src/EventListener/CreateSchemaListener.php @@ -26,12 +26,15 @@ use SimpleThings\EntityAudit\AuditConfiguration; use SimpleThings\EntityAudit\AuditManager; use SimpleThings\EntityAudit\Metadata\MetadataFactory; +use SimpleThings\EntityAudit\Utils\ORMCompatibilityTrait; /** * NEXT_MAJOR: do not implement EventSubscriber interface anymore. */ class CreateSchemaListener implements EventSubscriber { + use ORMCompatibilityTrait; + private AuditConfiguration $config; private MetadataFactory $metadataFactory; @@ -108,13 +111,11 @@ public function postGenerateSchemaTable(GenerateSchemaTableEventArgs $eventArgs) $revisionTable->addIndex([$this->config->getRevisionFieldName()], $revIndexName); foreach ($cm->associationMappings as $associationMapping) { - if ($associationMapping['isOwningSide'] && isset($associationMapping['joinTable'])) { - if (isset($associationMapping['joinTable']['name'])) { - if ($schema->hasTable($associationMapping['joinTable']['name'])) { - $this->createRevisionJoinTableForJoinTable($schema, $associationMapping['joinTable']['name']); - } else { - $this->defferedJoinTablesToCreate[] = $associationMapping['joinTable']['name']; - } + if (self::isManyToManyOwningSideMapping($associationMapping)) { + if ($schema->hasTable(self::getJoinTableName($associationMapping))) { + $this->createRevisionJoinTableForJoinTable($schema, self::getJoinTableName($associationMapping)); + } else { + $this->defferedJoinTablesToCreate[] = self::getJoinTableName($associationMapping); } } } diff --git a/src/EventListener/LogRevisionsListener.php b/src/EventListener/LogRevisionsListener.php index 802c02c1..1308ba4b 100644 --- a/src/EventListener/LogRevisionsListener.php +++ b/src/EventListener/LogRevisionsListener.php @@ -24,8 +24,8 @@ use Doctrine\ORM\Event\PostPersistEventArgs; use Doctrine\ORM\Event\PostUpdateEventArgs; use Doctrine\ORM\Events; -use Doctrine\ORM\Mapping\AssociationMapping; use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Mapping\ManyToManyOwningSideMapping; use Doctrine\ORM\Persisters\Entity\EntityPersister; use Doctrine\ORM\UnitOfWork; use Doctrine\ORM\Utility\PersisterHelper; @@ -35,12 +35,15 @@ use SimpleThings\EntityAudit\AuditManager; use SimpleThings\EntityAudit\DeferredChangedManyToManyEntityRevisionToPersist; use SimpleThings\EntityAudit\Metadata\MetadataFactory; +use SimpleThings\EntityAudit\Utils\ORMCompatibilityTrait; /** * NEXT_MAJOR: do not implement EventSubscriber interface anymore. */ class LogRevisionsListener implements EventSubscriber { + use ORMCompatibilityTrait; + private AuditConfiguration $config; private MetadataFactory $metadataFactory; @@ -154,11 +157,11 @@ public function postFlush(PostFlushEventArgs $eventArgs): void foreach ($meta->associationMappings as $mapping) { if (isset($mapping['joinColumns'])) { foreach ($mapping['joinColumns'] as $definition) { - if ($definition['name'] === $column) { + if (self::getMappingValue($definition, 'name') === $column) { /** @var class-string $targetEntity */ $targetEntity = $mapping['targetEntity']; $targetTable = $em->getClassMetadata($targetEntity); - $type = $targetTable->getTypeOfField($targetTable->getFieldForColumn($definition['referencedColumnName'])); + $type = $targetTable->getTypeOfField($targetTable->getFieldForColumn(self::getMappingValue($definition, 'referencedColumnName'))); } } } @@ -179,11 +182,11 @@ public function postFlush(PostFlushEventArgs $eventArgs): void foreach ($meta->identifier as $idField) { if (isset($meta->fieldMappings[$idField])) { /** @phpstan-var literal-string $columnName */ - $columnName = $meta->fieldMappings[$idField]['columnName']; - $types[] = $meta->fieldMappings[$idField]['type']; + $columnName = self::getMappingValue($meta->fieldMappings[$idField], 'columnName'); + $types[] = self::getMappingValue($meta->fieldMappings[$idField], 'type'); } elseif (isset($meta->associationMappings[$idField]['joinColumns'])) { /** @phpstan-var literal-string $columnName */ - $columnName = $meta->associationMappings[$idField]['joinColumns'][0]['name']; + $columnName = self::getMappingValue($meta->associationMappings[$idField]['joinColumns'][0], 'name'); $types[] = $meta->associationMappings[$idField]['type']; } else { throw new \RuntimeException('column name not found for'.$idField); @@ -350,7 +353,7 @@ private function getManyToManyRelations(EntityManagerInterface $em, object $enti $data = []; $class = $em->getClassMetadata($entity::class); foreach ($class->associationMappings as $field => $assoc) { - if (($assoc['type'] & ClassMetadata::MANY_TO_MANY) > 0 && $assoc['isOwningSide']) { + if (self::isManyToManyOwningSideMapping($assoc)) { $reflField = $class->reflFields[$field]; \assert(null !== $reflField); $data[$field] = $reflField->getValue($entity); @@ -416,13 +419,9 @@ private function getInsertRevisionSQL(EntityManagerInterface $em, ClassMetadata continue; } - if ( - ($assoc['type'] & ClassMetadata::TO_ONE) > 0 - && true === $assoc['isOwningSide'] - && isset($assoc['targetToSourceKeyColumns']) - ) { + if (self::isToOneOwningSide($assoc)) { /** @phpstan-var literal-string $sourceCol */ - foreach ($assoc['targetToSourceKeyColumns'] as $sourceCol) { + foreach (self::getMappingValue($assoc, 'targetToSourceKeyColumns') as $sourceCol) { $fields[$sourceCol] = true; $sql .= ', '.$sourceCol; $placeholders[] = '?'; @@ -443,15 +442,11 @@ private function getInsertRevisionSQL(EntityManagerInterface $em, ClassMetadata } $platform = $em->getConnection()->getDatabasePlatform(); - $type = Type::getType($class->fieldMappings[$field]['type']); + $type = Type::getType(self::getMappingValue($class->fieldMappings[$field], 'type')); - if (true === ($class->fieldMappings[$field]['requireSQLConversion'] ?? false)) { - /** @phpstan-var literal-string $placeholder */ - $placeholder = $type->convertToDatabaseValueSQL('?', $platform); - $placeholders[] = $placeholder; - } else { - $placeholders[] = '?'; - } + /** @phpstan-var literal-string $placeholder */ + $placeholder = $type->convertToDatabaseValueSQL('?', $platform); + $placeholders[] = $placeholder; /** @phpstan-var literal-string $columnName */ $columnName = $em->getConfiguration()->getQuoteStrategy()->getColumnName($field, $class, $platform); @@ -466,7 +461,7 @@ private function getInsertRevisionSQL(EntityManagerInterface $em, ClassMetadata && null !== $class->discriminatorColumn ) { /** @var literal-string $discriminatorColumnName */ - $discriminatorColumnName = $class->discriminatorColumn['name']; + $discriminatorColumnName = self::getMappingValue($class->discriminatorColumn, 'name'); $sql .= ', '.$discriminatorColumnName; $placeholders[] = '?'; } @@ -480,9 +475,9 @@ private function getInsertRevisionSQL(EntityManagerInterface $em, ClassMetadata } /** - * @param ClassMetadata $class - * @param ClassMetadata $targetClass - * @param array|AssociationMapping $assoc + * @param ClassMetadata $class + * @param ClassMetadata $targetClass + * @param array|ManyToManyOwningSideMapping $assoc * * @return literal-string * @@ -491,18 +486,17 @@ private function getInsertRevisionSQL(EntityManagerInterface $em, ClassMetadata private function getInsertJoinTableRevisionSQL( ClassMetadata $class, ClassMetadata $targetClass, - array|AssociationMapping $assoc + /* @phpstan-ignore-next-line */ + array|ManyToManyOwningSideMapping $assoc ): string { - $cacheKey = $class->name.'.'.$targetClass->name.'.'.$assoc['joinTable']['name']; + $joinTableName = self::getJoinTableName($assoc); + $cacheKey = $class->name.'.'.$targetClass->name.'.'.$joinTableName; if ( !isset($this->insertJoinTableRevisionSQL[$cacheKey]) - && isset($assoc['relationToSourceKeyColumns'], $assoc['relationToTargetKeyColumns'], $assoc['joinTable']['name']) ) { $placeholders = ['?', '?']; - /** @phpstan-var literal-string $joinTableName */ - $joinTableName = $assoc['joinTable']['name']; $tableName = $this->config->getTablePrefix().$joinTableName.$this->config->getTableSuffix(); /** @psalm-trace $sql */ @@ -510,13 +504,22 @@ private function getInsertJoinTableRevisionSQL( .' ('.$this->config->getRevisionFieldName(). ', '.$this->config->getRevisionTypeFieldName(); - /** @phpstan-var literal-string $sourceColumn */ - foreach ($assoc['relationToSourceKeyColumns'] as $sourceColumn => $targetColumn) { + /** + * @phpstan-var literal-string $sourceColumn + * + * @phpstan-ignore argument.type + */ + foreach (self::getMappingValue($assoc, 'relationToSourceKeyColumns') as $sourceColumn => $targetColumn) { $sql .= ', '.$sourceColumn; $placeholders[] = '?'; } - /** @phpstan-var literal-string $sourceColumn */ - foreach ($assoc['relationToTargetKeyColumns'] as $sourceColumn => $targetColumn) { + + /** + * @phpstan-var literal-string $sourceColumn + * + * @phpstan-ignore argument.type + */ + foreach (self::getMappingValue($assoc, 'relationToTargetKeyColumns') as $sourceColumn => $targetColumn) { $sql .= ', '.$sourceColumn; $placeholders[] = '?'; } @@ -605,7 +608,7 @@ private function saveRevisionEntityData(EntityManagerInterface $em, ClassMetadat } $params[] = $entityData[$field] ?? null; - $types[] = $class->fieldMappings[$field]['type']; + $types[] = self::getMappingValue($class->fieldMappings[$field], 'type'); } if ( @@ -613,24 +616,25 @@ private function saveRevisionEntityData(EntityManagerInterface $em, ClassMetadat && null !== $class->discriminatorColumn ) { $params[] = $class->discriminatorValue; - $types[] = $class->discriminatorColumn['type']; + $types[] = self::getMappingValue($class->discriminatorColumn, 'type'); } elseif ( $class->isInheritanceTypeJoined() && $class->name === $class->rootEntityName && null !== $class->discriminatorColumn ) { - $params[] = $entityData[$class->discriminatorColumn['name']]; - $types[] = $class->discriminatorColumn['type']; + $params[] = $entityData[self::getMappingValue($class->discriminatorColumn, 'name')]; + $types[] = self::getMappingValue($class->discriminatorColumn, 'type'); } if ( $class->isInheritanceTypeJoined() && $class->name !== $class->rootEntityName && null !== $class->discriminatorColumn ) { - $entityData[$class->discriminatorColumn['name']] = $class->discriminatorValue; + $entityData[self::getMappingValue($class->discriminatorColumn, 'name')] = $class->discriminatorValue; $this->saveRevisionEntityData( $em, $em->getClassMetadata($class->rootEntityName), + /* @phpstan-ignore argument.type */ $entityData, $revType ); @@ -646,28 +650,33 @@ private function saveRevisionEntityData(EntityManagerInterface $em, ClassMetadat } /** - * @param array|AssociationMapping $assoc - * @param array $entityData - * @param ClassMetadata $class - * @param ClassMetadata $targetClass + * @param array|ManyToManyOwningSideMapping $assoc + * @param array $entityData + * @param ClassMetadata $class + * @param ClassMetadata $targetClass */ private function recordRevisionForManyToManyEntity( object $relatedEntity, EntityManagerInterface $em, string $revType, array $entityData, - array|AssociationMapping $assoc, + /* @phpstan-ignore-next-line */ + array|ManyToManyOwningSideMapping $assoc, ClassMetadata $class, ClassMetadata $targetClass ): void { $conn = $em->getConnection(); $joinTableParams = [$this->getRevisionId($conn), $revType]; $joinTableTypes = [\PDO::PARAM_INT, \PDO::PARAM_STR]; - foreach ($assoc['relationToSourceKeyColumns'] as $targetColumn) { + + /* @phpstan-ignore argument.type */ + foreach (self::getMappingValue($assoc, 'relationToSourceKeyColumns') as $targetColumn) { $joinTableParams[] = $entityData[$class->fieldNames[$targetColumn]]; $joinTableTypes[] = PersisterHelper::getTypeOfColumn($targetColumn, $class, $em); } - foreach ($assoc['relationToTargetKeyColumns'] as $targetColumn) { + + /* @phpstan-ignore argument.type */ + foreach (self::getMappingValue($assoc, 'relationToTargetKeyColumns') as $targetColumn) { $reflField = $targetClass->reflFields[$targetClass->fieldNames[$targetColumn]]; \assert(null !== $reflField); $joinTableParams[] = $reflField->getValue($relatedEntity); @@ -731,7 +740,7 @@ private function prepareUpdateData(EntityManagerInterface $em, EntityPersister $ $newVal = $change[1]; if (!isset($classMetadata->associationMappings[$field])) { - $columnName = $classMetadata->fieldMappings[$field]['columnName']; + $columnName = self::getMappingValue($classMetadata->fieldMappings[$field], 'columnName'); $result[$persister->getOwningTable($field)][$columnName] = $newVal; continue; @@ -769,8 +778,8 @@ private function prepareUpdateData(EntityManagerInterface $em, EntityPersister $ $owningTable = $persister->getOwningTable($field); foreach ($assoc['joinColumns'] as $joinColumn) { - $sourceColumn = $joinColumn['name']; - $targetColumn = $joinColumn['referencedColumnName']; + $sourceColumn = self::getMappingValue($joinColumn, 'name'); + $targetColumn = self::getMappingValue($joinColumn, 'referencedColumnName'); $result[$owningTable][$sourceColumn] = null !== $newValId ? $newValId[$targetClass->getFieldForColumn($targetColumn)] diff --git a/src/Utils/ORMCompatibilityTrait.php b/src/Utils/ORMCompatibilityTrait.php new file mode 100644 index 00000000..b770a403 --- /dev/null +++ b/src/Utils/ORMCompatibilityTrait.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace SimpleThings\EntityAudit\Utils; + +use Doctrine\ORM\Mapping\AssociationMapping; +use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Mapping\DiscriminatorColumnMapping; +use Doctrine\ORM\Mapping\EmbeddedClassMapping; +use Doctrine\ORM\Mapping\FieldMapping; +use Doctrine\ORM\Mapping\JoinColumnMapping; +use Doctrine\ORM\Mapping\ManyToManyOwningSideMapping; +use Doctrine\ORM\Mapping\ToOneOwningSideMapping; + +/** + * @internal + */ +trait ORMCompatibilityTrait +{ + /** + * @param array|AssociationMapping|EmbeddedClassMapping|FieldMapping|JoinColumnMapping|DiscriminatorColumnMapping $mapping + * + * @phpstan-ignore-next-line + */ + private static function getMappingValue(array|AssociationMapping|EmbeddedClassMapping|FieldMapping|JoinColumnMapping|DiscriminatorColumnMapping $mapping, string $key): mixed + { + /* @phpstan-ignore-next-line */ + if ($mapping instanceof AssociationMapping || $mapping instanceof EmbeddedClassMapping || $mapping instanceof FieldMapping || $mapping instanceof JoinColumnMapping || $mapping instanceof DiscriminatorColumnMapping) { + /* @phpstan-ignore property.dynamicName */ + return $mapping->$key; + } + + return $mapping[$key] ?? null; + } + + /** + * @param array|ManyToManyOwningSideMapping $mapping + * + * @return literal-string + * + * @phpstan-ignore-next-line + */ + private static function getJoinTableName(array|ManyToManyOwningSideMapping $mapping): string + { + /* @phpstan-ignore-next-line */ + if ($mapping instanceof ManyToManyOwningSideMapping) { + /* @phpstan-ignore-next-line */ + return $mapping->joinTable->name; + } + + /* @phpstan-ignore-next-line */ + return $mapping['joinTable']['name']; + } + + /** + * @param array|AssociationMapping $mapping + */ + private static function isManyToManyOwningSideMapping(array|AssociationMapping $mapping): bool + { + /* @phpstan-ignore-next-line */ + if ($mapping instanceof AssociationMapping) { + /* @phpstan-ignore-next-line */ + return $mapping instanceof ManyToManyOwningSideMapping; + } + + /* @phpstan-ignore-next-line */ + return $mapping['isOwningSide'] && isset($mapping['joinTable']['name']); + } + + /** + * @param array|AssociationMapping $mapping + */ + private static function isToOneOwningSide(array|AssociationMapping $mapping): bool + { + /* @phpstan-ignore class.notFound */ + if ($mapping instanceof AssociationMapping) { + /* @phpstan-ignore class.notFound */ + return $mapping instanceof ToOneOwningSideMapping; + } + + return ($mapping['type'] & ClassMetadata::TO_ONE) > 0 + && true === $mapping['isOwningSide'] + && isset($mapping['targetToSourceKeyColumns']); + } +} diff --git a/tests/BaseTest.php b/tests/BaseTest.php index 8b520778..bb3e2aab 100644 --- a/tests/BaseTest.php +++ b/tests/BaseTest.php @@ -16,6 +16,7 @@ use Doctrine\Common\EventManager; use Doctrine\DBAL\Connection; use Doctrine\DBAL\DriverManager; +use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory; use Doctrine\DBAL\Types\Type; use Doctrine\ORM\Configuration; use Doctrine\ORM\EntityManager; @@ -94,6 +95,7 @@ protected function getEntityManager(): EntityManager } $config = ORMSetup::createAttributeMetadataConfiguration($mappingPaths, true); + $config->setSchemaManagerFactory(new DefaultSchemaManagerFactory()); $connection = $this->_getConnection($config); $this->em = new EntityManager($connection, $config, new EventManager()); From 03bc548f04063d09443e799e193ce3428ef4c2e8 Mon Sep 17 00:00:00 2001 From: Fran Moreno Date: Sun, 16 Jun 2024 21:18:13 +0200 Subject: [PATCH 05/10] Require gedmo/doctrine-extensions 3.15 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 4a71588c..cbca7b01 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "doctrine/doctrine-bundle": "^2.7", "doctrine/doctrine-fixtures-bundle": "^3.4", "friendsofphp/php-cs-fixer": "^3.4", - "gedmo/doctrine-extensions": "^3.7", + "gedmo/doctrine-extensions": "^3.15", "matthiasnoback/symfony-dependency-injection-test": "^4.2.1 || ^5.0", "phpstan/extension-installer": "^1.1", "phpstan/phpstan": "^1.0", From d07103ae63de0b0e4a3ea32cc57045a89264eecd Mon Sep 17 00:00:00 2001 From: Fran Moreno Date: Sun, 16 Jun 2024 21:26:50 +0200 Subject: [PATCH 06/10] Bump doctrine/dbal --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index cbca7b01..3c14bf30 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ "require": { "php": "^8.0", "doctrine/collections": "^1.8 || ^2.0", - "doctrine/dbal": "^3.4", + "doctrine/dbal": "^3.6", "doctrine/event-manager": "^1.2 || ^2.0", "doctrine/orm": "^2.14 || ^3.0", "doctrine/persistence": "^3.0", From 66efee82cb94336c4e3873b02bbaa6b9e288ca88 Mon Sep 17 00:00:00 2001 From: Fran Moreno Date: Mon, 24 Jun 2024 18:53:30 +0200 Subject: [PATCH 07/10] Remove more array access deprecations --- phpstan-baseline.neon | 4 - psalm-baseline.xml | 7 - src/AuditReader.php | 111 ++++++----- src/Collection/AuditedCollection.php | 8 +- ...angedManyToManyEntityRevisionToPersist.php | 15 +- src/EventListener/CreateSchemaListener.php | 9 +- src/EventListener/LogRevisionsListener.php | 81 +++----- src/Utils/ORMCompatibilityTrait.php | 178 ++++++++++++++++-- 8 files changed, 261 insertions(+), 152 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 21e083c1..394c5050 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -3,7 +3,3 @@ parameters: # The class "AuditController" is deprecated and extends a class that is only supported in "symfony/http-foundation" < 5. # NEXT_MAJOR: Remove these files - src/Controller/AuditController.php - ignoreErrors: - # For compatibility with doctrine/orm 2 and 3 - - '#.+ has invalid type Doctrine\\ORM\\Mapping\\AssociationMapping\.$#' - - '#^Property SimpleThings\\EntityAudit\\Collection\\AuditedCollection\:\:\$associationDefinition has unknown class Doctrine\\ORM\\Mapping\\AssociationMapping as its type\.$#' diff --git a/psalm-baseline.xml b/psalm-baseline.xml index efe38aa5..ee3489dc 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -6,11 +6,4 @@ Controller - - - - |AssociationMapping]]> - |AssociationMapping]]> - - diff --git a/src/AuditReader.php b/src/AuditReader.php index 943c21c4..57ea5aaa 100644 --- a/src/AuditReader.php +++ b/src/AuditReader.php @@ -19,9 +19,10 @@ use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\Type; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Exception\ORMException; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\QuoteStrategy; -use Doctrine\ORM\ORMException; +use Doctrine\ORM\ORMException as ORM2Exception; use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\Persisters\Entity\EntityPersister; use SimpleThings\EntityAudit\Collection\AuditedCollection; @@ -189,12 +190,17 @@ public function clearEntityCache(): void * @throws NotAuditedException * @throws Exception * @throws ORMException + * @throws ORM2Exception * @throws \RuntimeException * * @return object|null * + * @psalm-suppress UndefinedDocblockClass + * * @phpstan-param class-string $className * @phpstan-return T|null + * + * @phpstan-ignore throws.notThrowable */ public function find($className, $id, $revision, array $options = []) { @@ -215,7 +221,7 @@ public function find($className, $id, $revision, array $options = []) $idKeys = array_keys($id); $columnName = $idKeys[0]; } elseif (isset($classMetadata->fieldMappings[$idField])) { - $columnName = self::getMappingValue($classMetadata->fieldMappings[$idField], 'columnName'); + $columnName = self::getMappingColumnNameValue($classMetadata->fieldMappings[$idField]); } elseif (isset($classMetadata->associationMappings[$idField]['joinColumns'])) { $columnName = $classMetadata->associationMappings[$idField]['joinColumns'][0]['name']; } else { @@ -252,18 +258,15 @@ public function find($className, $id, $revision, array $options = []) } foreach ($classMetadata->associationMappings as $assoc) { - if ( - ($assoc['type'] & ClassMetadata::TO_ONE) === 0 - || false === $assoc['isOwningSide'] - || !isset($assoc['joinColumnFieldNames']) - ) { + if (!self::isToOneOwningSide($assoc)) { continue; } - foreach ($assoc['joinColumnFieldNames'] as $sourceCol) { + /** @var string $sourceCol */ + foreach (self::getMappingValue($assoc, 'joinColumnFieldNames') as $sourceCol) { $tableAlias = $classMetadata->isInheritanceTypeJoined() - && $classMetadata->isInheritedAssociation($assoc['fieldName']) - && !$classMetadata->isIdentifier($assoc['fieldName']) + && $classMetadata->isInheritedAssociation(self::getMappingFieldNameValue($assoc)) + && !$classMetadata->isIdentifier(self::getMappingFieldNameValue($assoc)) ? 're' // root entity : 'e'; $columnList[] = $tableAlias.'.'.$sourceCol; @@ -288,7 +291,7 @@ public function find($className, $id, $revision, array $options = []) !$classMetadata->isInheritanceTypeNone() && null !== $classMetadata->discriminatorColumn ) { - $columnList[] = self::getMappingValue($classMetadata->discriminatorColumn, 'name'); + $columnList[] = self::getMappingNameValue($classMetadata->discriminatorColumn); if ($classMetadata->isInheritanceTypeSingleTable() && null !== $classMetadata->discriminatorValue) { // Support for single table inheritance sub-classes @@ -300,7 +303,7 @@ public function find($className, $id, $revision, array $options = []) $whereSQL .= sprintf( ' AND %s IN (%s)', - self::getMappingValue($classMetadata->discriminatorColumn, 'name'), + self::getMappingNameValue($classMetadata->discriminatorColumn), implode(', ', $queriedDiscrValues) ); } @@ -386,10 +389,15 @@ public function findEntitesChangedAtRevision($revision) * @throws NotAuditedException * @throws Exception * @throws ORMException + * @throws ORM2Exception * @throws \RuntimeException * @throws DeletedException * * @return ChangedEntity[] + * + * @psalm-suppress UndefinedDocblockClass + * + * @phpstan-ignore throws.notThrowable */ public function findEntitiesChangedAtRevision($revision) { @@ -426,12 +434,8 @@ public function findEntitiesChangedAtRevision($revision) } foreach ($classMetadata->associationMappings as $assoc) { - if ( - ($assoc['type'] & ClassMetadata::TO_ONE) > 0 - && true === $assoc['isOwningSide'] - && isset($assoc['targetToSourceKeyColumns']) - ) { - foreach ($assoc['targetToSourceKeyColumns'] as $sourceCol) { + if (self::isToOneOwningSide($assoc)) { + foreach (self::getTargetToSourceKeyColumns($assoc) as $sourceCol) { $columnList .= ', '.$sourceCol; $columnMap[$sourceCol] = $this->getSQLResultCasing($this->platform, $sourceCol); } @@ -443,15 +447,15 @@ public function findEntitiesChangedAtRevision($revision) $classMetadata->isInheritanceTypeSingleTable() && null !== $classMetadata->discriminatorColumn ) { - $columnList .= ', e.'.self::getMappingValue($classMetadata->discriminatorColumn, 'name'); - $whereSQL .= ' AND e.'.self::getMappingValue($classMetadata->discriminatorColumn, 'fieldName').' = ?'; + $columnList .= ', e.'.self::getMappingNameValue($classMetadata->discriminatorColumn); + $whereSQL .= ' AND e.'.self::getMappingFieldNameValue($classMetadata->discriminatorColumn).' = ?'; $params[] = $classMetadata->discriminatorValue; } elseif ( $classMetadata->isInheritanceTypeJoined() && $classMetadata->rootEntityName !== $classMetadata->name && null !== $classMetadata->discriminatorColumn ) { - $columnList .= ', re.'.self::getMappingValue($classMetadata->discriminatorColumn, 'name'); + $columnList .= ', re.'.self::getMappingNameValue($classMetadata->discriminatorColumn); $rootClass = $this->em->getClassMetadata($classMetadata->rootEntityName); $rootTableName = $this->config->getTableName($rootClass); @@ -545,7 +549,7 @@ public function findRevisions($className, $id) if ('' !== $whereSQL) { $whereSQL .= ' AND '; } - $whereSQL .= 'e.'.self::getMappingValue($classMetadata->fieldMappings[$idField], 'columnName').' = ?'; + $whereSQL .= 'e.'.self::getMappingColumnNameValue($classMetadata->fieldMappings[$idField]).' = ?'; } elseif (isset($classMetadata->associationMappings[$idField]['joinColumns'])) { if ('' !== $whereSQL) { $whereSQL .= ' AND '; @@ -607,12 +611,12 @@ public function getCurrentRevision($className, $id) if ('' !== $whereSQL) { $whereSQL .= ' AND '; } - $whereSQL .= 'e.'.self::getMappingValue($classMetadata->fieldMappings[$idField], 'columnName').' = ?'; + $whereSQL .= 'e.'.self::getMappingColumnNameValue($classMetadata->fieldMappings[$idField]).' = ?'; } elseif (isset($classMetadata->associationMappings[$idField]['joinColumns'])) { if ('' !== $whereSQL) { $whereSQL .= ' AND '; } - $whereSQL .= 'e.'.self::getMappingValue($classMetadata->associationMappings[$idField]['joinColumns'][0], 'name').' = ?'; + $whereSQL .= 'e.'.self::getMappingNameValue($classMetadata->associationMappings[$idField]['joinColumns'][0]).' = ?'; } } @@ -644,12 +648,17 @@ public function getCurrentRevision($className, $id) * @throws NotAuditedException * @throws Exception * @throws ORMException + * @throws ORM2Exception * @throws \RuntimeException * * @return array> * + * @psalm-suppress UndefinedDocblockClass + * * @phpstan-param class-string $className * @phpstan-return array + * + * @phpstan-ignore throws.notThrowable */ public function diff($className, $id, $oldRevision, $newRevision) { @@ -697,12 +706,17 @@ public function getEntityValues($className, $entity) * @throws NotAuditedException * @throws Exception * @throws ORMException + * @throws ORM2Exception * @throws DeletedException * * @return array * + * @psalm-suppress UndefinedDocblockClass + * * @phpstan-param class-string $className * @phpstan-return array + * + * @phpstan-ignore throws.notThrowable */ public function getEntityHistory($className, $id) { @@ -722,11 +736,9 @@ public function getEntityHistory($className, $id) $whereId = []; foreach ($classMetadata->identifier as $idField) { if (isset($classMetadata->fieldMappings[$idField])) { - /** @phpstan-var literal-string $columnName */ - $columnName = self::getMappingValue($classMetadata->fieldMappings[$idField], 'columnName'); + $columnName = self::getMappingColumnNameValue($classMetadata->fieldMappings[$idField]); } elseif (isset($classMetadata->associationMappings[$idField]['joinColumns'])) { - /** @phpstan-var literal-string $columnName */ - $columnName = self::getMappingValue($classMetadata->associationMappings[$idField]['joinColumns'][0], 'name'); + $columnName = self::getMappingNameValue($classMetadata->associationMappings[$idField]['joinColumns'][0]); } else { continue; } @@ -752,16 +764,11 @@ public function getEntityHistory($className, $id) } foreach ($classMetadata->associationMappings as $assoc) { - if ( - ($assoc['type'] & ClassMetadata::TO_ONE) === 0 - || false === $assoc['isOwningSide'] - || !isset($assoc['targetToSourceKeyColumns']) - ) { + if (!self::isToOneOwningSide($assoc)) { continue; } - /** @phpstan-var literal-string $sourceCol */ - foreach ($assoc['targetToSourceKeyColumns'] as $sourceCol) { + foreach (self::getTargetToSourceKeyColumns($assoc) as $sourceCol) { $columnList[] = $sourceCol; $columnMap[$sourceCol] = $this->getSQLResultCasing($this->platform, $sourceCol); } @@ -816,12 +823,17 @@ protected function getEntityPersister($className) * @throws NotAuditedException * @throws Exception * @throws ORMException + * @throws ORM2Exception * @throws \RuntimeException * * @return object * + * @psalm-suppress UndefinedDocblockClass + * * @phpstan-param class-string $className * @phpstan-return T + * + * @phpstan-ignore throws.notThrowable */ private function createEntity($className, array $columnMap, array $data, $revision) { @@ -847,7 +859,7 @@ private function createEntity($className, array $columnMap, array $data, $revisi !$classMetadata->isInheritanceTypeNone() && null !== $classMetadata->discriminatorColumn ) { - $discriminator = $data[self::getMappingValue($classMetadata->discriminatorColumn, 'name')]; + $discriminator = $data[self::getMappingNameValue($classMetadata->discriminatorColumn)]; if (!isset($classMetadata->discriminatorMap[$discriminator])) { throw new \RuntimeException("No mapping found for [{$discriminator}]."); } @@ -894,12 +906,12 @@ private function createEntity($className, array $columnMap, array $data, $revisi foreach ($classMetadata->associationMappings as $field => $assoc) { /** @phpstan-var class-string $targetEntity */ - $targetEntity = $assoc['targetEntity']; + $targetEntity = self::getMappingTargetEntityValue($assoc); $targetClass = $this->em->getClassMetadata($targetEntity); $mappedBy = $assoc['mappedBy'] ?? null; - if (0 !== ($assoc['type'] & ClassMetadata::TO_ONE)) { + if (self::isToOne($assoc)) { if ($this->metadataFactory->isAudited($targetEntity)) { if ($this->loadAuditedEntities) { // Primary Key. Used for audit tables queries. @@ -907,8 +919,8 @@ private function createEntity($className, array $columnMap, array $data, $revisi // Primary Field. Used when fallback to Doctrine finder. $pf = []; - if (true === $assoc['isOwningSide'] && isset($assoc['targetToSourceKeyColumns'])) { - foreach ($assoc['targetToSourceKeyColumns'] as $foreign => $local) { + if (self::isToOneOwningSide($assoc)) { + foreach (self::getTargetToSourceKeyColumns($assoc) as $foreign => $local) { $key = $data[$columnMap[$local]]; if (null === $key) { continue; @@ -921,15 +933,15 @@ private function createEntity($className, array $columnMap, array $data, $revisi $otherEntityAssoc = $this->em->getClassMetadata($targetEntity) ->associationMappings[$mappedBy]; - if (isset($otherEntityAssoc['targetToSourceKeyColumns'])) { - foreach ($otherEntityAssoc['targetToSourceKeyColumns'] as $local => $foreign) { + if (self::isToOneOwningSide($otherEntityAssoc)) { + foreach (self::getTargetToSourceKeyColumns($otherEntityAssoc) as $local => $foreign) { $key = $data[$classMetadata->getFieldName($local)]; if (null === $key) { continue; } $pk[$foreign] = $key; - $pf[$otherEntityAssoc['fieldName']] = $key; + $pf[self::getMappingFieldNameValue($otherEntityAssoc)] = $key; } } } @@ -956,9 +968,9 @@ private function createEntity($className, array $columnMap, array $data, $revisi } } else { if ($this->loadNativeEntities) { - if (true === $assoc['isOwningSide'] && isset($assoc['targetToSourceKeyColumns'])) { + if (self::isToOneOwningSide($assoc)) { $associatedId = []; - foreach ($assoc['targetToSourceKeyColumns'] as $targetColumn => $srcColumn) { + foreach (self::getTargetToSourceKeyColumns($assoc) as $targetColumn => $srcColumn) { $joinColumnValue = $data[$columnMap[$srcColumn]] ?? null; if (null !== $joinColumnValue) { $targetField = $targetClass->fieldNames[$targetColumn]; @@ -1033,7 +1045,7 @@ private function createEntity($className, array $columnMap, array $data, $revisi if (self::isManyToManyOwningSideMapping($assoc)) { $whereId = [$this->config->getRevisionFieldName().' = ?']; $values = [$revision]; - foreach (self::getMappingValue($assoc, 'relationToSourceKeyColumns') as $sourceKeyJoinColumn => $sourceKeyColumn) { + foreach (self::getRelationToSourceKeyColumns($assoc) as $sourceKeyJoinColumn => $sourceKeyColumn) { $whereId[] = "{$sourceKeyJoinColumn} = ?"; $reflField = $classMetadata->reflFields['id']; @@ -1048,10 +1060,10 @@ private function createEntity($className, array $columnMap, array $data, $revisi $this->config->getRevisionTypeFieldName(), ]; $tableName = $this->config->getTablePrefix() - .self::getJoinTableName($assoc) + .self::getMappingJoinTableNameValue($assoc) .$this->config->getTableSuffix(); - foreach (self::getMappingValue($assoc, 'relationToTargetKeyColumns') as $targetKeyJoinColumn => $targetKeyColumn) { + foreach (self::getRelationToTargetKeyColumns($assoc) as $targetKeyJoinColumn => $targetKeyColumn) { $columnList[] = $targetKeyJoinColumn; } @@ -1072,8 +1084,7 @@ private function createEntity($className, array $columnMap, array $data, $revisi foreach ($rows as $row) { $id = []; - /** @phpstan-var string $targetKeyColumn */ - foreach (self::getMappingValue($assoc, 'relationToTargetKeyColumns') as $targetKeyJoinColumn => $targetKeyColumn) { + foreach (self::getRelationToTargetKeyColumns($assoc) as $targetKeyJoinColumn => $targetKeyColumn) { $joinKey = $row[$targetKeyJoinColumn]; $id[$targetKeyColumn] = $joinKey; } diff --git a/src/Collection/AuditedCollection.php b/src/Collection/AuditedCollection.php index 55152d56..063e81ea 100644 --- a/src/Collection/AuditedCollection.php +++ b/src/Collection/AuditedCollection.php @@ -59,10 +59,10 @@ class AuditedCollection implements Collection protected $initialized = false; /** - * @param string $class - * @param array $associationDefinition - * @param array $foreignKeys - * @param string|int $revision + * @param string $class + * @param array|AssociationMapping $associationDefinition + * @param array $foreignKeys + * @param string|int $revision * * @phpstan-param class-string $class * @phpstan-param ClassMetadata $metadata diff --git a/src/DeferredChangedManyToManyEntityRevisionToPersist.php b/src/DeferredChangedManyToManyEntityRevisionToPersist.php index 05182066..2b6f3448 100644 --- a/src/DeferredChangedManyToManyEntityRevisionToPersist.php +++ b/src/DeferredChangedManyToManyEntityRevisionToPersist.php @@ -14,6 +14,7 @@ namespace SimpleThings\EntityAudit; use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Mapping\ManyToManyOwningSideMapping; /** * @internal @@ -21,16 +22,16 @@ final class DeferredChangedManyToManyEntityRevisionToPersist { /** - * @param array $assoc - * @param array $entityData - * @param ClassMetadata $class - * @param ClassMetadata $targetClass + * @param array|ManyToManyOwningSideMapping $assoc + * @param array $entityData + * @param ClassMetadata $class + * @param ClassMetadata $targetClass */ public function __construct( private object $entity, private string $revType, private array $entityData, - private array $assoc, + private array|ManyToManyOwningSideMapping $assoc, private ClassMetadata $class, private ClassMetadata $targetClass ) { @@ -55,9 +56,9 @@ public function getEntityData(): array } /** - * @return array + * @return array|ManyToManyOwningSideMapping */ - public function getAssoc(): array + public function getAssoc(): array|ManyToManyOwningSideMapping { return $this->assoc; } diff --git a/src/EventListener/CreateSchemaListener.php b/src/EventListener/CreateSchemaListener.php index 5dcb37e2..0b06057a 100644 --- a/src/EventListener/CreateSchemaListener.php +++ b/src/EventListener/CreateSchemaListener.php @@ -64,6 +64,9 @@ public function getSubscribedEvents() ]; } + /** + * @psalm-suppress TypeDoesNotContainType, NoValue + */ public function postGenerateSchemaTable(GenerateSchemaTableEventArgs $eventArgs): void { $cm = $eventArgs->getClassMetadata(); @@ -112,10 +115,10 @@ public function postGenerateSchemaTable(GenerateSchemaTableEventArgs $eventArgs) foreach ($cm->associationMappings as $associationMapping) { if (self::isManyToManyOwningSideMapping($associationMapping)) { - if ($schema->hasTable(self::getJoinTableName($associationMapping))) { - $this->createRevisionJoinTableForJoinTable($schema, self::getJoinTableName($associationMapping)); + if ($schema->hasTable(self::getMappingJoinTableNameValue($associationMapping))) { + $this->createRevisionJoinTableForJoinTable($schema, self::getMappingJoinTableNameValue($associationMapping)); } else { - $this->defferedJoinTablesToCreate[] = self::getJoinTableName($associationMapping); + $this->defferedJoinTablesToCreate[] = self::getMappingJoinTableNameValue($associationMapping); } } } diff --git a/src/EventListener/LogRevisionsListener.php b/src/EventListener/LogRevisionsListener.php index 1308ba4b..af4d3c40 100644 --- a/src/EventListener/LogRevisionsListener.php +++ b/src/EventListener/LogRevisionsListener.php @@ -157,10 +157,8 @@ public function postFlush(PostFlushEventArgs $eventArgs): void foreach ($meta->associationMappings as $mapping) { if (isset($mapping['joinColumns'])) { foreach ($mapping['joinColumns'] as $definition) { - if (self::getMappingValue($definition, 'name') === $column) { - /** @var class-string $targetEntity */ - $targetEntity = $mapping['targetEntity']; - $targetTable = $em->getClassMetadata($targetEntity); + if (self::getMappingNameValue($definition) === $column) { + $targetTable = $em->getClassMetadata(self::getMappingTargetEntityValue($mapping)); $type = $targetTable->getTypeOfField($targetTable->getFieldForColumn(self::getMappingValue($definition, 'referencedColumnName'))); } } @@ -181,12 +179,10 @@ public function postFlush(PostFlushEventArgs $eventArgs): void foreach ($meta->identifier as $idField) { if (isset($meta->fieldMappings[$idField])) { - /** @phpstan-var literal-string $columnName */ - $columnName = self::getMappingValue($meta->fieldMappings[$idField], 'columnName'); + $columnName = self::getMappingColumnNameValue($meta->fieldMappings[$idField]); $types[] = self::getMappingValue($meta->fieldMappings[$idField], 'type'); } elseif (isset($meta->associationMappings[$idField]['joinColumns'])) { - /** @phpstan-var literal-string $columnName */ - $columnName = self::getMappingValue($meta->associationMappings[$idField]['joinColumns'][0], 'name'); + $columnName = self::getMappingNameValue($meta->associationMappings[$idField]['joinColumns'][0]); $types[] = $meta->associationMappings[$idField]['type']; } else { throw new \RuntimeException('column name not found for'.$idField); @@ -400,8 +396,6 @@ private function getRevisionId(Connection $conn) * @throws Exception * * @return literal-string - * - * @psalm-suppress MoreSpecificReturnType,PropertyTypeCoercion,LessSpecificReturnStatement https://github.com/vimeo/psalm/issues/10909 */ private function getInsertRevisionSQL(EntityManagerInterface $em, ClassMetadata $class): string { @@ -420,8 +414,7 @@ private function getInsertRevisionSQL(EntityManagerInterface $em, ClassMetadata } if (self::isToOneOwningSide($assoc)) { - /** @phpstan-var literal-string $sourceCol */ - foreach (self::getMappingValue($assoc, 'targetToSourceKeyColumns') as $sourceCol) { + foreach (self::getTargetToSourceKeyColumns($assoc) as $sourceCol) { $fields[$sourceCol] = true; $sql .= ', '.$sourceCol; $placeholders[] = '?'; @@ -460,8 +453,7 @@ private function getInsertRevisionSQL(EntityManagerInterface $em, ClassMetadata ) && null !== $class->discriminatorColumn ) { - /** @var literal-string $discriminatorColumnName */ - $discriminatorColumnName = self::getMappingValue($class->discriminatorColumn, 'name'); + $discriminatorColumnName = self::getMappingNameValue($class->discriminatorColumn); $sql .= ', '.$discriminatorColumnName; $placeholders[] = '?'; } @@ -480,16 +472,13 @@ private function getInsertRevisionSQL(EntityManagerInterface $em, ClassMetadata * @param array|ManyToManyOwningSideMapping $assoc * * @return literal-string - * - * @psalm-suppress MoreSpecificReturnType,PropertyTypeCoercion,LessSpecificReturnStatement https://github.com/vimeo/psalm/issues/10909 */ private function getInsertJoinTableRevisionSQL( ClassMetadata $class, ClassMetadata $targetClass, - /* @phpstan-ignore-next-line */ array|ManyToManyOwningSideMapping $assoc ): string { - $joinTableName = self::getJoinTableName($assoc); + $joinTableName = self::getMappingJoinTableNameValue($assoc); $cacheKey = $class->name.'.'.$targetClass->name.'.'.$joinTableName; if ( @@ -499,27 +488,16 @@ private function getInsertJoinTableRevisionSQL( $tableName = $this->config->getTablePrefix().$joinTableName.$this->config->getTableSuffix(); - /** @psalm-trace $sql */ $sql = 'INSERT INTO '.$tableName .' ('.$this->config->getRevisionFieldName(). ', '.$this->config->getRevisionTypeFieldName(); - /** - * @phpstan-var literal-string $sourceColumn - * - * @phpstan-ignore argument.type - */ - foreach (self::getMappingValue($assoc, 'relationToSourceKeyColumns') as $sourceColumn => $targetColumn) { + foreach (self::getRelationToSourceKeyColumns($assoc) as $sourceColumn => $targetColumn) { $sql .= ', '.$sourceColumn; $placeholders[] = '?'; } - /** - * @phpstan-var literal-string $sourceColumn - * - * @phpstan-ignore argument.type - */ - foreach (self::getMappingValue($assoc, 'relationToTargetKeyColumns') as $sourceColumn => $targetColumn) { + foreach (self::getRelationToTargetKeyColumns($assoc) as $sourceColumn => $targetColumn) { $sql .= ', '.$sourceColumn; $placeholders[] = '?'; } @@ -551,9 +529,8 @@ private function saveRevisionEntityData(EntityManagerInterface $em, ClassMetadat continue; } - if ($assoc['isOwningSide']) { - if (0 !== ($assoc['type'] & ClassMetadata::TO_ONE) - && isset($assoc['sourceToTargetKeyColumns'])) { + if (self::isOwningSide($assoc)) { + if (self::isToOneOwningSide($assoc)) { $data = $entityData[$field] ?? null; $relatedId = []; @@ -561,11 +538,9 @@ private function saveRevisionEntityData(EntityManagerInterface $em, ClassMetadat $relatedId = $uow->getEntityIdentifier($data); } - /** @var class-string $targetEntity */ - $targetEntity = $assoc['targetEntity']; - $targetClass = $em->getClassMetadata($targetEntity); + $targetClass = $em->getClassMetadata(self::getMappingTargetEntityValue($assoc)); - foreach ($assoc['sourceToTargetKeyColumns'] as $sourceColumn => $targetColumn) { + foreach (self::getSourceToTargetKeyColumns($assoc) as $sourceColumn => $targetColumn) { $fields[$sourceColumn] = true; if (null === $data) { $params[] = null; @@ -575,9 +550,8 @@ private function saveRevisionEntityData(EntityManagerInterface $em, ClassMetadat $types[] = $targetClass->getTypeOfField($targetClass->getFieldForColumn($targetColumn)); } } - } elseif (($assoc['type'] & ClassMetadata::MANY_TO_MANY) > 0 - && isset($assoc['relationToSourceKeyColumns'], $assoc['relationToTargetKeyColumns'])) { - $targetClass = $em->getClassMetadata($assoc['targetEntity']); + } elseif (self::isManyToManyOwningSideMapping($assoc)) { + $targetClass = $em->getClassMetadata(self::getMappingTargetEntityValue($assoc)); $collection = $entityData[$assoc['fieldName']]; if (null !== $collection) { @@ -622,7 +596,7 @@ private function saveRevisionEntityData(EntityManagerInterface $em, ClassMetadat && $class->name === $class->rootEntityName && null !== $class->discriminatorColumn ) { - $params[] = $entityData[self::getMappingValue($class->discriminatorColumn, 'name')]; + $params[] = $entityData[self::getMappingNameValue($class->discriminatorColumn)]; $types[] = self::getMappingValue($class->discriminatorColumn, 'type'); } @@ -630,11 +604,10 @@ private function saveRevisionEntityData(EntityManagerInterface $em, ClassMetadat $class->isInheritanceTypeJoined() && $class->name !== $class->rootEntityName && null !== $class->discriminatorColumn ) { - $entityData[self::getMappingValue($class->discriminatorColumn, 'name')] = $class->discriminatorValue; + $entityData[self::getMappingNameValue($class->discriminatorColumn)] = $class->discriminatorValue; $this->saveRevisionEntityData( $em, $em->getClassMetadata($class->rootEntityName), - /* @phpstan-ignore argument.type */ $entityData, $revType ); @@ -660,7 +633,6 @@ private function recordRevisionForManyToManyEntity( EntityManagerInterface $em, string $revType, array $entityData, - /* @phpstan-ignore-next-line */ array|ManyToManyOwningSideMapping $assoc, ClassMetadata $class, ClassMetadata $targetClass @@ -669,14 +641,12 @@ private function recordRevisionForManyToManyEntity( $joinTableParams = [$this->getRevisionId($conn), $revType]; $joinTableTypes = [\PDO::PARAM_INT, \PDO::PARAM_STR]; - /* @phpstan-ignore argument.type */ - foreach (self::getMappingValue($assoc, 'relationToSourceKeyColumns') as $targetColumn) { + foreach (self::getRelationToSourceKeyColumns($assoc) as $targetColumn) { $joinTableParams[] = $entityData[$class->fieldNames[$targetColumn]]; $joinTableTypes[] = PersisterHelper::getTypeOfColumn($targetColumn, $class, $em); } - /* @phpstan-ignore argument.type */ - foreach (self::getMappingValue($assoc, 'relationToTargetKeyColumns') as $targetColumn) { + foreach (self::getRelationToTargetKeyColumns($assoc) as $targetColumn) { $reflField = $targetClass->reflFields[$targetClass->fieldNames[$targetColumn]]; \assert(null !== $reflField); $joinTableParams[] = $reflField->getValue($relatedEntity); @@ -740,7 +710,7 @@ private function prepareUpdateData(EntityManagerInterface $em, EntityPersister $ $newVal = $change[1]; if (!isset($classMetadata->associationMappings[$field])) { - $columnName = self::getMappingValue($classMetadata->fieldMappings[$field], 'columnName'); + $columnName = self::getMappingColumnNameValue($classMetadata->fieldMappings[$field]); $result[$persister->getOwningTable($field)][$columnName] = $newVal; continue; @@ -749,10 +719,7 @@ private function prepareUpdateData(EntityManagerInterface $em, EntityPersister $ $assoc = $classMetadata->associationMappings[$field]; // Only owning side of x-1 associations can have a FK column. - if ( - 0 === ($assoc['type'] & ClassMetadata::TO_ONE) - || false === $assoc['isOwningSide'] - || !isset($assoc['joinColumns'])) { + if (!self::isToOneOwningSide($assoc)) { continue; } @@ -772,13 +739,11 @@ private function prepareUpdateData(EntityManagerInterface $em, EntityPersister $ $newValId = $uow->getEntityIdentifier($newVal); } - /** @var class-string $targetEntity */ - $targetEntity = $assoc['targetEntity']; - $targetClass = $em->getClassMetadata($targetEntity); + $targetClass = $em->getClassMetadata(self::getMappingTargetEntityValue($assoc)); $owningTable = $persister->getOwningTable($field); foreach ($assoc['joinColumns'] as $joinColumn) { - $sourceColumn = self::getMappingValue($joinColumn, 'name'); + $sourceColumn = self::getMappingNameValue($joinColumn); $targetColumn = self::getMappingValue($joinColumn, 'referencedColumnName'); $result[$owningTable][$sourceColumn] = null !== $newValId diff --git a/src/Utils/ORMCompatibilityTrait.php b/src/Utils/ORMCompatibilityTrait.php index b770a403..f117b30c 100644 --- a/src/Utils/ORMCompatibilityTrait.php +++ b/src/Utils/ORMCompatibilityTrait.php @@ -29,12 +29,9 @@ trait ORMCompatibilityTrait { /** * @param array|AssociationMapping|EmbeddedClassMapping|FieldMapping|JoinColumnMapping|DiscriminatorColumnMapping $mapping - * - * @phpstan-ignore-next-line */ - private static function getMappingValue(array|AssociationMapping|EmbeddedClassMapping|FieldMapping|JoinColumnMapping|DiscriminatorColumnMapping $mapping, string $key): mixed + final protected static function getMappingValue(array|AssociationMapping|EmbeddedClassMapping|FieldMapping|JoinColumnMapping|DiscriminatorColumnMapping $mapping, string $key): mixed { - /* @phpstan-ignore-next-line */ if ($mapping instanceof AssociationMapping || $mapping instanceof EmbeddedClassMapping || $mapping instanceof FieldMapping || $mapping instanceof JoinColumnMapping || $mapping instanceof DiscriminatorColumnMapping) { /* @phpstan-ignore property.dynamicName */ return $mapping->$key; @@ -44,52 +41,195 @@ private static function getMappingValue(array|AssociationMapping|EmbeddedClassMa } /** - * @param array|ManyToManyOwningSideMapping $mapping + * @param array|AssociationMapping|FieldMapping|DiscriminatorColumnMapping $mapping + * + * @return literal-string + */ + final protected static function getMappingFieldNameValue(array|AssociationMapping|EmbeddedClassMapping|FieldMapping|DiscriminatorColumnMapping $mapping): string + { + if ($mapping instanceof AssociationMapping || $mapping instanceof FieldMapping || $mapping instanceof DiscriminatorColumnMapping) { + /* @phpstan-ignore return.type */ + return $mapping->fieldName; + } + + /* @phpstan-ignore return.type */ + return $mapping['fieldName']; + } + + /** + * @param array|JoinColumnMapping|DiscriminatorColumnMapping $mapping + * + * @return literal-string + */ + final protected static function getMappingNameValue(array|JoinColumnMapping|DiscriminatorColumnMapping $mapping): string + { + if ($mapping instanceof JoinColumnMapping || $mapping instanceof DiscriminatorColumnMapping) { + /* @phpstan-ignore return.type */ + return $mapping->name; + } + + /* @phpstan-ignore return.type */ + return $mapping['name']; + } + + /** + * @param array|FieldMapping $mapping * * @return literal-string + */ + final protected static function getMappingColumnNameValue(array|FieldMapping $mapping): string + { + if ($mapping instanceof FieldMapping) { + /* @phpstan-ignore return.type */ + return $mapping->columnName; + } + + /* @phpstan-ignore return.type */ + return $mapping['columnName']; + } + + /** + * @param array|ManyToManyOwningSideMapping $mapping * - * @phpstan-ignore-next-line + * @return literal-string */ - private static function getJoinTableName(array|ManyToManyOwningSideMapping $mapping): string + final protected static function getMappingJoinTableNameValue(array|ManyToManyOwningSideMapping $mapping): string { - /* @phpstan-ignore-next-line */ if ($mapping instanceof ManyToManyOwningSideMapping) { - /* @phpstan-ignore-next-line */ + /* @phpstan-ignore return.type */ return $mapping->joinTable->name; } - /* @phpstan-ignore-next-line */ + /* @phpstan-ignore return.type */ return $mapping['joinTable']['name']; } /** * @param array|AssociationMapping $mapping + * + * @phpstan-assert-if-true ManyToManyOwningSideMapping $mapping */ - private static function isManyToManyOwningSideMapping(array|AssociationMapping $mapping): bool + final protected static function isManyToManyOwningSideMapping(array|AssociationMapping $mapping): bool { - /* @phpstan-ignore-next-line */ if ($mapping instanceof AssociationMapping) { - /* @phpstan-ignore-next-line */ return $mapping instanceof ManyToManyOwningSideMapping; } - /* @phpstan-ignore-next-line */ - return $mapping['isOwningSide'] && isset($mapping['joinTable']['name']); + return isset($mapping['joinTable']['name'], $mapping['relationToSourceKeyColumns'], $mapping['relationToTargetKeyColumns']) + && true === $mapping['isOwningSide'] + && ($mapping['type'] & ClassMetadata::MANY_TO_MANY) > 0; } /** * @param array|AssociationMapping $mapping + * + * @phpstan-assert-if-true ToOneOwningSideMapping $mapping */ - private static function isToOneOwningSide(array|AssociationMapping $mapping): bool + final protected static function isToOneOwningSide(array|AssociationMapping $mapping): bool { - /* @phpstan-ignore class.notFound */ if ($mapping instanceof AssociationMapping) { - /* @phpstan-ignore class.notFound */ - return $mapping instanceof ToOneOwningSideMapping; + return $mapping->isToOneOwningSide(); } return ($mapping['type'] & ClassMetadata::TO_ONE) > 0 && true === $mapping['isOwningSide'] && isset($mapping['targetToSourceKeyColumns']); } + + /** + * @param array|AssociationMapping $mapping + */ + final protected static function isToOne(array|AssociationMapping $mapping): bool + { + if ($mapping instanceof AssociationMapping) { + return $mapping->isToOne(); + } + + return ($mapping['type'] & ClassMetadata::TO_ONE) > 0; + } + + /** + * @param array|ToOneOwningSideMapping $mapping + * + * @return array + */ + final protected static function getTargetToSourceKeyColumns(array|ToOneOwningSideMapping $mapping): array + { + if ($mapping instanceof ToOneOwningSideMapping) { + /* @phpstan-ignore return.type */ + return $mapping->targetToSourceKeyColumns; + } + + return $mapping['targetToSourceKeyColumns']; + } + + /** + * @param array|ToOneOwningSideMapping $mapping + * + * @return array + */ + final protected static function getSourceToTargetKeyColumns(array|ToOneOwningSideMapping $mapping): array + { + if ($mapping instanceof ToOneOwningSideMapping) { + return $mapping->sourceToTargetKeyColumns; + } + + return $mapping['sourceToTargetKeyColumns']; + } + + /** + * @param array|ManyToManyOwningSideMapping $mapping + * + * @return array + */ + final protected static function getRelationToSourceKeyColumns(array|ManyToManyOwningSideMapping $mapping): array + { + if ($mapping instanceof ManyToManyOwningSideMapping) { + /* @phpstan-ignore return.type */ + return $mapping->relationToSourceKeyColumns; + } + + return $mapping['relationToSourceKeyColumns']; + } + + /** + * @param array|ManyToManyOwningSideMapping $mapping + * + * @return array + */ + final protected static function getRelationToTargetKeyColumns(array|ManyToManyOwningSideMapping $mapping): array + { + if ($mapping instanceof ManyToManyOwningSideMapping) { + /* @phpstan-ignore return.type */ + return $mapping->relationToTargetKeyColumns; + } + + return $mapping['relationToTargetKeyColumns']; + } + + /** + * @param array|AssociationMapping $mapping + * + * @phpstan-return class-string + */ + final protected static function getMappingTargetEntityValue(array|AssociationMapping $mapping): string + { + if ($mapping instanceof AssociationMapping) { + return $mapping->targetEntity; + } + + return $mapping['targetEntity']; + } + + /** + * @param array|AssociationMapping $mapping + */ + final protected static function isOwningSide(array|AssociationMapping $mapping): bool + { + if ($mapping instanceof AssociationMapping) { + return $mapping->isOwningSide(); + } + + return true === $mapping['isOwningSide']; + } } From c21a8b2509d049ce77f2e9d528b294166bd303eb Mon Sep 17 00:00:00 2001 From: Fran Moreno Date: Mon, 24 Jun 2024 20:28:33 +0200 Subject: [PATCH 08/10] Simplify checks --- src/Utils/ORMCompatibilityTrait.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Utils/ORMCompatibilityTrait.php b/src/Utils/ORMCompatibilityTrait.php index f117b30c..d97f548a 100644 --- a/src/Utils/ORMCompatibilityTrait.php +++ b/src/Utils/ORMCompatibilityTrait.php @@ -115,8 +115,7 @@ final protected static function isManyToManyOwningSideMapping(array|AssociationM return $mapping instanceof ManyToManyOwningSideMapping; } - return isset($mapping['joinTable']['name'], $mapping['relationToSourceKeyColumns'], $mapping['relationToTargetKeyColumns']) - && true === $mapping['isOwningSide'] + return true === $mapping['isOwningSide'] && ($mapping['type'] & ClassMetadata::MANY_TO_MANY) > 0; } @@ -132,8 +131,7 @@ final protected static function isToOneOwningSide(array|AssociationMapping $mapp } return ($mapping['type'] & ClassMetadata::TO_ONE) > 0 - && true === $mapping['isOwningSide'] - && isset($mapping['targetToSourceKeyColumns']); + && true === $mapping['isOwningSide']; } /** From 2510acfda89ba5c822beec325a80a886770ac948 Mon Sep 17 00:00:00 2001 From: Fran Moreno Date: Mon, 24 Jun 2024 20:28:47 +0200 Subject: [PATCH 09/10] Removed unneeded job --- .github/workflows/test-orm-3.yaml | 63 ------------------------------- 1 file changed, 63 deletions(-) delete mode 100644 .github/workflows/test-orm-3.yaml diff --git a/.github/workflows/test-orm-3.yaml b/.github/workflows/test-orm-3.yaml deleted file mode 100644 index 34f36dd1..00000000 --- a/.github/workflows/test-orm-3.yaml +++ /dev/null @@ -1,63 +0,0 @@ -# TEMPORARILY USED until AuditBundle is compatible with ORM 3 - -name: Test - -on: - pull_request: - -permissions: - contents: read - -jobs: - test: - name: PHP ${{ matrix.php-version }} + ${{ matrix.dependencies }} + ${{ matrix.variant }} (ORM 3) - - runs-on: ubuntu-latest - - continue-on-error: ${{ matrix.allowed-to-fail }} - - env: - SYMFONY_REQUIRE: ${{matrix.symfony-require}} - - strategy: - matrix: - include: - - php-version: '8.3' - dependencies: highest - allowed-to-fail: false - symfony-require: 7.0.* - variant: symfony/symfony:"7.0.*" - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Install PHP with extensions - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-version }} - coverage: pcov - tools: composer:v2, flex - - - name: Add PHPUnit matcher - run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" - - - name: Use gedmo-extension main branch - run: composer require "gedmo/doctrine-extensions":"dev-main" --dev --no-update - - - name: Install variant - if: matrix.variant != 'normal' && !startsWith(matrix.variant, 'symfony/symfony') - run: composer require ${{ matrix.variant }} --no-update - - - name: Install Composer dependencies (${{ matrix.dependencies }}) - uses: ramsey/composer-install@v2 - with: - dependency-versions: ${{ matrix.dependencies }} - - - name: Run Tests with coverage - run: make coverage - - - name: Send coverage to Codecov - uses: codecov/codecov-action@v3 - with: - files: build/logs/clover.xml From 3c45b4ca12da9dea5ab383ddc906b6be437c5e24 Mon Sep 17 00:00:00 2001 From: Fran Moreno Date: Mon, 24 Jun 2024 21:11:45 +0200 Subject: [PATCH 10/10] Add isManyToMany and remove deprecated exception --- src/AuditReader.php | 28 +--------------------------- src/Utils/ORMCompatibilityTrait.php | 20 +++++++++++++++----- 2 files changed, 16 insertions(+), 32 deletions(-) diff --git a/src/AuditReader.php b/src/AuditReader.php index 57ea5aaa..f5e919e4 100644 --- a/src/AuditReader.php +++ b/src/AuditReader.php @@ -22,7 +22,6 @@ use Doctrine\ORM\Exception\ORMException; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\QuoteStrategy; -use Doctrine\ORM\ORMException as ORM2Exception; use Doctrine\ORM\PersistentCollection; use Doctrine\ORM\Persisters\Entity\EntityPersister; use SimpleThings\EntityAudit\Collection\AuditedCollection; @@ -190,17 +189,12 @@ public function clearEntityCache(): void * @throws NotAuditedException * @throws Exception * @throws ORMException - * @throws ORM2Exception * @throws \RuntimeException * * @return object|null * - * @psalm-suppress UndefinedDocblockClass - * * @phpstan-param class-string $className * @phpstan-return T|null - * - * @phpstan-ignore throws.notThrowable */ public function find($className, $id, $revision, array $options = []) { @@ -389,15 +383,10 @@ public function findEntitesChangedAtRevision($revision) * @throws NotAuditedException * @throws Exception * @throws ORMException - * @throws ORM2Exception * @throws \RuntimeException * @throws DeletedException * * @return ChangedEntity[] - * - * @psalm-suppress UndefinedDocblockClass - * - * @phpstan-ignore throws.notThrowable */ public function findEntitiesChangedAtRevision($revision) { @@ -648,17 +637,12 @@ public function getCurrentRevision($className, $id) * @throws NotAuditedException * @throws Exception * @throws ORMException - * @throws ORM2Exception * @throws \RuntimeException * * @return array> * - * @psalm-suppress UndefinedDocblockClass - * * @phpstan-param class-string $className * @phpstan-return array - * - * @phpstan-ignore throws.notThrowable */ public function diff($className, $id, $oldRevision, $newRevision) { @@ -706,17 +690,12 @@ public function getEntityValues($className, $entity) * @throws NotAuditedException * @throws Exception * @throws ORMException - * @throws ORM2Exception * @throws DeletedException * * @return array * - * @psalm-suppress UndefinedDocblockClass - * * @phpstan-param class-string $className * @phpstan-return array - * - * @phpstan-ignore throws.notThrowable */ public function getEntityHistory($className, $id) { @@ -823,17 +802,12 @@ protected function getEntityPersister($className) * @throws NotAuditedException * @throws Exception * @throws ORMException - * @throws ORM2Exception * @throws \RuntimeException * * @return object * - * @psalm-suppress UndefinedDocblockClass - * * @phpstan-param class-string $className * @phpstan-return T - * - * @phpstan-ignore throws.notThrowable */ private function createEntity($className, array $columnMap, array $data, $revision) { @@ -1041,7 +1015,7 @@ private function createEntity($className, array $columnMap, array $data, $revisi $reflField = $classMetadata->reflFields[$assoc['fieldName']]; \assert(null !== $reflField); $reflField->setValue($entity, $collection); - } elseif (0 !== ($assoc['type'] & ClassMetadata::MANY_TO_MANY)) { + } elseif (self::isManyToMany($assoc)) { if (self::isManyToManyOwningSideMapping($assoc)) { $whereId = [$this->config->getRevisionFieldName().' = ?']; $values = [$revision]; diff --git a/src/Utils/ORMCompatibilityTrait.php b/src/Utils/ORMCompatibilityTrait.php index d97f548a..2c9b2539 100644 --- a/src/Utils/ORMCompatibilityTrait.php +++ b/src/Utils/ORMCompatibilityTrait.php @@ -112,11 +112,10 @@ final protected static function getMappingJoinTableNameValue(array|ManyToManyOwn final protected static function isManyToManyOwningSideMapping(array|AssociationMapping $mapping): bool { if ($mapping instanceof AssociationMapping) { - return $mapping instanceof ManyToManyOwningSideMapping; + return $mapping->isManyToMany() && $mapping->isOwningSide(); } - return true === $mapping['isOwningSide'] - && ($mapping['type'] & ClassMetadata::MANY_TO_MANY) > 0; + return true === $mapping['isOwningSide'] && ($mapping['type'] & ClassMetadata::MANY_TO_MANY) > 0; } /** @@ -130,8 +129,7 @@ final protected static function isToOneOwningSide(array|AssociationMapping $mapp return $mapping->isToOneOwningSide(); } - return ($mapping['type'] & ClassMetadata::TO_ONE) > 0 - && true === $mapping['isOwningSide']; + return ($mapping['type'] & ClassMetadata::TO_ONE) > 0 && true === $mapping['isOwningSide']; } /** @@ -146,6 +144,18 @@ final protected static function isToOne(array|AssociationMapping $mapping): bool return ($mapping['type'] & ClassMetadata::TO_ONE) > 0; } + /** + * @param array|AssociationMapping $mapping + */ + final protected static function isManyToMany(array|AssociationMapping $mapping): bool + { + if ($mapping instanceof AssociationMapping) { + return $mapping->isManyToMany(); + } + + return ($mapping['type'] & ClassMetadata::MANY_TO_MANY) > 0; + } + /** * @param array|ToOneOwningSideMapping $mapping *