Skip to content

Commit

Permalink
feat: introduce custom transformers (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
nikophil authored Dec 18, 2023
1 parent 50b2840 commit 901a867
Show file tree
Hide file tree
Showing 32 changed files with 759 additions and 89 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
- [GH#10](https://github.com/jolicode/automapper/pull/10) Introduce custom transformers

## [8.1.0] - 2023-12-14
### Added
Expand Down
50 changes: 36 additions & 14 deletions src/AutoMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace AutoMapper;

use AutoMapper\Exception\NoMappingFoundException;
use AutoMapper\Extractor\ClassMethodToCallbackExtractor;
use AutoMapper\Extractor\FromSourceMappingExtractor;
use AutoMapper\Extractor\FromTargetMappingExtractor;
use AutoMapper\Extractor\MapToContextPropertyInfoExtractorDecorator;
Expand All @@ -15,6 +16,8 @@
use AutoMapper\Transformer\ArrayTransformerFactory;
use AutoMapper\Transformer\BuiltinTransformerFactory;
use AutoMapper\Transformer\ChainTransformerFactory;
use AutoMapper\Transformer\CustomTransformer\CustomTransformerInterface;
use AutoMapper\Transformer\CustomTransformer\CustomTransformersRegistry;
use AutoMapper\Transformer\DateTimeTransformerFactory;
use AutoMapper\Transformer\EnumTransformerFactory;
use AutoMapper\Transformer\MultipleTransformerFactory;
Expand Down Expand Up @@ -58,6 +61,7 @@ class AutoMapper implements AutoMapperInterface, AutoMapperRegistryInterface, Ma
public function __construct(
private readonly ClassLoaderInterface $classLoader,
private readonly ChainTransformerFactory $chainTransformerFactory,
public readonly CustomTransformersRegistry $customTransformersRegistry,
private readonly ?MapperGeneratorMetadataFactoryInterface $mapperConfigurationFactory = null
) {
}
Expand Down Expand Up @@ -147,6 +151,11 @@ public function bindTransformerFactory(TransformerFactoryInterface $transformerF
}
}

public function bindCustomTransformer(CustomTransformerInterface $customTransformer): void
{
$this->customTransformersRegistry->addCustomTransformer($customTransformer);
}

public static function create(
bool $mapPrivateProperties = false,
ClassLoaderInterface $loader = null,
Expand All @@ -167,11 +176,14 @@ public static function create(
$classMetadataFactory = new ClassMetadataFactory($loaderClass);

if (null === $loader) {
$loader = new EvalLoader(new Generator(
(new ParserFactory())->create(ParserFactory::PREFER_PHP7),
new ClassDiscriminatorFromClassMetadata($classMetadataFactory),
$allowReadOnlyTargetToPopulate
));
$loader = new EvalLoader(
new Generator(
new ClassMethodToCallbackExtractor(),
(new ParserFactory())->create(ParserFactory::PREFER_PHP7),
new ClassDiscriminatorFromClassMetadata($classMetadataFactory),
$allowReadOnlyTargetToPopulate
)
);
}

$flags = ReflectionExtractor::ALLOW_PUBLIC | ReflectionExtractor::ALLOW_PROTECTED | ReflectionExtractor::ALLOW_PRIVATE;
Expand All @@ -186,12 +198,15 @@ public static function create(
[new MapToContextPropertyInfoExtractorDecorator($reflectionExtractor)]
);

$customTransformerRegistry = new CustomTransformersRegistry();

$transformerFactory = new ChainTransformerFactory();
$sourceTargetMappingExtractor = new SourceTargetMappingExtractor(
$propertyInfoExtractor,
new MapToContextPropertyInfoExtractorDecorator($reflectionExtractor),
$reflectionExtractor,
$transformerFactory,
$customTransformerRegistry,
$classMetadataFactory
);

Expand All @@ -200,6 +215,7 @@ public static function create(
$reflectionExtractor,
$reflectionExtractor,
$transformerFactory,
$customTransformerRegistry,
$classMetadataFactory,
$nameConverter
);
Expand All @@ -209,19 +225,25 @@ public static function create(
new MapToContextPropertyInfoExtractorDecorator($reflectionExtractor),
$reflectionExtractor,
$transformerFactory,
$customTransformerRegistry,
$classMetadataFactory,
$nameConverter
);

$autoMapper = $autoRegister ? new self($loader, $transformerFactory, new MapperGeneratorMetadataFactory(
$sourceTargetMappingExtractor,
$fromSourceMappingExtractor,
$fromTargetMappingExtractor,
$classPrefix,
$attributeChecking,
$dateTimeFormat,
$mapPrivateProperties
)) : new self($loader, $transformerFactory);
$autoMapper = $autoRegister ? new self(
$loader,
$transformerFactory,
$customTransformerRegistry,
new MapperGeneratorMetadataFactory(
$sourceTargetMappingExtractor,
$fromSourceMappingExtractor,
$fromTargetMappingExtractor,
$classPrefix,
$attributeChecking,
$dateTimeFormat,
$mapPrivateProperties
),
) : new self($loader, $transformerFactory, $customTransformerRegistry);

$transformerFactory->addTransformerFactory(new MultipleTransformerFactory($transformerFactory));
$transformerFactory->addTransformerFactory(new NullableTransformerFactory($transformerFactory));
Expand Down
2 changes: 2 additions & 0 deletions src/AutoMapperRegistryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
* Allows to retrieve a mapper.
*
* @author Joel Wurtz <[email protected]>
*
* @internal
*/
interface AutoMapperRegistryInterface
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@
use PhpParser\ParserFactory;

/**
* Extracts the code of the given method from a given class and wraps it inside a closure, in order to inject it
* in the generated mappers.
*
* @author Nicolas Philippe <[email protected]>
* @author Baptiste Leduc <[email protected]>
*
* @internal
*/
final readonly class AstExtractor
final readonly class ClassMethodToCallbackExtractor
{
private Parser $parser;

Expand All @@ -31,9 +34,6 @@ public function __construct(?Parser $parser = null)
}

/**
* Extracts the code of the given method from a given class, and wraps it inside a closure, in order to inject it
* in the generated mappers.
*
* @param class-string $class
* @param Arg[] $inputParameters
*/
Expand Down Expand Up @@ -63,16 +63,6 @@ public function extract(string $class, string $method, array $inputParameters):
throw new InvalidArgumentException("Input parameters and method parameters in class \"{$class}\" do not match.");
}

foreach ($classMethod->getParams() as $key => $parameter) {
/** @var Expr\Variable $inputParameterValue */
$inputParameterValue = $inputParameters[$key]->value;

if ($parameter->var instanceof Expr\Variable && $inputParameterValue->name !== $parameter->var->name) {
$parameterName = \is_string($parameter->var->name) ? $parameter->var->name : 'N/A';
throw new InvalidArgumentException("Method parameter \"{$parameterName}\" does not match type \"{$inputParameters[$key]->getType()}\" from input parameter \"{$inputParameters[$key]->name}\" in \"{$class}::{$method}\" method.");
}
}

$closureParameters = [];
foreach ($classMethod->getParams() as $parameter) {
if ($parameter->var instanceof Expr\Variable && $parameter->type instanceof Identifier) {
Expand Down
13 changes: 10 additions & 3 deletions src/Extractor/FromSourceMappingExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use AutoMapper\Exception\InvalidMappingException;
use AutoMapper\MapperMetadataInterface;
use AutoMapper\Transformer\CustomTransformer\CustomTransformersRegistry;
use AutoMapper\Transformer\TransformerFactoryInterface;
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyReadInfo;
Expand All @@ -31,10 +32,11 @@ public function __construct(
PropertyReadInfoExtractorInterface $readInfoExtractor,
PropertyWriteInfoExtractorInterface $writeInfoExtractor,
TransformerFactoryInterface $transformerFactory,
CustomTransformersRegistry $customTransformerRegistry,
ClassMetadataFactoryInterface $classMetadataFactory = null,
private readonly ?AdvancedNameConverterInterface $nameConverter = null,
) {
parent::__construct($propertyInfoExtractor, $readInfoExtractor, $writeInfoExtractor, $transformerFactory, $classMetadataFactory);
parent::__construct($propertyInfoExtractor, $readInfoExtractor, $writeInfoExtractor, $transformerFactory, $customTransformerRegistry, $classMetadataFactory);
}

public function getPropertiesMapping(MapperMetadataInterface $mapperMetadata): array
Expand Down Expand Up @@ -66,10 +68,15 @@ public function getPropertiesMapping(MapperMetadataInterface $mapperMetadata): a
$targetTypes = [];

foreach ($sourceTypes as $type) {
$targetTypes[] = $this->transformType($mapperMetadata->getTarget(), $type);
$targetType = $this->transformType($mapperMetadata->getTarget(), $type);

if ($targetType) {
$targetTypes[] = $targetType;
}
}

$transformer = $this->transformerFactory->getTransformer($sourceTypes, $targetTypes, $mapperMetadata);
$transformer = $this->customTransformerRegistry->getCustomTransformerClass($mapperMetadata, $sourceTypes, $targetTypes, $property)
?? $this->transformerFactory->getTransformer($sourceTypes, $targetTypes, $mapperMetadata);

if (null === $transformer) {
continue;
Expand Down
13 changes: 10 additions & 3 deletions src/Extractor/FromTargetMappingExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use AutoMapper\Exception\InvalidMappingException;
use AutoMapper\MapperMetadataInterface;
use AutoMapper\Transformer\CustomTransformer\CustomTransformersRegistry;
use AutoMapper\Transformer\TransformerFactoryInterface;
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyReadInfo;
Expand All @@ -32,10 +33,11 @@ public function __construct(
PropertyReadInfoExtractorInterface $readInfoExtractor,
PropertyWriteInfoExtractorInterface $writeInfoExtractor,
TransformerFactoryInterface $transformerFactory,
CustomTransformersRegistry $customTransformerRegistry,
ClassMetadataFactoryInterface $classMetadataFactory = null,
private readonly ?AdvancedNameConverterInterface $nameConverter = null,
) {
parent::__construct($propertyInfoExtractor, $readInfoExtractor, $writeInfoExtractor, $transformerFactory, $classMetadataFactory);
parent::__construct($propertyInfoExtractor, $readInfoExtractor, $writeInfoExtractor, $transformerFactory, $customTransformerRegistry, $classMetadataFactory);
}

public function getPropertiesMapping(MapperMetadataInterface $mapperMetadata): array
Expand All @@ -61,10 +63,15 @@ public function getPropertiesMapping(MapperMetadataInterface $mapperMetadata): a
$sourceTypes = [];

foreach ($targetTypes as $type) {
$sourceTypes[] = $this->transformType($mapperMetadata->getSource(), $type);
$sourceType = $this->transformType($mapperMetadata->getSource(), $type);

if ($sourceType) {
$sourceTypes[] = $sourceType;
}
}

$transformer = $this->transformerFactory->getTransformer($sourceTypes, $targetTypes, $mapperMetadata);
$transformer = $this->customTransformerRegistry->getCustomTransformerClass($mapperMetadata, $sourceTypes, $targetTypes, $property)
?? $this->transformerFactory->getTransformer($sourceTypes, $targetTypes, $mapperMetadata);

if (null === $transformer) {
continue;
Expand Down
2 changes: 2 additions & 0 deletions src/Extractor/MappingExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace AutoMapper\Extractor;

use AutoMapper\Transformer\CustomTransformer\CustomTransformersRegistry;
use AutoMapper\Transformer\TransformerFactoryInterface;
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyReadInfo;
Expand All @@ -25,6 +26,7 @@ public function __construct(
protected readonly PropertyReadInfoExtractorInterface $readInfoExtractor,
protected readonly PropertyWriteInfoExtractorInterface $writeInfoExtractor,
protected readonly TransformerFactoryInterface $transformerFactory,
protected readonly CustomTransformersRegistry $customTransformerRegistry,
private readonly ?ClassMetadataFactoryInterface $classMetadataFactory = null,
) {
}
Expand Down
4 changes: 3 additions & 1 deletion src/Extractor/PropertyMapping.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace AutoMapper\Extractor;

use AutoMapper\Transformer\CustomTransformer\CustomTransformerInterface;
use AutoMapper\Transformer\TransformerInterface;

/**
Expand All @@ -17,7 +18,8 @@ public function __construct(
public readonly ReadAccessor $readAccessor,
public readonly ?WriteMutator $writeMutator,
public readonly ?WriteMutator $writeMutatorConstructor,
public readonly TransformerInterface $transformer,
/** @var TransformerInterface|class-string<CustomTransformerInterface> */
public readonly TransformerInterface|string $transformer,
public readonly string $property,
public readonly bool $checkExists = false,
public readonly ?array $sourceGroups = null,
Expand Down
8 changes: 5 additions & 3 deletions src/Extractor/SourceTargetMappingExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@ public function getPropertiesMapping(MapperMetadataInterface $mapperMetadata): a
continue;
}

$sourceTypes = $this->propertyInfoExtractor->getTypes($mapperMetadata->getSource(), $property);
$targetTypes = $this->propertyInfoExtractor->getTypes($mapperMetadata->getTarget(), $property);
$transformer = $this->transformerFactory->getTransformer($sourceTypes, $targetTypes, $mapperMetadata);
$sourceTypes = $this->propertyInfoExtractor->getTypes($mapperMetadata->getSource(), $property) ?? [];
$targetTypes = $this->propertyInfoExtractor->getTypes($mapperMetadata->getTarget(), $property) ?? [];

$transformer = $this->customTransformerRegistry->getCustomTransformerClass($mapperMetadata, $sourceTypes, $targetTypes, $property)
?? $this->transformerFactory->getTransformer($sourceTypes, $targetTypes, $mapperMetadata);

if (null === $transformer) {
continue;
Expand Down
Loading

0 comments on commit 901a867

Please sign in to comment.