Skip to content

Commit

Permalink
Merge pull request #25 from rekalogika:fix/preset-mapping
Browse files Browse the repository at this point in the history
fix(`PresetMapping`): Support proxied classes, add tests.
  • Loading branch information
priyadi committed Feb 20, 2024
2 parents 8edee5b + 10dcec6 commit 4a80abf
Show file tree
Hide file tree
Showing 7 changed files with 227 additions and 36 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* test: test array cast to object mapping
* feat(`Context`): `with()` not accepts multiple argument.
* build: Deinternalize `ObjectCacheFactory`
* fix(`PresetMapping`): Support proxied classes, add tests.

## 1.0.0

Expand Down
8 changes: 8 additions & 0 deletions config/tests.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* that was distributed with this source code.
*/

use Rekalogika\Mapper\MapperInterface;
use Rekalogika\Mapper\Tests\Common\TestKernel;
use Rekalogika\Mapper\Tests\Fixtures\Money\MoneyToMoneyDtoTransformer;
use Rekalogika\Mapper\Tests\Fixtures\ObjectMapper\MoneyObjectMapper;
Expand All @@ -21,6 +22,7 @@
use Rekalogika\Mapper\Tests\Fixtures\PropertyMapper\PropertyMapperWithConstructorWithoutClassAttribute;
use Rekalogika\Mapper\Tests\Fixtures\PropertyMapper\PropertyMapperWithExtraArguments;
use Rekalogika\Mapper\Tests\Fixtures\PropertyMapper\PropertyMapperWithoutClassAttribute;
use Rekalogika\Mapper\Tests\Fixtures\RememberingMapper\RememberingMapper;
use Rekalogika\Mapper\Tests\Fixtures\TransformerOverride\OverrideTransformer;
use Rekalogika\Mapper\Transformer\Implementation\ScalarToScalarTransformer;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
Expand Down Expand Up @@ -58,4 +60,10 @@
->args([
'$transformer' => service(ScalarToScalarTransformer::class),
]);

$services->set(RememberingMapper::class)
->args([
'$decorated' => service(MapperInterface::class),
'$objectCacheFactory' => service('rekalogika.mapper.object_cache_factory'),
]);
};
48 changes: 14 additions & 34 deletions src/Transformer/Context/PresetMapping.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@

namespace Rekalogika\Mapper\Transformer\Context;

use Rekalogika\Mapper\ObjectCache\ObjectCache;
use Rekalogika\Mapper\Transformer\Exception\PresetMappingNotFound;
use Rekalogika\Mapper\Transformer\Model\SplObjectStorageWrapper;

