From aeab8f30a1cc4dae34ef872e1b31b4c5c54607d3 Mon Sep 17 00:00:00 2001 From: Jan Lam Date: Mon, 15 Jun 2026 09:55:15 +0200 Subject: [PATCH 1/4] Drop PHP<8.3 support --- .github/workflows/main.yaml | 2 +- composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index ab443d6..4df289f 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -8,7 +8,7 @@ jobs: strategy: matrix: - php-versions: ['7.3', '7.4', '8.0'] + php-versions: ['8.3', '8.4'] name: PHP ${{ matrix.php-versions }} steps: - uses: actions/checkout@v2 diff --git a/composer.json b/composer.json index a9c68e6..bc1b7d1 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,7 @@ "license": "MIT", "minimum-stability": "stable", "require": { - "php": "^7.3||^8.0", + "php": "^8.3", "doctrine/orm": "^2.7.4", "hostnet/entity-tracker-component": "^2.0.1" }, From a005004e58b2e8f005a9beae80d8c206813e8ea3 Mon Sep 17 00:00:00 2001 From: Jan Lam Date: Mon, 15 Jun 2026 11:08:58 +0200 Subject: [PATCH 2/4] Attribute support --- src/Attributes/Mutation.php | 45 ++++++++++++++ src/Listener/MutationListener.php | 51 ++++++++++++++-- src/Mutation.php | 4 ++ src/MutationAwareInterface.php | 3 + src/Resolver/MutationResolver.php | 6 +- src/Resolver/MutationResolverInterface.php | 11 ++-- test/Attributes/MutationTest.php | 45 ++++++++++++++ test/Listener/MutationListenerTest.php | 55 ++++++++++++++++++ test/Mocked/MockMutationEntityAttribute.php | 58 +++++++++++++++++++ .../MockMutationEntityAttributeMutation.php | 14 +++++ 10 files changed, 278 insertions(+), 14 deletions(-) create mode 100644 src/Attributes/Mutation.php create mode 100644 test/Attributes/MutationTest.php create mode 100644 test/Mocked/MockMutationEntityAttribute.php create mode 100644 test/Mocked/MockMutationEntityAttributeMutation.php diff --git a/src/Attributes/Mutation.php b/src/Attributes/Mutation.php new file mode 100644 index 0000000..37c6968 --- /dev/null +++ b/src/Attributes/Mutation.php @@ -0,0 +1,45 @@ +strategy, [self::STRATEGY_COPY_PREVIOUS, self::STRATEGY_COPY_CURRENT], true)) { + throw new \RuntimeException( + sprintf("Unknown strategy '%s' for class %s.", $this->strategy, get_class($this)) + ); + } + + return $this->strategy; + } +} diff --git a/src/Listener/MutationListener.php b/src/Listener/MutationListener.php index 73abfce..f3128f8 100644 --- a/src/Listener/MutationListener.php +++ b/src/Listener/MutationListener.php @@ -6,7 +6,7 @@ namespace Hostnet\Component\EntityMutation\Listener; -use Hostnet\Component\EntityMutation\Mutation; +use Hostnet\Component\EntityMutation\Attributes\Mutation; use Hostnet\Component\EntityMutation\MutationAwareInterface; use Hostnet\Component\EntityMutation\Resolver\MutationResolverInterface; use Hostnet\Component\EntityTracker\Event\EntityChangedEvent; @@ -18,6 +18,11 @@ class MutationListener */ private $resolver; + /** + * Caches the class names to prevent iterating over attribute and annotations again on the next entity. + */ + private array $is_mutation_cache = []; + /** * @param MutationResolverInterface $resolver */ @@ -29,12 +34,12 @@ public function __construct(MutationResolverInterface $resolver) /** * @param EntityChangedEvent $event */ - public function entityChanged(EntityChangedEvent $event) + public function entityChanged(EntityChangedEvent $event): void { $em = $event->getEntityManager(); $entity = $event->getCurrentEntity(); - if (null === ($annotation = $this->resolver->getMutationAnnotation($em, $entity))) { + if (false === $strategy = $this->getMutationStrategy($em, $entity)) { return; } @@ -44,8 +49,6 @@ public function entityChanged(EntityChangedEvent $event) return; } - $strategy = $annotation->getStrategy(); - if ($strategy === Mutation::STRATEGY_COPY_PREVIOUS && null === $event->getOriginalEntity()) { return; } @@ -72,4 +75,42 @@ public function entityChanged(EntityChangedEvent $event) $entity->addMutation($mutation); } } + + private function getMutationStrategy($em, $entity): false|string + { + $class = get_class($entity); + if (array_key_exists($class, $this->is_mutation_cache)) { + return $this->is_mutation_cache[$class]; + } + + if (null !== $annotation = $this->resolver->getMutationAnnotation($em, $entity)) { + $this->is_mutation_cache[$class] = $annotation->getStrategy(); + + return $this->is_mutation_cache[$class]; + } + + if (null !== $strategy = $this->getMutationAttributeStrategy($entity)) { + $this->is_mutation_cache[$class] = $strategy; + + return $this->is_mutation_cache[$class]; + } + + $this->is_mutation_cache[$class] = false; + + return false; + } + + private function getMutationAttributeStrategy($entity): ?string + { + $reflection = new \ReflectionClass($entity); + $attributes = $reflection->getAttributes(Mutation::class); + + if (empty($attributes)) { + return null; + } + + /** @var Mutation $attribute */ + $attribute = $attributes[0]->newInstance(); + return $attribute->getStrategy(); + } } diff --git a/src/Mutation.php b/src/Mutation.php index f5418c8..d60a252 100644 --- a/src/Mutation.php +++ b/src/Mutation.php @@ -11,6 +11,8 @@ /** * @Annotation * @Target({"CLASS"}) + * + * @deprecated Please use the attribute instead. */ class Mutation extends Tracked { @@ -37,6 +39,8 @@ class Mutation extends Tracked * Get the strategy for storing the mutation data. * * @return Mutation::STRATEGY_COPY_PREVIOUS|Mutation::STRATEGY_COPY_CURRENT + * + * @deprecated Please use the attribute instead. */ public function getStrategy() { diff --git a/src/MutationAwareInterface.php b/src/MutationAwareInterface.php index 87e69cc..19e00c6 100644 --- a/src/MutationAwareInterface.php +++ b/src/MutationAwareInterface.php @@ -6,6 +6,9 @@ namespace Hostnet\Component\EntityMutation; +/** + * TODO: add typehints on next BC break, removing doctrine/annotations + */ interface MutationAwareInterface { /** diff --git a/src/Resolver/MutationResolver.php b/src/Resolver/MutationResolver.php index 06b1963..2e08f1c 100644 --- a/src/Resolver/MutationResolver.php +++ b/src/Resolver/MutationResolver.php @@ -33,7 +33,7 @@ public function __construct(EntityAnnotationMetadataProvider $provider) /** * {@inheritdoc} */ - public function getMutationAnnotation(EntityManagerInterface $em, $entity) + public function getMutationAnnotation(EntityManagerInterface $em, $entity): ?Mutation { return $this->provider->getAnnotationFromEntity($em, $entity, $this->annotation); } @@ -41,7 +41,7 @@ public function getMutationAnnotation(EntityManagerInterface $em, $entity) /** * {@inheritdoc} */ - public function getMutationClassName(EntityManagerInterface $em, $entity) + public function getMutationClassName(EntityManagerInterface $em, $entity): ?string { if (null === ($annotation = $this->getMutationAnnotation($em, $entity))) { return null; @@ -53,7 +53,7 @@ public function getMutationClassName(EntityManagerInterface $em, $entity) /** * {@inheritdoc} */ - public function getMutatableFields(EntityManagerInterface $em, $entity) + public function getMutatableFields(EntityManagerInterface $em, $entity): array { $mutation_class = $this->getMutationClassName($em, $entity); $metadata = $em->getClassMetadata(get_class($entity)); diff --git a/src/Resolver/MutationResolverInterface.php b/src/Resolver/MutationResolverInterface.php index 687bdec..3eb1946 100644 --- a/src/Resolver/MutationResolverInterface.php +++ b/src/Resolver/MutationResolverInterface.php @@ -14,21 +14,20 @@ interface MutationResolverInterface /** * Return the mutation annotation * - * @return Mutation + * + * @deprecated Please use the attribute instead. */ - public function getMutationAnnotation(EntityManagerInterface $em, $entity); + public function getMutationAnnotation(EntityManagerInterface $em, $entity): ?Mutation; /** * Return the mutation class name - * - * @return string */ - public function getMutationClassName(EntityManagerInterface $em, $entity); + public function getMutationClassName(EntityManagerInterface $em, $entity): ?string; /** * Return list of mutatable fields * * @return string[] */ - public function getMutatableFields(EntityManagerInterface $em, $entity); + public function getMutatableFields(EntityManagerInterface $em, $entity): array; } diff --git a/test/Attributes/MutationTest.php b/test/Attributes/MutationTest.php new file mode 100644 index 0000000..6621edb --- /dev/null +++ b/test/Attributes/MutationTest.php @@ -0,0 +1,45 @@ +assertEquals(Mutation::STRATEGY_COPY_PREVIOUS, $mutation->getStrategy()); + $mutation->strategy = Mutation::STRATEGY_COPY_CURRENT; + $this->assertEquals(Mutation::STRATEGY_COPY_CURRENT, $mutation->getStrategy()); + } + + /** + * @dataProvider getStrategyExceptionProvider + * @param mixed $strategy + */ + public function testGetStrategyException($strategy): void + { + $mutation = new Mutation(); + $mutation->strategy = $strategy; + $this->expectException(\RuntimeException::class); + $mutation->getStrategy(); + } + + public function getStrategyExceptionProvider(): iterable + { + return [ + [''], + ['test'], + ['last'], + ['before'], + ]; + } +} diff --git a/test/Listener/MutationListenerTest.php b/test/Listener/MutationListenerTest.php index 5e66461..845034c 100644 --- a/test/Listener/MutationListenerTest.php +++ b/test/Listener/MutationListenerTest.php @@ -9,6 +9,8 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\Mapping\ClassMetadata; use Hostnet\Component\EntityMutation\Mocked\MockMutationEntity; +use Hostnet\Component\EntityMutation\Mocked\MockMutationEntityAttribute; +use Hostnet\Component\EntityMutation\Mocked\MockMutationEntityAttributeMutation; use Hostnet\Component\EntityMutation\Mocked\MockMutationEntityMutation; use Hostnet\Component\EntityMutation\Mutation; use Hostnet\Component\EntityMutation\Resolver\MutationResolverInterface; @@ -269,4 +271,57 @@ public function testOnEntityChangedNoAnnotation(): void $this->assertCount(0, $current_entity->getMutations()); } + + public function testOnEntityChangedWithAttribute(): void + { + $current_entity = new MockMutationEntityAttribute(); + $original_entity = new MockMutationEntityAttribute(); + + $current_entity->id = 2; + $original_entity->id = 1; + + $mutated_fields = ['id']; + + $this->resolver + ->expects($this->exactly(2)) + ->method('getMutatableFields') + ->with($this->em, $current_entity) + ->willReturn(['id']); + + $this->resolver + ->expects($this->once()) + ->method('getMutationAnnotation') + ->with($this->em, $current_entity) + ->willReturn(null); + + $this->resolver + ->expects($this->exactly(2)) + ->method('getMutationClassName') + ->with($this->em, $current_entity) + ->willReturn(get_class($current_entity) . 'Mutation'); + + $mutation_meta = $this->createMock(ClassMetadata::class); + $mutation_meta + ->expects($this->any()) + ->method('getReflectionClass') + ->willReturn(new \ReflectionClass(get_class($current_entity) . 'Mutation')); + + $this->em + ->expects($this->exactly(2)) + ->method('getClassMetadata') + ->with(get_class($current_entity) . 'Mutation') + ->willReturn($mutation_meta); + + $this->em + ->expects($this->exactly(2)) + ->method('persist') + ->with($this->isInstanceOf(get_class($current_entity) . 'Mutation')); + + $event = new EntityChangedEvent($this->em, $current_entity, $original_entity, $mutated_fields); + $this->listener->entityChanged($event); + + $this->assertTrue(current($current_entity->getMutations()) instanceof MockMutationEntityAttributeMutation); + + $this->listener->entityChanged($event); // covers getting the attribute from the cache, submits a 2nd mutation. + } } diff --git a/test/Mocked/MockMutationEntityAttribute.php b/test/Mocked/MockMutationEntityAttribute.php new file mode 100644 index 0000000..0d5cc2e --- /dev/null +++ b/test/Mocked/MockMutationEntityAttribute.php @@ -0,0 +1,58 @@ +mutations[] = $mutation; + } + + /** + * @see \Hostnet\Component\EntityMutation\MutationAwareInterface::getMutations() + */ + public function getMutations() + { + return $this->mutations; + } + + /** + * @see \Hostnet\Component\EntityMutation\MutationAwareInterface::getPreviousMutation() + */ + public function getPreviousMutation() + { + return current($this->mutations) ?: null; + } +} diff --git a/test/Mocked/MockMutationEntityAttributeMutation.php b/test/Mocked/MockMutationEntityAttributeMutation.php new file mode 100644 index 0000000..f5f202e --- /dev/null +++ b/test/Mocked/MockMutationEntityAttributeMutation.php @@ -0,0 +1,14 @@ + Date: Tue, 16 Jun 2026 16:30:39 +0200 Subject: [PATCH 3/4] Support Doctrine proxy objects --- src/Attributes/Mutation.php | 4 +- src/Listener/MutationListener.php | 58 ++++++++-------------- src/Mutation.php | 7 ++- src/Resolver/MutationResolver.php | 20 +++++--- src/Resolver/MutationResolverInterface.php | 9 ++-- test/Listener/MutationListenerTest.php | 17 ++++--- test/Resolver/MutationResolverTest.php | 30 ++++++++--- 7 files changed, 81 insertions(+), 64 deletions(-) diff --git a/src/Attributes/Mutation.php b/src/Attributes/Mutation.php index 37c6968..8d516f8 100644 --- a/src/Attributes/Mutation.php +++ b/src/Attributes/Mutation.php @@ -19,13 +19,13 @@ public function __construct( * The Previous values will be stored in the mutation table. This is the * default strategy. */ - public const STRATEGY_COPY_PREVIOUS = 'previous'; + public const string STRATEGY_COPY_PREVIOUS = 'previous'; /** * The current values will be stored in the mutation table. And the * mutation will also be added on creation of the entity. */ - public const STRATEGY_COPY_CURRENT = 'current'; + public const string STRATEGY_COPY_CURRENT = 'current'; /** * Get the strategy for storing the mutation data. diff --git a/src/Listener/MutationListener.php b/src/Listener/MutationListener.php index f3128f8..2cf187a 100644 --- a/src/Listener/MutationListener.php +++ b/src/Listener/MutationListener.php @@ -10,25 +10,19 @@ use Hostnet\Component\EntityMutation\MutationAwareInterface; use Hostnet\Component\EntityMutation\Resolver\MutationResolverInterface; use Hostnet\Component\EntityTracker\Event\EntityChangedEvent; +use Psr\Cache\CacheItemInterface; +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\Adapter\ArrayAdapter; class MutationListener { - /** - * @var MutationResolverInterface - */ - private $resolver; - - /** - * Caches the class names to prevent iterating over attribute and annotations again on the next entity. - */ - private array $is_mutation_cache = []; - /** * @param MutationResolverInterface $resolver */ - public function __construct(MutationResolverInterface $resolver) - { - $this->resolver = $resolver; + public function __construct( + private MutationResolverInterface $resolver, + private CacheItemPoolInterface $is_mutation_cache = new ArrayAdapter() + ) { } /** @@ -78,39 +72,29 @@ public function entityChanged(EntityChangedEvent $event): void private function getMutationStrategy($em, $entity): false|string { - $class = get_class($entity); - if (array_key_exists($class, $this->is_mutation_cache)) { - return $this->is_mutation_cache[$class]; - } + $cache_key = base64_encode('MUTATION-' . get_class($entity)); + $cached_item = $this->is_mutation_cache->getItem($cache_key); - if (null !== $annotation = $this->resolver->getMutationAnnotation($em, $entity)) { - $this->is_mutation_cache[$class] = $annotation->getStrategy(); - - return $this->is_mutation_cache[$class]; + if ($cached_item->isHit()) { + return $cached_item->get(); } - if (null !== $strategy = $this->getMutationAttributeStrategy($entity)) { - $this->is_mutation_cache[$class] = $strategy; - - return $this->is_mutation_cache[$class]; + if (null !== $attribute = $this->resolver->getMutationAttribute($em, $entity)) { + return $this->save($cached_item, $attribute->getStrategy()); } - $this->is_mutation_cache[$class] = false; + if (null !== $annotation = $this->resolver->getMutationAnnotation($em, $entity)) { + return $this->save($cached_item, $annotation->getStrategy()); + } - return false; + return $this->save($cached_item, false); } - private function getMutationAttributeStrategy($entity): ?string + private function save(CacheItemInterface $item, false|string $value): false|string { - $reflection = new \ReflectionClass($entity); - $attributes = $reflection->getAttributes(Mutation::class); - - if (empty($attributes)) { - return null; - } + $item->set($value); + $this->is_mutation_cache->save($item); - /** @var Mutation $attribute */ - $attribute = $attributes[0]->newInstance(); - return $attribute->getStrategy(); + return $value; } } diff --git a/src/Mutation.php b/src/Mutation.php index d60a252..781e0c0 100644 --- a/src/Mutation.php +++ b/src/Mutation.php @@ -20,14 +20,17 @@ class Mutation extends Tracked * The Previous values will be stored in the mutation table. This is the * default strategy. */ - public const STRATEGY_COPY_PREVIOUS = 'previous'; + public const string STRATEGY_COPY_PREVIOUS = 'previous'; /** * The Previous values will be stored in the mutation table. And the * mutation will also be added on creation of the entity. */ - public const STRATEGY_COPY_CURRENT = 'current'; + public const string STRATEGY_COPY_CURRENT = 'current'; + /** + * @deprecated Not supported by the attribute + */ public $class = ''; /** diff --git a/src/Resolver/MutationResolver.php b/src/Resolver/MutationResolver.php index 2e08f1c..d6c6510 100644 --- a/src/Resolver/MutationResolver.php +++ b/src/Resolver/MutationResolver.php @@ -7,7 +7,8 @@ namespace Hostnet\Component\EntityMutation\Resolver; use Doctrine\ORM\EntityManagerInterface; -use Hostnet\Component\EntityMutation\Mutation; +use Hostnet\Component\EntityMutation\Attributes\Mutation; +use Hostnet\Component\EntityMutation\Mutation as MutationAnnotation; use Hostnet\Component\EntityTracker\Provider\EntityAnnotationMetadataProvider; class MutationResolver implements MutationResolverInterface @@ -15,7 +16,7 @@ class MutationResolver implements MutationResolverInterface /** * @var string */ - private $annotation = Mutation::class; + private $annotation = MutationAnnotation::class; /** * @var EntityAnnotationMetadataProvider @@ -33,18 +34,25 @@ public function __construct(EntityAnnotationMetadataProvider $provider) /** * {@inheritdoc} */ - public function getMutationAnnotation(EntityManagerInterface $em, $entity): ?Mutation + public function getMutationAnnotation(EntityManagerInterface $em, $entity): ?MutationAnnotation { return $this->provider->getAnnotationFromEntity($em, $entity, $this->annotation); } + public function getMutationAttribute(EntityManagerInterface $em, $entity): ?Mutation + { + return $this->provider->getAttributeFromEntity(Mutation::class, $em, $entity); + } + /** * {@inheritdoc} */ - public function getMutationClassName(EntityManagerInterface $em, $entity): ?string + public function getMutationClassName(EntityManagerInterface $em, $entity): string { - if (null === ($annotation = $this->getMutationAnnotation($em, $entity))) { - return null; + $annotation = $this->getMutationAnnotation($em, $entity); + // If $annotation is null, we must be using the attribute, otherwise this code would not get hit. + if (null === $annotation) { + return get_class($entity) . 'Mutation'; } return !empty($annotation->class) ? $annotation->class : get_class($entity) . 'Mutation'; diff --git a/src/Resolver/MutationResolverInterface.php b/src/Resolver/MutationResolverInterface.php index 3eb1946..4a0aa93 100644 --- a/src/Resolver/MutationResolverInterface.php +++ b/src/Resolver/MutationResolverInterface.php @@ -7,7 +7,8 @@ namespace Hostnet\Component\EntityMutation\Resolver; use Doctrine\ORM\EntityManagerInterface; -use Hostnet\Component\EntityMutation\Mutation; +use Hostnet\Component\EntityMutation\Attributes\Mutation; +use Hostnet\Component\EntityMutation\Mutation as MutationAnnotation; interface MutationResolverInterface { @@ -17,12 +18,14 @@ interface MutationResolverInterface * * @deprecated Please use the attribute instead. */ - public function getMutationAnnotation(EntityManagerInterface $em, $entity): ?Mutation; + public function getMutationAnnotation(EntityManagerInterface $em, $entity): ?MutationAnnotation; + + public function getMutationAttribute(EntityManagerInterface $em, $entity): ?Mutation; /** * Return the mutation class name */ - public function getMutationClassName(EntityManagerInterface $em, $entity): ?string; + public function getMutationClassName(EntityManagerInterface $em, $entity): string; /** * Return list of mutatable fields diff --git a/test/Listener/MutationListenerTest.php b/test/Listener/MutationListenerTest.php index 845034c..69cd9f6 100644 --- a/test/Listener/MutationListenerTest.php +++ b/test/Listener/MutationListenerTest.php @@ -8,11 +8,12 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\Mapping\ClassMetadata; +use Hostnet\Component\EntityMutation\Attributes\Mutation; use Hostnet\Component\EntityMutation\Mocked\MockMutationEntity; use Hostnet\Component\EntityMutation\Mocked\MockMutationEntityAttribute; use Hostnet\Component\EntityMutation\Mocked\MockMutationEntityAttributeMutation; use Hostnet\Component\EntityMutation\Mocked\MockMutationEntityMutation; -use Hostnet\Component\EntityMutation\Mutation; +use Hostnet\Component\EntityMutation\Mutation as MutationAnnotation; use Hostnet\Component\EntityMutation\Resolver\MutationResolverInterface; use Hostnet\Component\EntityTracker\Event\EntityChangedEvent; use PHPUnit\Framework\TestCase; @@ -52,7 +53,7 @@ public function testOnEntityChanged(): void ->with($this->em, $current_entity) ->willReturn(['id']); - $annotation = new Mutation(); + $annotation = new MutationAnnotation(); $this->resolver ->expects($this->once()) @@ -105,8 +106,8 @@ public function testOnEntityChangedCopyCurrent(): void ->with($this->em, $current_entity) ->willReturn(['id']); - $annotation = new Mutation(); - $annotation->strategy = Mutation::STRATEGY_COPY_CURRENT; + $annotation = new MutationAnnotation(); + $annotation->strategy = MutationAnnotation::STRATEGY_COPY_CURRENT; $this->resolver ->expects($this->once()) @@ -226,8 +227,8 @@ public function testOnEntityChangedEmptyChanges(): void ->with($this->em, $current_entity) ->willReturn([]); - $annotation = new Mutation(); - $annotation->strategy = Mutation::STRATEGY_COPY_CURRENT; + $annotation = new MutationAnnotation(); + $annotation->strategy = MutationAnnotation::STRATEGY_COPY_CURRENT; $this->resolver ->expects($this->once()) @@ -290,9 +291,9 @@ public function testOnEntityChangedWithAttribute(): void $this->resolver ->expects($this->once()) - ->method('getMutationAnnotation') + ->method('getMutationAttribute') ->with($this->em, $current_entity) - ->willReturn(null); + ->willReturn(new Mutation()); $this->resolver ->expects($this->exactly(2)) diff --git a/test/Resolver/MutationResolverTest.php b/test/Resolver/MutationResolverTest.php index 3dc2543..e5deb14 100644 --- a/test/Resolver/MutationResolverTest.php +++ b/test/Resolver/MutationResolverTest.php @@ -8,7 +8,8 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\Mapping\ClassMetadata; -use Hostnet\Component\EntityMutation\Mutation; +use Hostnet\Component\EntityMutation\Attributes\Mutation; +use Hostnet\Component\EntityMutation\Mutation as MutationAnnotation; use Hostnet\Component\EntityTracker\Provider\EntityAnnotationMetadataProvider; use PHPUnit\Framework\TestCase; @@ -43,7 +44,7 @@ public function testGetMutationAnnotation(): void $this->provider ->expects($this->once()) ->method('getAnnotationFromEntity') - ->with($this->em, $entity, Mutation::class); + ->with($this->em, $entity, MutationAnnotation::class); $this->resolver->getMutationAnnotation($this->em, $entity); } @@ -51,16 +52,18 @@ public function testGetMutationAnnotation(): void public function testGetMutationClassName(): void { $entity = new \stdClass(); - $annotation = new Mutation(); + $annotation = new MutationAnnotation(); $annotation->class = 'Phpunit'; $this->provider ->expects($this->exactly(3)) ->method('getAnnotationFromEntity') ->with($this->em, $entity, 'Hostnet\Component\EntityMutation\Mutation') - ->willReturnOnConsecutiveCalls(null, new Mutation(), $annotation); + ->willReturnOnConsecutiveCalls(null, new MutationAnnotation(), $annotation); + + // Without annotation, assuming the attribute is in use + $this->assertEquals('stdClassMutation', $this->resolver->getMutationClassName($this->em, $entity)); - $this->assertEquals('', $this->resolver->getMutationClassName($this->em, $entity)); $this->assertEquals('stdClassMutation', $this->resolver->getMutationClassName($this->em, $entity)); $this->assertEquals('Phpunit', $this->resolver->getMutationClassName($this->em, $entity)); } @@ -74,7 +77,7 @@ public function testGetMutatedFields(): void $this->provider ->expects($this->once()) ->method('getAnnotationFromEntity') - ->willReturnOnConsecutiveCalls(new Mutation()); + ->willReturnOnConsecutiveCalls(new MutationAnnotation()); $metadata->expects($this->once())->method('getFieldNames')->willReturn(['id']); $metadata_meta->expects($this->once())->method('getFieldNames')->willReturn(['id']); @@ -89,4 +92,19 @@ public function testGetMutatedFields(): void $this->assertEquals(['id', 'test'], $this->resolver->getMutatableFields($this->em, $entity)); } + + public function testGetMutationAttribute(): void + { + $entity = new \stdClass(); + + $attribute = new Mutation(Mutation::STRATEGY_COPY_CURRENT); + + $this->provider + ->expects($this->once()) + ->method('getAttributeFromEntity') + ->with(Mutation::class, $this->em, $entity) + ->willReturn($attribute); + + self::assertSame($attribute, $this->resolver->getMutationAttribute($this->em, $entity)); + } } From dfc36f87851f279abdedf845a2af7c09f633b8aa Mon Sep 17 00:00:00 2001 From: Jan Lam Date: Thu, 18 Jun 2026 10:52:02 +0200 Subject: [PATCH 4/4] Update entity-tracker-component --- composer.json | 4 ++-- src/Resolver/MutationResolver.php | 13 ++----------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/composer.json b/composer.json index bc1b7d1..60ce454 100644 --- a/composer.json +++ b/composer.json @@ -6,13 +6,13 @@ "require": { "php": "^8.3", "doctrine/orm": "^2.7.4", - "hostnet/entity-tracker-component": "^2.0.1" + "hostnet/entity-tracker-component": "^2.3.0" }, "require-dev": { "hostnet/database-test-lib": "^2.0.2", "hostnet/phpcs-tool": "^9.1.0", "phpunit/phpunit": "^9.5.6", - "symfony/cache": "^5.3" + "symfony/cache": "^6.4|^7.4" }, "autoload": { "psr-4": { diff --git a/src/Resolver/MutationResolver.php b/src/Resolver/MutationResolver.php index d6c6510..311d1e4 100644 --- a/src/Resolver/MutationResolver.php +++ b/src/Resolver/MutationResolver.php @@ -9,7 +9,7 @@ use Doctrine\ORM\EntityManagerInterface; use Hostnet\Component\EntityMutation\Attributes\Mutation; use Hostnet\Component\EntityMutation\Mutation as MutationAnnotation; -use Hostnet\Component\EntityTracker\Provider\EntityAnnotationMetadataProvider; +use Hostnet\Component\EntityTracker\Provider\EntityMetadataProvider; class MutationResolver implements MutationResolverInterface { @@ -18,17 +18,8 @@ class MutationResolver implements MutationResolverInterface */ private $annotation = MutationAnnotation::class; - /** - * @var EntityAnnotationMetadataProvider - */ - private $provider; - - /** - * @param EntityAnnotationMetadataProvider $provider - */ - public function __construct(EntityAnnotationMetadataProvider $provider) + public function __construct(private EntityMetadataProvider $provider) { - $this->provider = $provider; } /**