From 7c3f666b819a5480b345c13952baeae8d24303b1 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Thu, 17 Aug 2023 17:26:50 -0400 Subject: [PATCH] Adding tests + tweaks & fixes --- README.md | 30 +++++++- .../MicroMapperCompilerPass.php | 2 +- src/MicroMapper.php | 7 +- src/SymfonycastsMicroMapperBundle.php | 2 +- tests/IntegrationTest.php | 31 ++++++++ tests/MapperConfigTest.php | 38 ++++++++++ tests/MicroMapperTest.php | 71 +++++++++++++++++++ tests/fixtures/DinoRegion.php | 11 +++ tests/fixtures/DinoRegionDto.php | 18 +++++ tests/fixtures/DinoRegionToDtoMapper.php | 50 +++++++++++++ tests/fixtures/Dinosaur.php | 15 ++++ tests/fixtures/DinosaurDto.php | 11 +++ tests/fixtures/DinosaurToDtoMapper.php | 38 ++++++++++ tests/fixtures/MicroMapperTestKernel.php | 65 +++++++++++++++++ 14 files changed, 384 insertions(+), 5 deletions(-) create mode 100644 tests/IntegrationTest.php create mode 100644 tests/MapperConfigTest.php create mode 100644 tests/MicroMapperTest.php create mode 100644 tests/fixtures/DinoRegion.php create mode 100644 tests/fixtures/DinoRegionDto.php create mode 100644 tests/fixtures/DinoRegionToDtoMapper.php create mode 100644 tests/fixtures/Dinosaur.php create mode 100644 tests/fixtures/DinosaurDto.php create mode 100644 tests/fixtures/DinosaurToDtoMapper.php create mode 100644 tests/fixtures/MicroMapperTestKernel.php diff --git a/README.md b/README.md index 3e130d6..7730cf4 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,32 @@ Then this library is for you! Define a "mapper" class: ```php -TODO +use App\Entity\Dragon; +use App\DTO\DragonDTO; + +#[AsMapper(from: Dragon::class, to: DragonDTO::class)] +class DragonEntityToDtoMapper implements MapperInterface +{ + public function init(object $from, string $toClass, array $context): object + { + $entity = $from; + $dto = new DragonDTO(); + $dto->id = $entity->getId(); + + return $dto; + } + + public function populate(object $from, object $to, array $context): object + { + $dto = $from; + $entity = $to; + + $dto->name = $entity->getName(); + $dto->firePower = $entity->getFirePower(); + + return $entity; + } +} ``` Then... map! @@ -18,7 +43,7 @@ $dragonDTO = $microMapper->map($dragon, DragonDTO::class); ``` MicroMapper is similar to other data mappers, like -[jane-php/automapper](https://github.com/janephp/automapper), except less +[jane-php/automapper](https://github.com/janephp/automapper), except... less impressive! Jane's Automapper is awesome and handles a lot of heavy lifting. With MicroMapper, *you* do the heavy lifting. Let's review with a table! @@ -49,3 +74,4 @@ TODO - property accessor for doctrine relations - nested objects +- manual setup diff --git a/src/Bundle/DependencyInjection/MicroMapperCompilerPass.php b/src/Bundle/DependencyInjection/MicroMapperCompilerPass.php index d59e27e..de89585 100644 --- a/src/Bundle/DependencyInjection/MicroMapperCompilerPass.php +++ b/src/Bundle/DependencyInjection/MicroMapperCompilerPass.php @@ -14,7 +14,7 @@ */ class MicroMapperCompilerPass implements CompilerPassInterface { - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { $mapperConfigDefinitions = []; foreach ($container->findTaggedServiceIds('micro_mapper.mapper') as $id => $tags) { diff --git a/src/MicroMapper.php b/src/MicroMapper.php index 21c3f24..adf8481 100644 --- a/src/MicroMapper.php +++ b/src/MicroMapper.php @@ -15,10 +15,15 @@ class MicroMapper implements MicroMapperInterface /** * @param MapperConfig[] $mapperConfigs */ - public function __construct(private array $mapperConfigs) + public function __construct(private array $mapperConfigs = []) { } + public function addMapperConfig(MapperConfig $mapperConfig): void + { + $this->mapperConfigs[] = $mapperConfig; + } + public function map(object $from, string $toClass, array $context = []): object { $this->currentDepth++; diff --git a/src/SymfonycastsMicroMapperBundle.php b/src/SymfonycastsMicroMapperBundle.php index b10442c..e6839c5 100644 --- a/src/SymfonycastsMicroMapperBundle.php +++ b/src/SymfonycastsMicroMapperBundle.php @@ -18,7 +18,7 @@ protected function createContainerExtension(): ?ExtensionInterface return new MicroMapperExtension(); } - public function build(ContainerBuilder $container) + public function build(ContainerBuilder $container): void { $container->addCompilerPass(new MicroMapperCompilerPass()); } diff --git a/tests/IntegrationTest.php b/tests/IntegrationTest.php new file mode 100644 index 0000000..53c79b0 --- /dev/null +++ b/tests/IntegrationTest.php @@ -0,0 +1,31 @@ +id = 1; + $region->name = 'North America'; + $region->climate = 'temperate'; + $dinosaur1 = new Dinosaur(3, 'Velociraptor', 'mongoliensis'); + $dinosaur1->region = $region; + $region->dinosaurs = [$dinosaur1]; + + $microMapper = self::getContainer()->get('public.micro_mapper'); + assert($microMapper instanceof MicroMapperInterface); + $dto = $microMapper->map($region, DinoRegionDto::class); + $this->assertInstanceOf(DinoRegionDto::class, $dto); + $this->assertSame(1, $dto->id); + $this->assertCount(1, $dto->dinosaursMappedShallow); + $this->assertSame(3, $dto->dinosaursMappedShallow[0]->id); + } +} diff --git a/tests/MapperConfigTest.php b/tests/MapperConfigTest.php new file mode 100644 index 0000000..ed9ff2e --- /dev/null +++ b/tests/MapperConfigTest.php @@ -0,0 +1,38 @@ + $this->createMock(MapperInterface::class), + ); + + $this->assertTrue($config->supports(new DinoRegion(), DinoRegionDto::class)); + $this->assertFalse($config->supports(new \stdClass(), DinoRegionDto::class)); + $this->assertFalse($config->supports(new DinoRegion(), \stdClass::class)); + $this->assertFalse($config->supports(new DinoRegionDto(), DinoRegion::class)); + } + + public function testGetMapper() + { + $mockMapper = $this->createMock(MapperInterface::class); + $config = new MapperConfig( + fromClass: DinoRegion::class, + toClass: DinoRegionDto::class, + mapper: fn() => $mockMapper, + ); + + $this->assertSame($mockMapper, $config->getMapper()); + } +} diff --git a/tests/MicroMapperTest.php b/tests/MicroMapperTest.php new file mode 100644 index 0000000..49d787a --- /dev/null +++ b/tests/MicroMapperTest.php @@ -0,0 +1,71 @@ +createMapper(); + $region = new DinoRegion(); + $region->id = 1; + $region->name = 'North America'; + $region->climate = 'temperate'; + + $dinosaur1 = new Dinosaur(3, 'Velociraptor', 'mongoliensis'); + $dinosaur1->region = $region; + $dinosaur2 = new Dinosaur(4, 'Triceratops', 'horridus'); + $dinosaur2->region = $region; + $region->dinosaurs = [$dinosaur1, $dinosaur2]; + + $dto = $this->createMapper()->map($region, DinoRegionDto::class); + $this->assertInstanceOf(DinoRegionDto::class, $dto); + $this->assertSame(1, $dto->id); + $this->assertSame('North America', $dto->name); + $this->assertSame('temperate', $dto->climate); + $this->assertCount(2, $dto->dinosaursMappedShallow); + $this->assertCount(2, $dto->dinosaursMappedDeep); + + // id is mapped for both deep and shallow + $this->assertSame(3, $dto->dinosaursMappedShallow[0]->id); + $this->assertSame(3, $dto->dinosaursMappedDeep[0]->id); + // further properties are only in the deep + $this->assertNull($dto->dinosaursMappedShallow[0]->genus); + $this->assertSame('Velociraptor', $dto->dinosaursMappedDeep[0]->genus); + // the deep will have a region, but it will be shallow + $this->assertSame($dto->dinosaursMappedDeep[0]->region->id, 1); + $this->assertNull($dto->dinosaursMappedDeep[0]->region->name); + } + + private function createMapper(): MicroMapperInterface + { + $microMapper = new MicroMapper([]); + $microMapper->addMapperConfig(new MapperConfig( + DinoRegion::class, + DinoRegionDto::class, + fn() => new DinoRegionToDtoMapper($microMapper) + )); + $microMapper->addMapperConfig(new MapperConfig( + Dinosaur::class, + DinosaurDto::class, + fn() => new DinosaurToDtoMapper($microMapper) + )); + + return $microMapper; + } +} diff --git a/tests/fixtures/DinoRegion.php b/tests/fixtures/DinoRegion.php new file mode 100644 index 0000000..7b7b8e5 --- /dev/null +++ b/tests/fixtures/DinoRegion.php @@ -0,0 +1,11 @@ +id = $from->id; + + return $dto; + } + + public function populate(object $from, object $to, array $context): object + { + assert($from instanceof DinoRegion); + assert($to instanceof DinoRegionDto); + + $to->name = $from->name; + $to->climate = $from->climate; + $shallowDinosaurDtos = []; + foreach ($from->dinosaurs as $dino) { + $shallowDinosaurDtos[] = $this->microMapper->map($dino, DinosaurDto::class, [ + MicroMapperInterface::MAX_DEPTH => 0, + ]); + } + $to->dinosaursMappedShallow = $shallowDinosaurDtos; + + $deepDinosaurDtos = []; + foreach ($from->dinosaurs as $dino) { + $deepDinosaurDtos[] = $this->microMapper->map($dino, DinosaurDto::class, [ + MicroMapperInterface::MAX_DEPTH => 1, + ]); + } + $to->dinosaursMappedDeep = $deepDinosaurDtos; + + return $to; + } +} diff --git a/tests/fixtures/Dinosaur.php b/tests/fixtures/Dinosaur.php new file mode 100644 index 0000000..a0b399d --- /dev/null +++ b/tests/fixtures/Dinosaur.php @@ -0,0 +1,15 @@ +id = $from->id; + + return $dto; + } + + public function populate(object $from, object $to, array $context): object + { + assert($from instanceof Dinosaur); + assert($to instanceof DinosaurDto); + + $to->genus = $from->genus; + $to->species = $from->species; + $to->region = $this->microMapper->map($from->region, DinoRegionDto::class, [ + MicroMapperInterface::MAX_DEPTH => 0, + ]); + + return $to; + } +} diff --git a/tests/fixtures/MicroMapperTestKernel.php b/tests/fixtures/MicroMapperTestKernel.php new file mode 100644 index 0000000..9bdc737 --- /dev/null +++ b/tests/fixtures/MicroMapperTestKernel.php @@ -0,0 +1,65 @@ +loadFromExtension('framework', [ + 'secret' => 'foo', + 'test' => true, + 'http_method_override' => true, + 'handle_all_throwables' => true, + 'php_errors' => [ + 'log' => true, + ], + ]); + $container->register(DinoRegionToDtoMapper::class) + ->setAutowired(true) + ->setAutoconfigured(true); + $container->register(DinosaurToDtoMapper::class) + ->setAutowired(true) + ->setAutoconfigured(true); + $container->setAlias('public.micro_mapper', new Alias(MicroMapperInterface::class, true)); + } + + public function getCacheDir(): string + { + return sys_get_temp_dir().'/cache'.spl_object_hash($this); + } + + public function getLogDir(): string + { + return sys_get_temp_dir().'/logs'.spl_object_hash($this); + } + + public function getProjectDir(): string + { + return __DIR__; + } +}