diff --git a/composer.json b/composer.json index cfd47c2..5cb6a63 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "require": { "php": ">=8.2", "doctrine/dbal": "^2.13.1|^3.2", - "doctrine/orm": "^2.13", + "doctrine/orm": "^2.13|^3.2", "symfony/cache": "^5.4|^6.0|^7.0", "symfony/event-dispatcher": "^5.4|^6.0|^7.0", "symfony/lock": "^5.4|^6.0|^7.0", @@ -40,7 +40,7 @@ }, "scripts": { "test": "XDEBUG_MODE=coverage ./vendor/bin/phpunit --colors=always", - "cs-fix": "tools/php-cs-fixer/vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.php --using-cache=no --verbose --ansi", + "cs-fix": "tools/php-cs-fixer/vendor/bin/php-cs-fixer fix --using-cache=no --verbose --ansi", "cs-check": "tools/php-cs-fixer/vendor/bin/php-cs-fixer fix --using-cache=no --verbose --ansi --dry-run", "phpstan": "tools/phpstan/vendor/bin/phpstan --memory-limit=1G --ansi analyse src", "rector": "tools/rector/vendor/bin/rector", diff --git a/rector.php b/rector.php index 3b0a74e..ff0244c 100644 --- a/rector.php +++ b/rector.php @@ -27,6 +27,7 @@ SymfonySetList::SYMFONY_54, PHPUnitSetList::PHPUNIT_110, DoctrineSetList::DOCTRINE_CODE_QUALITY, + DoctrineSetList::DOCTRINE_ORM_29, DoctrineSetList::DOCTRINE_DBAL_40, ]) ->withAttributesSets( diff --git a/src/Provider/Doctrine/Auditing/Event/DoctrineSubscriber.php b/src/Provider/Doctrine/Auditing/Event/DoctrineSubscriber.php index d61e26a..9f3fdd3 100644 --- a/src/Provider/Doctrine/Auditing/Event/DoctrineSubscriber.php +++ b/src/Provider/Doctrine/Auditing/Event/DoctrineSubscriber.php @@ -10,6 +10,7 @@ use Doctrine\Common\EventSubscriber; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware; +use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Event\OnFlushEventArgs; use Doctrine\ORM\Events; @@ -18,7 +19,10 @@ final class DoctrineSubscriber implements EventSubscriber /** @var Transaction[] */ private array $transactions = []; - public function __construct(private readonly TransactionManagerInterface $transactionManager) {} + public function __construct( + private readonly TransactionManagerInterface $transactionManager, + private readonly EntityManagerInterface $entityManager + ) {} /** * It is called inside EntityManager#flush() after the changes to all the managed entities @@ -28,16 +32,15 @@ public function __construct(private readonly TransactionManagerInterface $transa */ public function onFlush(OnFlushEventArgs $args): void { - $entityManager = $args->getObjectManager(); - $entityManagerId = spl_object_id($entityManager); + $entityManagerId = spl_object_id($this->entityManager); // cached transaction model, if it holds same EM no need to create a new one - $transaction = ($this->transactions[$entityManagerId] ??= new Transaction($entityManager)); + $transaction = ($this->transactions[$entityManagerId] ??= new Transaction($this->entityManager)); // Populate transaction $this->transactionManager->populate($transaction); - $driver = $entityManager->getConnection()->getDriver(); + $driver = $this->entityManager->getConnection()->getDriver(); if (!$driver instanceof DHDriver) { $driver = $this->getWrappedDriver($driver); diff --git a/src/Provider/Doctrine/Auditing/Transaction/AuditTrait.php b/src/Provider/Doctrine/Auditing/Transaction/AuditTrait.php index 5a34226..30f4507 100644 --- a/src/Provider/Doctrine/Auditing/Transaction/AuditTrait.php +++ b/src/Provider/Doctrine/Auditing/Transaction/AuditTrait.php @@ -33,27 +33,35 @@ private function id(EntityManagerInterface $entityManager, object $entity): mixe } if (isset($meta->fieldMappings[$pk])) { + \assert(\is_string($meta->fieldMappings[$pk]['type'])); $type = Type::getType($meta->fieldMappings[$pk]['type']); + \assert(\is_object($meta->getReflectionProperty($pk))); + return $this->value($entityManager, $type, $meta->getReflectionProperty($pk)->getValue($entity)); } - /** + /* * Primary key is not part of fieldMapping. * * @see https://github.com/DamienHarper/auditor-bundle/issues/40 * @see https://www.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/composite-primary-keys.html#identity-through-foreign-entities * We try to get it from associationMapping (will throw a MappingException if not available) */ + \assert(\is_object($meta->getReflectionProperty($pk))); $targetEntity = $meta->getReflectionProperty($pk)->getValue($entity); $mapping = $meta->getAssociationMapping($pk); + \assert(\is_string($mapping['targetEntity'])); $meta = $entityManager->getClassMetadata($mapping['targetEntity']); $pk = $meta->getSingleIdentifierFieldName(); + + \assert(\is_string($meta->fieldMappings[$pk]['type'])); $type = Type::getType($meta->fieldMappings[$pk]['type']); \assert(\is_object($targetEntity)); + \assert(\is_object($meta->getReflectionProperty($pk))); return $this->value($entityManager, $type, $meta->getReflectionProperty($pk)->getValue($targetEntity)); } @@ -151,6 +159,7 @@ private function diff(EntityManagerInterface $entityManager, object $entity, arr && $this->provider->isAuditedField($entity, $fieldName) ) { $mapping = $meta->fieldMappings[$fieldName]; + \assert(\is_string($mapping['type'])); $type = Type::getType($mapping['type']); $o = $this->value($entityManager, $type, $old); $n = $this->value($entityManager, $type, $new); diff --git a/src/Provider/Doctrine/Auditing/Transaction/TransactionHydrator.php b/src/Provider/Doctrine/Auditing/Transaction/TransactionHydrator.php index 9f6ba47..77d7592 100644 --- a/src/Provider/Doctrine/Auditing/Transaction/TransactionHydrator.php +++ b/src/Provider/Doctrine/Auditing/Transaction/TransactionHydrator.php @@ -9,6 +9,7 @@ use DH\Auditor\Provider\Doctrine\Model\Transaction; use DH\Auditor\Transaction\TransactionHydratorInterface; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Mapping\AssociationMapping; use Doctrine\ORM\PersistentCollection; final class TransactionHydrator implements TransactionHydratorInterface @@ -86,8 +87,9 @@ private function hydrateWithScheduledCollectionUpdates(Transaction $transaction, if (null !== $owner && $this->provider->isAudited($owner)) { $mapping = $collection->getMapping(); - if (!\is_array($mapping)) { - continue; + // TODO: backward compatibility code until we drop doctrine/orm <3.0 + if ($mapping instanceof AssociationMapping) { + $mapping = $mapping->toArray(); } /** @var object $entity */ @@ -126,8 +128,9 @@ private function hydrateWithScheduledCollectionDeletions(Transaction $transactio if (null !== $owner && $this->provider->isAudited($owner)) { $mapping = $collection->getMapping(); - if (!\is_array($mapping)) { - continue; + // TODO: backward compatibility code until we drop doctrine/orm <3.0 + if ($mapping instanceof AssociationMapping) { + $mapping = $mapping->toArray(); } /** @var object $entity */ diff --git a/src/Provider/Doctrine/DoctrineProvider.php b/src/Provider/Doctrine/DoctrineProvider.php index 267c101..8120750 100644 --- a/src/Provider/Doctrine/DoctrineProvider.php +++ b/src/Provider/Doctrine/DoctrineProvider.php @@ -70,7 +70,7 @@ public function registerAuditingService(AuditingServiceInterface $service): Prov // Register subscribers $evm->addEventListener([Events::loadClassMetadata], new TableSchemaListener($this)); $evm->addEventListener([ToolEvents::postGenerateSchemaTable], new CreateSchemaListener($this)); - $evm->addEventSubscriber(new DoctrineSubscriber($this->transactionManager)); + $evm->addEventSubscriber(new DoctrineSubscriber($this->transactionManager, $entityManager)); return $this; } diff --git a/src/Provider/Doctrine/Persistence/Event/CreateSchemaListener.php b/src/Provider/Doctrine/Persistence/Event/CreateSchemaListener.php index 80e39de..ec87ee4 100644 --- a/src/Provider/Doctrine/Persistence/Event/CreateSchemaListener.php +++ b/src/Provider/Doctrine/Persistence/Event/CreateSchemaListener.php @@ -9,7 +9,7 @@ use DH\Auditor\Provider\Doctrine\Service\AuditingService; use DH\Auditor\Provider\Doctrine\Service\StorageService; use DH\Auditor\Tests\Provider\Doctrine\Persistence\Event\CreateSchemaListenerTest; -use Doctrine\ORM\Mapping\ClassMetadataInfo; +use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Tools\Event\GenerateSchemaTableEventArgs; /** @@ -25,9 +25,9 @@ public function postGenerateSchemaTable(GenerateSchemaTableEventArgs $eventArgs) // check inheritance type and returns if unsupported if (!\in_array($metadata->inheritanceType, [ - ClassMetadataInfo::INHERITANCE_TYPE_NONE, - ClassMetadataInfo::INHERITANCE_TYPE_JOINED, - ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_TABLE, + 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', $metadata->inheritanceType)); } @@ -38,7 +38,7 @@ public function postGenerateSchemaTable(GenerateSchemaTableEventArgs $eventArgs) $audited = false; if ( $metadata->rootEntityName === $metadata->name - && ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_TABLE === $metadata->inheritanceType + && ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE === $metadata->inheritanceType ) { foreach ($metadata->subClasses as $subClass) { if ($this->provider->isAuditable($subClass)) { diff --git a/tests/Provider/Doctrine/Event/DoctrineSubscriberTest.php b/tests/Provider/Doctrine/Event/DoctrineSubscriberTest.php index 6f122ff..3efde30 100644 --- a/tests/Provider/Doctrine/Event/DoctrineSubscriberTest.php +++ b/tests/Provider/Doctrine/Event/DoctrineSubscriberTest.php @@ -62,7 +62,7 @@ public function process($transaction): void ->willReturn($driver) ; - $target = new DoctrineSubscriber($transactionManager); + $target = new DoctrineSubscriber($transactionManager, $objectManager); $target->onFlush($args); foreach ($dhDriver->getFlusherList() as $item) { @@ -117,7 +117,7 @@ public function createDatabasePlatformForVersion($version): void {} ->willReturn($driver) ; - $target = new DoctrineSubscriber($transactionManager); + $target = new DoctrineSubscriber($transactionManager, $objectManager); $target->onFlush($args); foreach ($dhDriver->getFlusherList() as $item) { @@ -171,7 +171,7 @@ public function createDatabasePlatformForVersion($version): void {} ->willReturn($configuration = $this->createMock(Configuration::class)) ; - $target = new DoctrineSubscriber($transactionManager); + $target = new DoctrineSubscriber($transactionManager, $objectManager); $target->onFlush($args); $this->assertTrue(true); diff --git a/tests/Provider/Doctrine/Traits/EntityManagerInterfaceTrait.php b/tests/Provider/Doctrine/Traits/EntityManagerInterfaceTrait.php index 044545b..484302b 100644 --- a/tests/Provider/Doctrine/Traits/EntityManagerInterfaceTrait.php +++ b/tests/Provider/Doctrine/Traits/EntityManagerInterfaceTrait.php @@ -25,7 +25,7 @@ private function createEntityManager(?array $paths = null, string $connectionNam $connection = $this->getConnection($connectionName, $params); - $em = EntityManager::create($connection, $configuration); + $em = new EntityManager($connection, $configuration); $evm = $em->getEventManager(); $allListeners = method_exists($evm, 'getAllListeners') ? $evm->getAllListeners() : $evm->getListeners(); foreach ($allListeners as $event => $listeners) {