Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
1.4.1
=====

* (improvement) Avoid using deprecated Doctrine internals when resolving class names.


1.4.0
=====

Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
38 changes: 30 additions & 8 deletions src/Normalizer/SimpleNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,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;
Expand All @@ -23,14 +24,20 @@
*/
class SimpleNormalizer
{
private readonly ?ClassMetadataFactory $doctrineMetadata;

/**
* @param ServiceLocator<SimpleObjectNormalizerInterface> $objectNormalizers
*/
public function __construct (
private readonly ServiceLocator $objectNormalizers,
private readonly bool $isDebug = false,
private readonly ?ValidJsonVerifier $validJsonVerifier = null,
) {}
?EntityManagerInterface $entityManager = null,
)
{
$this->doctrineMetadata = $entityManager?->getMetadataFactory();
}

/**
*/
Expand Down Expand Up @@ -104,12 +111,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);
Expand All @@ -131,6 +133,26 @@ 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
{
// 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.
Expand Down
26 changes: 26 additions & 0 deletions tests/Fixture/DummyVONormalizer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php declare(strict_types=1);

namespace Tests\Torr\SimpleNormalizer\Fixture;

use Torr\SimpleNormalizer\Normalizer\SimpleNormalizer;
use Torr\SimpleNormalizer\Normalizer\SimpleObjectNormalizerInterface;

/**
* @final
*/
readonly class DummyVONormalizer implements SimpleObjectNormalizerInterface
{
public function __construct (
private mixed $returnValue = null,
) {}

public function normalize (object $value, array $context, SimpleNormalizer $normalizer) : mixed
{
return $this->returnValue;
}

public static function getNormalizedType () : string
{
return DummyVO::class;
}
}
111 changes: 95 additions & 16 deletions tests/Normalizer/SimpleNormalizerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

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;
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;

/**
Expand Down Expand Up @@ -124,27 +127,103 @@ public function testInvalidNestedNormalizer () : void
]);
}

/**
*/
public function testWithoutEntityManager () : void
{
$locator = $this->createMock(ServiceLocator::class);

$locator->expects(self::once())
->method("get")
->with(DummyVO::class)
->willReturn(new DummyVONormalizer(5));

$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 DummyVONormalizer(5));

$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 DummyVONormalizer(5));

$normalizer = new SimpleNormalizer(
objectNormalizers: $locator,
isDebug: true,
validJsonVerifier: new ValidJsonVerifier(),
entityManager: $entityManager,
);

$normalizer->normalize(new DummyVO(5));
}

/**
* @return ServiceLocator<mixed>
*/
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),
]);
}
}