/**
* Contains preset object to object mapping, used by `PresetTransformer`
*/
final readonly class PresetMapping
{
/**
Expand All @@ -27,7 +28,7 @@
/**
* @param iterable<object,iterable<class-string,object>> $mappings
*/
public function __construct(iterable $mappings)
public function __construct(iterable $mappings = [])
{
/**
* @var \WeakMap<object,\ArrayObject<class-string,object>>
Expand All @@ -47,44 +48,23 @@ public function __construct(iterable $mappings)
$this->mappings = $weakMap;
}

public static function fromObjectCache(ObjectCache $objectCache): self
public function mergeFrom(self $presetMapping): void
{
$objectCacheWeakMap = $objectCache->getInternalMapping();

/** @var SplObjectStorageWrapper<object,\ArrayObject<class-string,object>> */
$presetMapping = new SplObjectStorageWrapper(new \SplObjectStorage());

/**
* @var object $source
* @var \ArrayObject<class-string,object> $classToTargetMapping
*/
foreach ($objectCacheWeakMap as $source => $classToTargetMapping) {
$newTargetClass = $source::class;
/** @var object */
$newTarget = $source;

/**
* @var string $targetClass
* @var object $target
*/
foreach ($classToTargetMapping as $targetClass => $target) {
if (!class_exists($targetClass)) {
continue;
}

$newSource = $target;

if (!$presetMapping->offsetExists($newSource)) {
/** @var \ArrayObject<class-string,object> */
$arrayObject = new \ArrayObject();
$presetMapping->offsetSet($newSource, $arrayObject);
}

$presetMapping->offsetGet($newSource)?->offsetSet($newTargetClass, $newTarget);
foreach ($presetMapping->mappings as $source => $classToTargetMapping) {
if (!$this->mappings->offsetExists($source)) {
/** @var \ArrayObject<class-string,object> */
$arrayObject = new \ArrayObject();
$this->mappings->offsetSet($source, $arrayObject);
}
}

return new self($presetMapping);
foreach ($classToTargetMapping as $class => $target) {
$this->mappings->offsetGet($source)?->offsetSet($class, $target);
}
}
}

/**
Expand Down
65 changes: 65 additions & 0 deletions src/Transformer/Context/PresetMappingFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

declare(strict_types=1);

/*
* This file is part of rekalogika/mapper package.
*
* (c) Priyadi Iman Nurcahyo <https://rekalogika.dev>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/

namespace Rekalogika\Mapper\Transformer\Context;

use Rekalogika\Mapper\ObjectCache\ObjectCache;
use Rekalogika\Mapper\Transformer\Model\SplObjectStorageWrapper;
use Rekalogika\Mapper\Util\ClassUtil;

final readonly class PresetMappingFactory
{
private function __construct()
{
}

public static function fromObjectCache(ObjectCache $objectCache): PresetMapping
{
$objectCacheWeakMap = $objectCache->getInternalMapping();

/** @var SplObjectStorageWrapper<object,\ArrayObject<class-string,object>> */
$presetMapping = new SplObjectStorageWrapper(new \SplObjectStorage());

/**
* @var object $source
* @var \ArrayObject<class-string,object> $classToTargetMapping
*/
foreach ($objectCacheWeakMap as $source => $classToTargetMapping) {
$newTargetClass = ClassUtil::determineRealClassFromPossibleProxy($source::class);
/** @var object */
$newTarget = $source;

/**
* @var string $targetClass
* @var object $target
*/
foreach ($classToTargetMapping as $targetClass => $target) {
if (!class_exists($targetClass)) {
continue;
}

$newSource = $target;

if (!$presetMapping->offsetExists($newSource)) {
/** @var \ArrayObject<class-string,object> */
$arrayObject = new \ArrayObject();
$presetMapping->offsetSet($newSource, $arrayObject);
}

$presetMapping->offsetGet($newSource)?->offsetSet($newTargetClass, $newTarget);
}
}

return new PresetMapping($presetMapping);
}
}
65 changes: 65 additions & 0 deletions tests/Fixtures/RememberingMapper/RememberingMapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

declare(strict_types=1);

