Skip to content

Commit

Permalink
Merge pull request #48 from jolicode/feat/transformer-factory-aware
Browse files Browse the repository at this point in the history
fix(transformer): change the way transformer factory are injected to make it work as soon as automapper is created
  • Loading branch information
joelwurtz authored Mar 11, 2024
2 parents f683a5f + 18a9f2e commit b0dcc81
Show file tree
Hide file tree
Showing 17 changed files with 239 additions and 109 deletions.
39 changes: 24 additions & 15 deletions src/AutoMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public function __construct(
public readonly CustomTransformersRegistry $customTransformersRegistry,
private readonly ?MapperGeneratorMetadataFactoryInterface $mapperConfigurationFactory = null,
) {
$this->chainTransformerFactory->setAutoMapperRegistry($this);
}

public function register(MapperGeneratorMetadataInterface $configuration): void
Expand Down Expand Up @@ -149,8 +150,13 @@ public function getMetadata(string $source, string $target): ?MapperGeneratorMet
return $this->metadata[$source][$target];
}

/**
* @deprecated since 8.2, will be removed in 9.0.
*/
public function bindTransformerFactory(TransformerFactoryInterface $transformerFactory): void
{
trigger_deprecation('jolicode/automapper', '8.2', 'The "%s()" method will be removed in version 9.0, transformer must be injected in the chain transformer factory constructor instead.', __METHOD__);

if (!$this->chainTransformerFactory->hasTransformerFactory($transformerFactory)) {
$this->chainTransformerFactory->addTransformerFactory($transformerFactory);
}
Expand Down Expand Up @@ -202,7 +208,24 @@ public static function create(

$customTransformerRegistry = new CustomTransformersRegistry();

$transformerFactory = new ChainTransformerFactory();
$factories = [
new MultipleTransformerFactory(),
new NullableTransformerFactory(),
new UniqueTypeTransformerFactory(),
new DateTimeTransformerFactory(),
new BuiltinTransformerFactory(),
new ArrayTransformerFactory(),
new ObjectTransformerFactory(),
new EnumTransformerFactory(),
new CustomTransformerFactory($customTransformerRegistry),
];

if (class_exists(AbstractUid::class)) {
$factories[] = new SymfonyUidTransformerFactory();
}

$transformerFactory = new ChainTransformerFactory($factories);

$sourceTargetMappingExtractor = new SourceTargetMappingExtractor(
$propertyInfoExtractor,
new MapToContextPropertyInfoExtractorDecorator($reflectionExtractor),
Expand Down Expand Up @@ -244,20 +267,6 @@ public static function create(
),
) : new self($loader, $transformerFactory, $customTransformerRegistry);

$transformerFactory->addTransformerFactory(new MultipleTransformerFactory($transformerFactory));
$transformerFactory->addTransformerFactory(new NullableTransformerFactory($transformerFactory));
$transformerFactory->addTransformerFactory(new UniqueTypeTransformerFactory($transformerFactory));
$transformerFactory->addTransformerFactory(new DateTimeTransformerFactory());
$transformerFactory->addTransformerFactory(new BuiltinTransformerFactory());
$transformerFactory->addTransformerFactory(new ArrayTransformerFactory($transformerFactory));
$transformerFactory->addTransformerFactory(new ObjectTransformerFactory($autoMapper));
$transformerFactory->addTransformerFactory(new EnumTransformerFactory());
$transformerFactory->addTransformerFactory(new CustomTransformerFactory($customTransformerRegistry));

if (class_exists(AbstractUid::class)) {
$transformerFactory->addTransformerFactory(new SymfonyUidTransformerFactory());
}

return $autoMapper;
}
}
17 changes: 17 additions & 0 deletions src/AutoMapperRegistryAwareInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace AutoMapper;

/**
* Allows to use a AutoMapperRegistry.
*
* @author Joel Wurtz <[email protected]>
*
* @internal
*/
interface AutoMapperRegistryAwareInterface
{
public function setAutoMapperRegistry(AutoMapperRegistryInterface $autoMapperRegistry): void;
}
20 changes: 20 additions & 0 deletions src/AutoMapperRegistryAwareTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace AutoMapper;

