Skip to content

Commit

Permalink
refactor: Move mapping logic in ObjectToObjectTransformer` to its own…
Browse files Browse the repository at this point in the history
… service.
  • Loading branch information
priyadi committed Jan 16, 2024
1 parent b1ee292 commit fa874c2
Show file tree
Hide file tree
Showing 8 changed files with 286 additions and 89 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
* dx: Clarity.
* style: Exception messages.
* feat: Uses transformer class names as service IDs for easier decoration.
* refactor: Move mapping logic in `ObjectToObjectTransformer` to its own
service.

## 0.5.12

Expand Down
12 changes: 11 additions & 1 deletion config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
use Rekalogika\Mapper\Transformer\DateTimeTransformer;
use Rekalogika\Mapper\Transformer\InheritanceMapTransformer;
use Rekalogika\Mapper\Transformer\NullTransformer;
use Rekalogika\Mapper\Transformer\ObjectMappingResolver\ObjectMappingResolver;
use Rekalogika\Mapper\Transformer\ObjectToArrayTransformer;
use Rekalogika\Mapper\Transformer\ObjectToObjectTransformer;
use Rekalogika\Mapper\Transformer\ObjectToStringTransformer;
Expand Down Expand Up @@ -138,9 +139,9 @@
'$propertyListExtractor' => service('rekalogika.mapper.property_info'),
'$propertyTypeExtractor' => service('rekalogika.mapper.property_info'),
'$propertyInitializableExtractor' => service('rekalogika.mapper.property_info'),
'$propertyAccessExtractor' => service('rekalogika.mapper.property_info'),
'$propertyAccessor' => service('property_accessor'),
'$typeResolver' => service('rekalogika.mapper.type_resolver'),
'$objectMappingResolver' => service('rekalogika.mapper.object_mapping_resolver'),
])
->tag('rekalogika.mapper.transformer', ['priority' => -900]);

Expand Down Expand Up @@ -186,6 +187,15 @@
service('rekalogika.mapper.type_resolver.caching.inner'),
]);

# object mapping resolver

$services
->set('rekalogika.mapper.object_mapping_resolver', ObjectMappingResolver::class)
->args([
service('rekalogika.mapper.property_info'),
service('rekalogika.mapper.property_info'),
]);

# transformer registry

$services
Expand Down
18 changes: 17 additions & 1 deletion src/MapperFactory/MapperFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
use Rekalogika\Mapper\Transformer\DateTimeTransformer;
use Rekalogika\Mapper\Transformer\InheritanceMapTransformer;
use Rekalogika\Mapper\Transformer\NullTransformer;
use Rekalogika\Mapper\Transformer\ObjectMappingResolver\Contracts\ObjectMappingResolverInterface;
use Rekalogika\Mapper\Transformer\ObjectMappingResolver\ObjectMappingResolver;
use Rekalogika\Mapper\Transformer\ObjectToArrayTransformer;
use Rekalogika\Mapper\Transformer\ObjectToObjectTransformer;
use Rekalogika\Mapper\Transformer\ObjectToStringTransformer;
Expand Down Expand Up @@ -94,6 +96,7 @@ class MapperFactory
private CacheItemPoolInterface $propertyInfoExtractorCache;
private null|(PropertyInfoExtractorInterface&PropertyInitializableExtractorInterface) $propertyInfoExtractor = null;
private ?TypeResolverInterface $typeResolver = null;
private ?ObjectMappingResolverInterface $objectMappingResolver = null;
private ?MainTransformer $mainTransformer = null;
private ?MapperInterface $mapper = null;
private ?MappingFactoryInterface $mappingFactory = null;
Expand Down Expand Up @@ -275,9 +278,9 @@ protected function getObjectToObjectTransformer(): TransformerInterface
$this->getPropertyListExtractor(),
$this->getPropertyInfoExtractor(),
$this->getPropertyInitializableExtractor(),
$this->getPropertyAccessExtractor(),
$this->getPropertyAccessor(),
$this->getTypeResolver(),
$this->getObjectMappingResolver(),
);
}

Expand Down Expand Up @@ -404,6 +407,19 @@ protected function getTypeResolver(): TypeResolverInterface
return $this->typeResolver;
}

protected function getObjectMappingResolver(): ObjectMappingResolverInterface
{
if (null === $this->objectMappingResolver) {
$this->objectMappingResolver = new ObjectMappingResolver(
$this->getPropertyAccessExtractor(),
$this->getPropertyListExtractor(),
);
}

return $this->objectMappingResolver;
}


/**
* @return iterable<string,TransformerInterface>
*/
Expand Down
47 changes: 47 additions & 0 deletions src/Transformer/ObjectMappingResolver/Contracts/ObjectMapping.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?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\ObjectMappingResolver\Contracts;

use Symfony\Component\PropertyInfo\Type;

final class ObjectMapping
{
/**
* @param array<int,ObjectMappingEntry> $propertyMapping
*/
public function __construct(
private Type $sourceType,
private Type $targetType,
private array $propertyMapping,
) {
}

public function getSourceType(): Type
{
return $this->sourceType;
}

public function getTargetType(): Type
{
return $this->targetType;
}

/**
* @return \Traversable<int,ObjectMappingEntry>
*/
public function getPropertyMapping(): \Traversable
{
yield from $this->propertyMapping;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?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\ObjectMappingResolver\Contracts;

final class ObjectMappingEntry
{
public function __construct(
private string $sourcePath,
private string $targetPath,
) {
}

public function getSourcePath(): string
{
return $this->sourcePath;
}

public function getTargetPath(): string
{
return $this->targetPath;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?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\ObjectMappingResolver\Contracts;

use Rekalogika\Mapper\Context\Context;
use Symfony\Component\PropertyInfo\Type;

interface ObjectMappingResolverInterface
{
public function resolveObjectMapping(
Type $sourceType,
Type $targetType,
Context $context
): ObjectMapping;
}
121 changes: 121 additions & 0 deletions src/Transformer/ObjectMappingResolver/ObjectMappingResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?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\ObjectMappingResolver;

use Rekalogika\Mapper\Context\Context;
use Rekalogika\Mapper\Exception\InvalidArgumentException;
use Rekalogika\Mapper\Transformer\ObjectMappingResolver\Contracts\ObjectMapping;
use Rekalogika\Mapper\Transformer\ObjectMappingResolver\Contracts\ObjectMappingEntry;
use Rekalogika\Mapper\Transformer\ObjectMappingResolver\Contracts\ObjectMappingResolverInterface;
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
use Symfony\Component\PropertyInfo\Type;

final class ObjectMappingResolver implements ObjectMappingResolverInterface
{
public function __construct(
private PropertyAccessExtractorInterface $propertyAccessExtractor,
private PropertyListExtractorInterface $propertyListExtractor,
) {
}

public function resolveObjectMapping(
Type $sourceType,
Type $targetType,
Context $context
): ObjectMapping {
$sourceProperties = $this->listSourceAttributes($sourceType, $context);
$writableTargetProperties = $this
->listTargetWritableAttributes($targetType, $context);

$propertiesToMap = array_intersect($sourceProperties, $writableTargetProperties);

$results = [];

foreach ($propertiesToMap as $property) {
$results[] = new ObjectMappingEntry(
$property,
$property,
);
}

return new ObjectMapping(
$sourceType,
$targetType,
$results,
);
}

/**
* @return array<int,string>
* @todo cache result
*/
protected function listSourceAttributes(
Type $sourceType,
Context $context
): array {
$class = $sourceType->getClassName();

if (null === $class) {
throw new InvalidArgumentException('Cannot get class name from source type.', context: $context);
}

$attributes = $this->propertyListExtractor->getProperties($class);

if (null === $attributes) {
throw new InvalidArgumentException(sprintf('Cannot get properties from source class "%s".', $class), context: $context);
}

$readableAttributes = [];

foreach ($attributes as $attribute) {
if ($this->propertyAccessExtractor->isReadable($class, $attribute)) {
$readableAttributes[] = $attribute;
}
}

return $readableAttributes;
}

/**
* @return array<int,string>
* @todo cache result
*/
protected function listTargetWritableAttributes(
Type $targetType,
Context $context
): array {
$class = $targetType->getClassName();

if (null === $class) {
throw new InvalidArgumentException('Cannot get class name from source type.', context: $context);
}

$attributes = $this->propertyListExtractor->getProperties($class);

if (null === $attributes) {
throw new InvalidArgumentException(sprintf('Cannot get properties from target class "%s".', $class), context: $context);
}

$writableAttributes = [];

foreach ($attributes as $attribute) {
if ($this->propertyAccessExtractor->isWritable($class, $attribute)) {
$writableAttributes[] = $attribute;
}
}

return $writableAttributes;
}
}
Loading

0 comments on commit fa874c2

Please sign in to comment.