/*
* This file is part of rekalogika/mapper package.
*
* (c) Priyadi Iman Nurcahyo <https://rekalogika.dev>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/

namespace Rekalogika\Mapper\Tests\Fixtures\RememberingMapper;

use Rekalogika\Mapper\Context\Context;
use Rekalogika\Mapper\Exception\UnexpectedValueException;
use Rekalogika\Mapper\MapperInterface;
use Rekalogika\Mapper\ObjectCache\ObjectCacheFactoryInterface;
use Rekalogika\Mapper\Transformer\Context\PresetMapping;
use Rekalogika\Mapper\Transformer\Context\PresetMappingFactory;
use Symfony\Contracts\Service\ResetInterface;

class RememberingMapper implements MapperInterface, ResetInterface
{
private PresetMapping $presetMapping;

public function __construct(
private MapperInterface $decorated,
private ObjectCacheFactoryInterface $objectCacheFactory
) {
$this->presetMapping = new PresetMapping();
}

public function reset(): void
{
$this->presetMapping = new PresetMapping();
}

public function map(object $source, object|string $target, ?Context $context = null): object
{
$objectCache = $this->objectCacheFactory->createObjectCache();

if ($context === null) {
$context = Context::create();
}

$context = $context->with($objectCache, $this->presetMapping);

$result = $this->decorated->map($source, $target, $context);

if (is_object($target)) {
$target = $target::class;
}

if (!$result instanceof $target) {
throw new UnexpectedValueException(sprintf('Expected instance of "%s", got "%s"', $target, get_class($result)));
}

$newPresetMapping = PresetMappingFactory::fromObjectCache($objectCache);
$this->presetMapping->mergeFrom($newPresetMapping);

return $result;
}
}
33 changes: 31 additions & 2 deletions tests/IntegrationTest/PresetMappingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
use Rekalogika\Mapper\Tests\Common\FrameworkTestCase;
use Rekalogika\Mapper\Tests\Fixtures\Scalar\ObjectWithScalarProperties;
use Rekalogika\Mapper\Tests\Fixtures\ScalarDto\ObjectWithScalarPropertiesDto;
use Rekalogika\Mapper\Transformer\Context\PresetMapping;
use Rekalogika\Mapper\Transformer\Context\PresetMappingFactory;
use Rekalogika\Mapper\TypeResolver\TypeResolverInterface;
use Rekalogika\Mapper\Util\TypeFactory;

Expand All @@ -41,11 +41,40 @@ public function testFromObjectCache(): void

$objectCache->saveTarget($source, $targetType, $target);

$presetMapping = PresetMapping::fromObjectCache($objectCache);
$presetMapping = PresetMappingFactory::fromObjectCache($objectCache);

$result = $presetMapping->findResult($target, $source::class);

$this->assertSame($source, $result);
}

public function testMerge(): void
{
$objectCache = $this->createObjectCache();

$source = new ObjectWithScalarProperties();
$targetType = TypeFactory::objectOfClass(ObjectWithScalarProperties::class);
$target = new ObjectWithScalarPropertiesDto();

$objectCache->saveTarget($source, $targetType, $target);

$presetMapping = PresetMappingFactory::fromObjectCache($objectCache);

$source2 = new ObjectWithScalarProperties();
$targetType2 = TypeFactory::objectOfClass(ObjectWithScalarProperties::class);
$target2 = new ObjectWithScalarPropertiesDto();

$objectCache->saveTarget($source2, $targetType2, $target2);

$presetMapping2 = PresetMappingFactory::fromObjectCache($objectCache);

$presetMapping->mergeFrom($presetMapping2);

$result = $presetMapping->findResult($target, $source::class);
$this->assertSame($source, $result);

$result = $presetMapping->findResult($target2, $source2::class);
$this->assertSame($source2, $result);
}

}
43 changes: 43 additions & 0 deletions tests/IntegrationTest/RememberingMapperTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

/*
* This file is part of rekalogika/mapper package.
*
* (c) Priyadi Iman Nurcahyo <https://rekalogika.dev>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/

namespace Rekalogika\Mapper\Tests\IntegrationTest;

use Rekalogika\Mapper\Tests\Common\FrameworkTestCase;
use Rekalogika\Mapper\Tests\Fixtures\RememberingMapper\RememberingMapper;
use Rekalogika\Mapper\Tests\Fixtures\Scalar\ObjectWithScalarProperties;
use Rekalogika\Mapper\Tests\Fixtures\Scalar\ObjectWithScalarPropertiesWithNullContents;
use Rekalogika\Mapper\Tests\Fixtures\ScalarDto\ObjectWithFloatPropertiesDto;
use Rekalogika\Mapper\Tests\Fixtures\ScalarDto\ObjectWithIntPropertiesDto;

class RememberingMapperTest extends FrameworkTestCase
{
public function testMapping(): void
{
$mapper = $this->get(RememberingMapper::class);

$source1 = new ObjectWithScalarProperties();
$target1 = ObjectWithFloatPropertiesDto::class;
$result1 = $mapper->map($source1, $target1);

$source2 = new ObjectWithScalarPropertiesWithNullContents();
$target2 = ObjectWithIntPropertiesDto::class;
$result2 = $mapper->map($source2, $target2);

$shouldbeSource1 = $mapper->map($result1, $source1::class);
$shouldbeSource2 = $mapper->map($result2, $source2::class);

$this->assertEquals($source1, $shouldbeSource1);
$this->assertEquals($source2, $shouldbeSource2);
}
}

0 comments on commit 4a80abf

Please sign in to comment.