From 3589b0e0c13e8df02481a3c68f4bbdc5fca77f8f Mon Sep 17 00:00:00 2001 From: Jannik Zschiesche Date: Mon, 22 Sep 2025 15:57:15 +0200 Subject: [PATCH 1/2] Avoid using deprecated doctrine helpers for resolving classes --- CHANGELOG.md | 6 ++ composer.json | 2 +- src/Normalizer/SimpleNormalizer.php | 33 ++++-- tests/Normalizer/SimpleNormalizerTest.php | 123 ++++++++++++++++++++++ 4 files changed, 156 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b87922..aaeab35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +1.4.1 +===== + +* (improvement) Avoid using deprecated Doctrine internals when resolving class names. + + 1.4.0 ===== diff --git a/composer.json b/composer.json index 09352ac..6c5e796 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,7 @@ "require-dev": { "21torr/janus": "^1.5.1", "bamarni/composer-bin-plugin": "^1.8.2", - "doctrine/common": "^3.5", + "doctrine/orm": "^3.0", "phpunit/phpunit": "^12.2.5", "roave/security-advisories": "dev-latest" }, diff --git a/src/Normalizer/SimpleNormalizer.php b/src/Normalizer/SimpleNormalizer.php index 61c1d70..4c0addd 100644 --- a/src/Normalizer/SimpleNormalizer.php +++ b/src/Normalizer/SimpleNormalizer.php @@ -3,6 +3,8 @@ namespace Torr\SimpleNormalizer\Normalizer; use Doctrine\Common\Util\ClassUtils; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Mapping\ClassMetadataFactory; use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; use Symfony\Component\DependencyInjection\ServiceLocator; use Torr\SimpleNormalizer\Exception\ObjectTypeNotSupportedException; @@ -23,6 +25,8 @@ */ class SimpleNormalizer { + private readonly ?ClassMetadataFactory $doctrineMetadata; + /** * @param ServiceLocator $objectNormalizers */ @@ -30,7 +34,11 @@ public function __construct ( private readonly ServiceLocator $objectNormalizers, private readonly bool $isDebug = false, private readonly ?ValidJsonVerifier $validJsonVerifier = null, - ) {} + ?EntityManagerInterface $entityManager = null, + ) + { + $this->doctrineMetadata = $entityManager?->getMetadataFactory(); + } /** */ @@ -104,12 +112,7 @@ private function recursiveNormalize (mixed $value, array $context = []) : mixed try { - $className = $value::class; - - if (class_exists(ClassUtils::class)) - { - $className = ClassUtils::getRealClass($className); - } + $className = $this->normalizeClassName($value::class); $normalizer = $this->objectNormalizers->get($className); \assert($normalizer instanceof SimpleObjectNormalizerInterface); @@ -131,6 +134,22 @@ private function recursiveNormalize (mixed $value, array $context = []) : mixed )); } + /** + * Normalizes the class name + */ + private function normalizeClassName (string $className) : string + { + // if there is no doctrine, just return + if (null === $this->doctrineMetadata) + { + return $className; + } + + return $this->doctrineMetadata->hasMetadataFor($className) + ? $this->doctrineMetadata->getMetadataFor($className)->getName() + : $className; + } + /** * The actual customized normalization logic for arrays, that recursively normalizes the value. * It must never call one of the public methods above and just normalizes the value. diff --git a/tests/Normalizer/SimpleNormalizerTest.php b/tests/Normalizer/SimpleNormalizerTest.php index d2043b3..3774ca6 100644 --- a/tests/Normalizer/SimpleNormalizerTest.php +++ b/tests/Normalizer/SimpleNormalizerTest.php @@ -2,6 +2,9 @@ namespace Tests\Torr\SimpleNormalizer\Normalizer; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Mapping\ClassMetadataFactory; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\ServiceLocator; @@ -124,6 +127,126 @@ public function testInvalidNestedNormalizer () : void ]); } + /** + */ + public function testWithoutEntityManager () : void + { + $locator = $this->createMock(ServiceLocator::class); + + $locator->expects(self::once()) + ->method("get") + ->with(DummyVO::class) + ->willReturn(new readonly class implements SimpleObjectNormalizerInterface { + public function normalize (object $value, array $context, SimpleNormalizer $normalizer) : int + { + return 5; + } + + public static function getNormalizedType () : string + { + return DummyVO::class; + } + }); + + $normalizer = new SimpleNormalizer( + objectNormalizers: $locator, + isDebug: true, + validJsonVerifier: new ValidJsonVerifier(), + ); + + $normalizer->normalize(new DummyVO(5)); + } + + /** + */ + public function testWithEntityManagerButNoMapping () : void + { + $metadataFactory = $this->createMock(ClassMetadataFactory::class); + $metadataFactory + ->expects(self::once()) + ->method("hasMetadataFor") + ->with(DummyVO::class) + ->willReturn(false); + + $entityManager = $this->createMock(EntityManagerInterface::class); + $entityManager->method("getMetadataFactory")->willReturn($metadataFactory); + + $locator = $this->createMock(ServiceLocator::class); + + $locator->expects(self::once()) + ->method("get") + ->with(DummyVO::class) + ->willReturn(new readonly class implements SimpleObjectNormalizerInterface { + public function normalize (object $value, array $context, SimpleNormalizer $normalizer) : int + { + return 5; + } + + public static function getNormalizedType () : string + { + return DummyVO::class; + } + }); + + $normalizer = new SimpleNormalizer( + objectNormalizers: $locator, + isDebug: true, + validJsonVerifier: new ValidJsonVerifier(), + entityManager: $entityManager, + ); + + $normalizer->normalize(new DummyVO(5)); + } + + /** + */ + public function testWithEntityManagerWithMapping () : void + { + $classMetaData = new ClassMetadata("SomeClass"); + + $metadataFactory = $this->createMock(ClassMetadataFactory::class); + $metadataFactory + ->expects(self::once()) + ->method("hasMetadataFor") + ->with(DummyVO::class) + ->willReturn(true); + + $metadataFactory + ->expects(self::once()) + ->method("getMetadataFor") + ->with(DummyVO::class) + ->willReturn($classMetaData); + + $entityManager = $this->createMock(EntityManagerInterface::class); + $entityManager->method("getMetadataFactory")->willReturn($metadataFactory); + + $locator = $this->createMock(ServiceLocator::class); + + $locator->expects(self::once()) + ->method("get") + ->with("SomeClass") + ->willReturn(new readonly class implements SimpleObjectNormalizerInterface { + public function normalize (object $value, array $context, SimpleNormalizer $normalizer) : int + { + return 5; + } + + public static function getNormalizedType () : string + { + return DummyVO::class; + } + }); + + $normalizer = new SimpleNormalizer( + objectNormalizers: $locator, + isDebug: true, + validJsonVerifier: new ValidJsonVerifier(), + entityManager: $entityManager, + ); + + $normalizer->normalize(new DummyVO(5)); + } + /** * @return ServiceLocator */ From 977c6bdc5937a64add8347afae5c5f939ef0924d Mon Sep 17 00:00:00 2001 From: Jannik Zschiesche Date: Mon, 22 Sep 2025 16:02:19 +0200 Subject: [PATCH 2/2] Add normalizer fixture to make test implementation easier + fix CS --- src/Normalizer/SimpleNormalizer.php | 5 ++- tests/Fixture/DummyVONormalizer.php | 26 +++++++++++ tests/Normalizer/SimpleNormalizerTest.php | 54 +++-------------------- 3 files changed, 35 insertions(+), 50 deletions(-) create mode 100644 tests/Fixture/DummyVONormalizer.php diff --git a/src/Normalizer/SimpleNormalizer.php b/src/Normalizer/SimpleNormalizer.php index 4c0addd..6bd02da 100644 --- a/src/Normalizer/SimpleNormalizer.php +++ b/src/Normalizer/SimpleNormalizer.php @@ -2,7 +2,6 @@ namespace Torr\SimpleNormalizer\Normalizer; -use Doctrine\Common\Util\ClassUtils; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadataFactory; use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; @@ -136,6 +135,10 @@ private function recursiveNormalize (mixed $value, array $context = []) : mixed /** * Normalizes the class name + * + * @param class-string $className + * + * @return class-string */ private function normalizeClassName (string $className) : string { diff --git a/tests/Fixture/DummyVONormalizer.php b/tests/Fixture/DummyVONormalizer.php new file mode 100644 index 0000000..259c650 --- /dev/null +++ b/tests/Fixture/DummyVONormalizer.php @@ -0,0 +1,26 @@ +returnValue; + } + + public static function getNormalizedType () : string + { + return DummyVO::class; + } +} diff --git a/tests/Normalizer/SimpleNormalizerTest.php b/tests/Normalizer/SimpleNormalizerTest.php index 3774ca6..5986dd5 100644 --- a/tests/Normalizer/SimpleNormalizerTest.php +++ b/tests/Normalizer/SimpleNormalizerTest.php @@ -9,9 +9,9 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\ServiceLocator; use Tests\Torr\SimpleNormalizer\Fixture\DummyVO; +use Tests\Torr\SimpleNormalizer\Fixture\DummyVONormalizer; use Torr\SimpleNormalizer\Exception\IncompleteNormalizationException; use Torr\SimpleNormalizer\Normalizer\SimpleNormalizer; -use Torr\SimpleNormalizer\Normalizer\SimpleObjectNormalizerInterface; use Torr\SimpleNormalizer\Normalizer\Validator\ValidJsonVerifier; /** @@ -136,17 +136,7 @@ public function testWithoutEntityManager () : void $locator->expects(self::once()) ->method("get") ->with(DummyVO::class) - ->willReturn(new readonly class implements SimpleObjectNormalizerInterface { - public function normalize (object $value, array $context, SimpleNormalizer $normalizer) : int - { - return 5; - } - - public static function getNormalizedType () : string - { - return DummyVO::class; - } - }); + ->willReturn(new DummyVONormalizer(5)); $normalizer = new SimpleNormalizer( objectNormalizers: $locator, @@ -176,17 +166,7 @@ public function testWithEntityManagerButNoMapping () : void $locator->expects(self::once()) ->method("get") ->with(DummyVO::class) - ->willReturn(new readonly class implements SimpleObjectNormalizerInterface { - public function normalize (object $value, array $context, SimpleNormalizer $normalizer) : int - { - return 5; - } - - public static function getNormalizedType () : string - { - return DummyVO::class; - } - }); + ->willReturn(new DummyVONormalizer(5)); $normalizer = new SimpleNormalizer( objectNormalizers: $locator, @@ -225,17 +205,7 @@ public function testWithEntityManagerWithMapping () : void $locator->expects(self::once()) ->method("get") ->with("SomeClass") - ->willReturn(new readonly class implements SimpleObjectNormalizerInterface { - public function normalize (object $value, array $context, SimpleNormalizer $normalizer) : int - { - return 5; - } - - public static function getNormalizedType () : string - { - return DummyVO::class; - } - }); + ->willReturn(new DummyVONormalizer(5)); $normalizer = new SimpleNormalizer( objectNormalizers: $locator, @@ -253,21 +223,7 @@ public static function getNormalizedType () : string private function createNormalizerObjectNormalizers (mixed $returnValue) : ServiceLocator { return new ServiceLocator([ - DummyVO::class => static fn () => new readonly class($returnValue) implements SimpleObjectNormalizerInterface { - public function __construct ( - private mixed $returnValue, - ) {} - - public function normalize (object $value, array $context, SimpleNormalizer $normalizer) : mixed - { - return $this->returnValue; - } - - public static function getNormalizedType () : string - { - return DummyVO::class; - } - }, + DummyVO::class => static fn () => new DummyVONormalizer($returnValue), ]); } }