/**
* Helper when using the AutoMapperRegistryAwareInterface.
*
* @internal
*/
trait AutoMapperRegistryAwareTrait
{
protected AutoMapperRegistryInterface $autoMapperRegistry;

public function setAutoMapperRegistry(AutoMapperRegistryInterface $autoMapperRegistry): void
{
$this->autoMapperRegistry = $autoMapperRegistry;
}
}
7 changes: 2 additions & 5 deletions src/Transformer/ArrayTransformerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,9 @@
*
* @author Joel Wurtz <[email protected]>
*/
final class ArrayTransformerFactory extends AbstractUniqueTypeTransformerFactory implements PrioritizedTransformerFactoryInterface
final class ArrayTransformerFactory extends AbstractUniqueTypeTransformerFactory implements PrioritizedTransformerFactoryInterface, ChainTransformerFactoryAwareInterface
{
public function __construct(
private readonly ChainTransformerFactory $chainTransformerFactory,
) {
}
use ChainTransformerFactoryAwareTrait;

protected function createTransformer(Type $sourceType, Type $targetType, MapperMetadataInterface $mapperMetadata): ?TransformerInterface
{
Expand Down
118 changes: 86 additions & 32 deletions src/Transformer/ChainTransformerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,107 @@

namespace AutoMapper\Transformer;

use AutoMapper\AutoMapperRegistryAwareInterface;
use AutoMapper\AutoMapperRegistryInterface;
use AutoMapper\MapperMetadataInterface;

/**
* @author Joel Wurtz <[email protected]>
*/
final class ChainTransformerFactory implements TransformerPropertyFactoryInterface, TransformerFactoryInterface
final class ChainTransformerFactory implements TransformerPropertyFactoryInterface, TransformerFactoryInterface, AutoMapperRegistryAwareInterface
{
/** @var array<int, list<TransformerFactoryInterface|TransformerPropertyFactoryInterface>> */
private array $factories = [];
protected ?AutoMapperRegistryInterface $autoMapperRegistry = null;

/** @var list<TransformerFactoryInterface|TransformerPropertyFactoryInterface>|null */
private ?array $sorted = null;
/**
* @param array<TransformerFactoryInterface|TransformerPropertyFactoryInterface> $factories
*/
public function __construct(private array $factories = [])
{
foreach ($this->factories as $factory) {
if ($factory instanceof ChainTransformerFactoryAwareInterface) {
$factory->setChainTransformerFactory($this);
}
}

$this->sortFactories();
}

public function setAutoMapperRegistry(AutoMapperRegistryInterface $autoMapperRegistry): void
{
$this->autoMapperRegistry = $autoMapperRegistry;

foreach ($this->factories as $factory) {
if ($factory instanceof AutoMapperRegistryAwareInterface) {
$factory->setAutoMapperRegistry($autoMapperRegistry);
}
}
}

/**
* Biggest priority is MultipleTransformerFactory with 128, so default priority will be bigger in order to
* be used before it, 256 should be enough.
*
* @deprecated since 8.2, will be removed in 9.0. Pass the factory into the constructor instead
*/
public function addTransformerFactory(TransformerFactoryInterface|TransformerPropertyFactoryInterface $transformerFactory, int $priority = 256): void
{
$this->sorted = null;
trigger_deprecation('jolicode/automapper', '8.2', 'The "%s()" method will be removed in version 9.0, transformer must be injected in the constructor instead.', __METHOD__);

if ($transformerFactory instanceof AutoMapperRegistryAwareInterface && null !== $this->autoMapperRegistry) {
$transformerFactory->setAutoMapperRegistry($this->autoMapperRegistry);
}

if ($transformerFactory instanceof PrioritizedTransformerFactoryInterface) {
$priority = $transformerFactory->getPriority();
if ($transformerFactory instanceof ChainTransformerFactoryAwareInterface) {
$transformerFactory->setChainTransformerFactory($this);
}

if (!\array_key_exists($priority, $this->factories)) {
$this->factories[$priority] = [];
if (!$transformerFactory instanceof PrioritizedTransformerFactoryInterface) {
/** @var TransformerFactoryInterface|TransformerPropertyFactoryInterface $transformerFactory */
$transformerFactory = new class($transformerFactory, $priority) implements TransformerFactoryInterface, TransformerPropertyFactoryInterface, PrioritizedTransformerFactoryInterface {
public function __construct(private readonly TransformerFactoryInterface|TransformerPropertyFactoryInterface $transformerFactory, private readonly int $priority)
{
}

public function getTransformer(?array $sourceTypes, ?array $targetTypes, MapperMetadataInterface $mapperMetadata): ?TransformerInterface
{
if ($this->transformerFactory instanceof TransformerFactoryInterface) {
return $this->transformerFactory->getTransformer($sourceTypes, $targetTypes, $mapperMetadata);
}

return null;
}

public function getPropertyTransformer(?array $sourceTypes, ?array $targetTypes, MapperMetadataInterface $mapperMetadata, string $property): ?TransformerInterface
{
if ($this->transformerFactory instanceof TransformerPropertyFactoryInterface) {
return $this->transformerFactory->getPropertyTransformer($sourceTypes, $targetTypes, $mapperMetadata, $property);
}

return $this->transformerFactory->getTransformer($sourceTypes, $targetTypes, $mapperMetadata);
}

public function getPriority(): int
{
return $this->priority;
}
};
}
$this->factories[$priority][] = $transformerFactory;

$this->factories[] = $transformerFactory;
$this->sortFactories();
}

/**
* @deprecated since 8.2, will be removed in 9.0.
*/
public function hasTransformerFactory(TransformerFactoryInterface $transformerFactory): bool
{
trigger_deprecation('jolicode/automapper', '8.2', 'The "%s()" method will be removed in version 9.0, transformer must be injected in the constructor instead.', __METHOD__);

$this->sortFactories();

$transformerFactoryClass = $transformerFactory::class;
foreach ($this->sorted ?? [] as $factory) {
foreach ($this->factories as $factory) {
if (is_a($factory, $transformerFactoryClass)) {
return true;
}
Expand All @@ -51,9 +115,7 @@ public function hasTransformerFactory(TransformerFactoryInterface $transformerFa

public function getPropertyTransformer(?array $sourceTypes, ?array $targetTypes, MapperMetadataInterface $mapperMetadata, string $property): ?TransformerInterface
{
$this->sortFactories();

foreach ($this->sorted ?? [] as $factory) {
foreach ($this->factories as $factory) {
if ($factory instanceof TransformerPropertyFactoryInterface) {
$transformer = $factory->getPropertyTransformer($sourceTypes, $targetTypes, $mapperMetadata, $property);
} else {
Expand All @@ -70,17 +132,13 @@ public function getPropertyTransformer(?array $sourceTypes, ?array $targetTypes,

public function getTransformer(?array $sourceTypes, ?array $targetTypes, MapperMetadataInterface $mapperMetadata): ?TransformerInterface
{
$this->sortFactories();

foreach ($this->sorted ?? [] as $factory) {
$transformer = null;

foreach ($this->factories as $factory) {
if ($factory instanceof TransformerFactoryInterface) {
$transformer = $factory->getTransformer($sourceTypes, $targetTypes, $mapperMetadata);
}

if (null !== $transformer) {
return $transformer;
if (null !== $transformer) {
return $transformer;
}
}
}

Expand All @@ -89,15 +147,11 @@ public function getTransformer(?array $sourceTypes, ?array $targetTypes, MapperM

private function sortFactories(): void
{
if (null === $this->sorted) {
$this->sorted = [];
krsort($this->factories);
usort($this->factories, static function (TransformerPropertyFactoryInterface|TransformerFactoryInterface $a, TransformerPropertyFactoryInterface|TransformerFactoryInterface $b) {
$aPriority = $a instanceof PrioritizedTransformerFactoryInterface ? $a->getPriority() : 256;
$bPriority = $b instanceof PrioritizedTransformerFactoryInterface ? $b->getPriority() : 256;

foreach ($this->factories as $prioritisedFactories) {
foreach ($prioritisedFactories as $factory) {
$this->sorted[] = $factory;
}
}
}
return $bPriority <=> $aPriority;
});
}
}
15 changes: 15 additions & 0 deletions src/Transformer/ChainTransformerFactoryAwareInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace AutoMapper\Transformer;

/**
* Allows to use a chain transformer factory.
*
* @author Joel Wurtz <[email protected]>
*/
interface ChainTransformerFactoryAwareInterface
{
public function setChainTransformerFactory(ChainTransformerFactory $chainTransformerFactory): void;
}
15 changes: 15 additions & 0 deletions src/Transformer/ChainTransformerFactoryAwareTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace AutoMapper\Transformer;

trait ChainTransformerFactoryAwareTrait
{
protected ChainTransformerFactory $chainTransformerFactory;

public function setChainTransformerFactory(ChainTransformerFactory $chainTransformerFactory): void
{
$this->chainTransformerFactory = $chainTransformerFactory;
}
}
7 changes: 2 additions & 5 deletions src/Transformer/MultipleTransformerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,9 @@
/**
* @author Joel Wurtz <[email protected]>
*/
final readonly class MultipleTransformerFactory implements TransformerFactoryInterface, PrioritizedTransformerFactoryInterface
final class MultipleTransformerFactory implements TransformerFactoryInterface, PrioritizedTransformerFactoryInterface, ChainTransformerFactoryAwareInterface
{
public function __construct(
private ChainTransformerFactory $chainTransformerFactory,
) {
}
use ChainTransformerFactoryAwareTrait;

public function getTransformer(?array $sourceTypes, ?array $targetTypes, MapperMetadataInterface $mapperMetadata): ?TransformerInterface
{
Expand Down
7 changes: 2 additions & 5 deletions src/Transformer/NullableTransformerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,9 @@
/**
* @author Joel Wurtz <[email protected]>
*/
final readonly class NullableTransformerFactory implements TransformerFactoryInterface, PrioritizedTransformerFactoryInterface
final class NullableTransformerFactory implements TransformerFactoryInterface, PrioritizedTransformerFactoryInterface, ChainTransformerFactoryAwareInterface
{
public function __construct(
private ChainTransformerFactory $chainTransformerFactory,
) {
}
use ChainTransformerFactoryAwareTrait;

public function getTransformer(?array $sourceTypes, ?array $targetTypes, MapperMetadataInterface $mapperMetadata): ?TransformerInterface
{
Expand Down
12 changes: 5 additions & 7 deletions src/Transformer/ObjectTransformerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,17 @@

namespace AutoMapper\Transformer;

use AutoMapper\AutoMapperRegistryInterface;
use AutoMapper\AutoMapperRegistryAwareInterface;
use AutoMapper\AutoMapperRegistryAwareTrait;
use AutoMapper\MapperMetadataInterface;
use Symfony\Component\PropertyInfo\Type;

/**
* @author Joel Wurtz <[email protected]>
*/
final class ObjectTransformerFactory extends AbstractUniqueTypeTransformerFactory implements PrioritizedTransformerFactoryInterface
final class ObjectTransformerFactory extends AbstractUniqueTypeTransformerFactory implements PrioritizedTransformerFactoryInterface, AutoMapperRegistryAwareInterface
{
public function __construct(
private readonly AutoMapperRegistryInterface $autoMapper,
) {
}
use AutoMapperRegistryAwareTrait;

protected function createTransformer(Type $sourceType, Type $targetType, MapperMetadataInterface $mapperMetadata): ?TransformerInterface
{
Expand All @@ -36,7 +34,7 @@ protected function createTransformer(Type $sourceType, Type $targetType, MapperM
$targetTypeName = $targetType->getClassName();
}

if (null !== $sourceTypeName && null !== $targetTypeName && $this->autoMapper->hasMapper($sourceTypeName, $targetTypeName)) {
if (null !== $sourceTypeName && null !== $targetTypeName && $this->autoMapperRegistry->hasMapper($sourceTypeName, $targetTypeName)) {
return new ObjectTransformer($sourceType, $targetType);
}

Expand Down
Loading

0 comments on commit b0dcc81

Please sign in to comment.