Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Types: better matching between types to better handle multiple types #75

Merged
merged 4 commits into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Event/PropertyMetadataEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace AutoMapper\Event;

use AutoMapper\Metadata\MapperMetadata;
use AutoMapper\Metadata\TypesMatching;
use AutoMapper\Transformer\TransformerInterface;

/**
Expand All @@ -16,6 +17,7 @@ public function __construct(
public readonly MapperMetadata $mapperMetadata,
public readonly SourcePropertyMetadata $source,
public readonly TargetPropertyMetadata $target,
public ?TypesMatching $types = null,
public ?int $maxDepth = null,
public ?TransformerInterface $transformer = null,
public ?bool $ignored = null,
Expand Down
1 change: 1 addition & 0 deletions src/EventListener/MapFromListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ private function addPropertyFromTarget(GenerateMapperEvent $event, MapFrom $mapF
$event->mapperMetadata,
$sourceProperty,
$targetProperty,
null,
$mapFrom->maxDepth,
$this->getTransformerFromMapAttribute($event->mapperMetadata->target, $mapFrom),
joelwurtz marked this conversation as resolved.
Show resolved Hide resolved
$mapFrom->ignore,
Expand Down
1 change: 1 addition & 0 deletions src/EventListener/MapToListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ private function addPropertyFromSource(GenerateMapperEvent $event, MapTo $mapTo,
$event->mapperMetadata,
$sourceProperty,
$targetProperty,
null,
$mapTo->maxDepth,
$this->getTransformerFromMapAttribute($event->mapperMetadata->source, $mapTo),
$mapTo->ignore,
Expand Down
9 changes: 5 additions & 4 deletions src/Extractor/FromSourceMappingExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace AutoMapper\Extractor;

use AutoMapper\Configuration;
use AutoMapper\Metadata\TypesMatching;
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface;
Expand All @@ -30,20 +31,20 @@ public function __construct(
parent::__construct($configuration, $propertyInfoExtractor, $readInfoExtractor, $writeInfoExtractor);
}

public function getTypes(string $source, string $sourceProperty, string $target, string $targetProperty): array
public function getTypes(string $source, string $sourceProperty, string $target, string $targetProperty): TypesMatching
{
$types = new TypesMatching();
$sourceTypes = $this->propertyInfoExtractor->getTypes($source, $sourceProperty) ?? [new Type(Type::BUILTIN_TYPE_NULL)];
$targetTypes = [];

foreach ($sourceTypes as $type) {
$targetType = $this->transformType($target, $type);

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

return [$sourceTypes, $targetTypes];
return $types;
}

private function transformType(string $target, Type $type = null): ?Type
Expand Down
9 changes: 5 additions & 4 deletions src/Extractor/FromTargetMappingExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace AutoMapper\Extractor;

use AutoMapper\Configuration;
use AutoMapper\Metadata\TypesMatching;
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyReadInfoExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyWriteInfoExtractorInterface;
Expand All @@ -30,20 +31,20 @@ public function __construct(
parent::__construct($configuration, $propertyInfoExtractor, $readInfoExtractor, $writeInfoExtractor);
}

public function getTypes(string $source, string $sourceProperty, string $target, string $targetProperty): array
public function getTypes(string $source, string $sourceProperty, string $target, string $targetProperty): TypesMatching
{
$types = new TypesMatching();
$targetTypes = $this->propertyInfoExtractor->getTypes($target, $targetProperty) ?? [];
$sourceTypes = [];

foreach ($targetTypes as $type) {
$sourceType = $this->transformType($source, $type);

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

return [$sourceTypes, $targetTypes];
return $types;
}

public function getReadAccessor(string $source, string $target, string $property): ?ReadAccessor
Expand Down
7 changes: 2 additions & 5 deletions src/Extractor/MappingExtractorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace AutoMapper\Extractor;

use Symfony\Component\PropertyInfo\Type;
use AutoMapper\Metadata\TypesMatching;

/**
* Extracts mapping.
Expand All @@ -22,10 +22,7 @@ interface MappingExtractorInterface
*/
public function getProperties(string $class): iterable;

/**
* @return array{0: Type[], 1: Type[]}
*/
public function getTypes(string $source, string $sourceProperty, string $target, string $targetProperty): array;
public function getTypes(string $source, string $sourceProperty, string $target, string $targetProperty): TypesMatching;
joelwurtz marked this conversation as resolved.
Show resolved Hide resolved

public function getDateTimeFormat(string $class, string $property): string;

Expand Down
6 changes: 4 additions & 2 deletions src/Extractor/SourceTargetMappingExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace AutoMapper\Extractor;

use AutoMapper\Metadata\TypesMatching;

/**
* Extracts mapping between two objects, only gives properties that have the same name.
*
Expand All @@ -13,11 +15,11 @@
*/
class SourceTargetMappingExtractor extends MappingExtractor
{
public function getTypes(string $source, string $sourceProperty, string $target, string $targetProperty): array
public function getTypes(string $source, string $sourceProperty, string $target, string $targetProperty): TypesMatching
{
$sourceTypes = $this->propertyInfoExtractor->getTypes($source, $sourceProperty) ?? [];
$targetTypes = $this->propertyInfoExtractor->getTypes($target, $targetProperty) ?? [];

return [$sourceTypes, $targetTypes];
return TypesMatching::fromSourceAndTargetTypes($sourceTypes, $targetTypes);
}
}
17 changes: 6 additions & 11 deletions src/Metadata/MetadataRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,7 @@ private function createGeneratorMetadata(MapperMetadata $mapperMetadata): Genera
$propertiesMapping = [];

foreach ($propertyEvents as $propertyMappedEvent) {
[$sourceTypes, $targetTypes] = $extractor->getTypes($mapperMetadata->source, $propertyMappedEvent->source->name, $mapperMetadata->target, $propertyMappedEvent->target->name);

// Create the source property metadata
if ($propertyMappedEvent->source->types === null) {
$propertyMappedEvent->source->types = $sourceTypes;
}

if ($propertyMappedEvent->source->accessor === null) {
$propertyMappedEvent->source->accessor = $extractor->getReadAccessor($mapperMetadata->source, $mapperMetadata->target, $propertyMappedEvent->source->name);
}
Expand All @@ -176,10 +170,6 @@ private function createGeneratorMetadata(MapperMetadata $mapperMetadata): Genera
}

// Create the target property metadata
if ($propertyMappedEvent->target->types === null) {
$propertyMappedEvent->target->types = $targetTypes;
}

if ($propertyMappedEvent->target->writeMutator === null) {
$propertyMappedEvent->target->writeMutator = $extractor->getWriteMutator($mapperMetadata->source, $mapperMetadata->target, $propertyMappedEvent->target->name, [
'enable_constructor_extraction' => false,
Expand All @@ -203,8 +193,12 @@ private function createGeneratorMetadata(MapperMetadata $mapperMetadata): Genera
$sourcePropertyMetadata = SourcePropertyMetadata::fromEvent($propertyMappedEvent->source);
$targetPropertyMetadata = TargetPropertyMetadata::fromEvent($propertyMappedEvent->target);

if (null === $propertyMappedEvent->types) {
$propertyMappedEvent->types = $extractor->getTypes($mapperMetadata->source, $propertyMappedEvent->source->name, $mapperMetadata->target, $propertyMappedEvent->target->name);
}

if (null === $propertyMappedEvent->transformer) {
$transformer = $this->transformerFactory->getTransformer($sourcePropertyMetadata, $targetPropertyMetadata, $mapperMetadata);
$transformer = $this->transformerFactory->getTransformer($propertyMappedEvent->types, $sourcePropertyMetadata, $targetPropertyMetadata, $mapperMetadata);

if (null === $transformer) {
continue;
Expand All @@ -220,6 +214,7 @@ private function createGeneratorMetadata(MapperMetadata $mapperMetadata): Genera
$propertiesMapping[] = new PropertyMetadata(
$sourcePropertyMetadata,
$targetPropertyMetadata,
$propertyMappedEvent->types,
$propertyMappedEvent->transformer,
$propertyMappedEvent->ignored,
$propertyMappedEvent->maxDepth,
Expand Down
1 change: 1 addition & 0 deletions src/Metadata/PropertyMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ final class PropertyMetadata
public function __construct(
public readonly SourcePropertyMetadata $source,
public readonly TargetPropertyMetadata $target,
public readonly TypesMatching $types,
public TransformerInterface $transformer,
public bool $isIgnored = false,
public ?int $maxDepth = null,
Expand Down
12 changes: 0 additions & 12 deletions src/Metadata/SourcePropertyMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

use AutoMapper\Event\SourcePropertyMetadata as SourcePropertyMetadataEvent;
use AutoMapper\Extractor\ReadAccessor;
use Symfony\Component\PropertyInfo\Type;

/**
* Source Property metadata.
Expand All @@ -18,11 +17,9 @@
final readonly class SourcePropertyMetadata
{
/**
* @param Type[] $types
* @param string[]|null $groups
*/
public function __construct(
public array $types,
public string $name,
public ?ReadAccessor $accessor = null,
public bool $checkExists = false,
Expand All @@ -31,18 +28,9 @@ public function __construct(
) {
}

/**
* @param Type[] $types
*/
public function withTypes(array $types): self
{
return new self($types, $this->name, $this->accessor, $this->checkExists, $this->groups);
}

public static function fromEvent(SourcePropertyMetadataEvent $metadata): self
{
return new self(
$metadata->types ?? [],
$metadata->name,
$metadata->accessor,
$metadata->checkExists ?? false,
Expand Down
12 changes: 0 additions & 12 deletions src/Metadata/TargetPropertyMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

use AutoMapper\Event\TargetPropertyMetadata as EventTargetPropertyMetadata;
use AutoMapper\Extractor\WriteMutator;
use Symfony\Component\PropertyInfo\Type;

/**
* Target Property metadata.
Expand All @@ -18,11 +17,9 @@
final readonly class TargetPropertyMetadata
{
/**
* @param Type[] $types
* @param string[]|null $groups
*/
public function __construct(
public array $types,
public string $name,
public ?WriteMutator $writeMutator = null,
public ?WriteMutator $writeMutatorConstructor = null,
Expand All @@ -31,18 +28,9 @@ public function __construct(
) {
}

/**
* @param Type[] $types
*/
public function withTypes(array $types): self
{
return new self($types, $this->name, $this->writeMutator, $this->writeMutatorConstructor, $this->groups, $this->dateTimeFormat);
}

public static function fromEvent(EventTargetPropertyMetadata $metadata): self
{
return new self(
$metadata->types ?? [],
$metadata->name,
$metadata->writeMutator,
$metadata->writeMutatorConstructor,
Expand Down
58 changes: 58 additions & 0 deletions src/Metadata/TypesMatching.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

declare(strict_types=1);

namespace AutoMapper\Metadata;

use Symfony\Component\PropertyInfo\Type;

/**
* @internal
*
* @extends \SplObjectStorage<Type, Type[]>
nikophil marked this conversation as resolved.
Show resolved Hide resolved
*/
final class TypesMatching extends \SplObjectStorage
{
public function getSourceUniqueType(): ?Type
{
if (0 === \count($this) || \count($this) > 1) {
return null;
}

// Get first type from the SplObjectStorage
$this->rewind();
$sourceType = $this->current();

if (!$sourceType instanceof Type) {
return null;
}

return $sourceType;
}

public function getTargetUniqueType(Type $sourceType): ?Type
{
$targetTypes = $this[$sourceType] ?? [];

if (0 === \count($targetTypes) || \count($targetTypes) > 1 || !$targetTypes[0] instanceof Type) {
return null;
}

return $targetTypes[0];
}

/**
* @param Type[] $sourceTypes
* @param Type[] $targetTypes
*/
public static function fromSourceAndTargetTypes(array $sourceTypes, array $targetTypes): self
{
$types = new self();

foreach ($sourceTypes as $sourceType) {
$types[$sourceType] = $targetTypes;
}

return $types;
}
}
2 changes: 1 addition & 1 deletion src/Transformer/AbstractArrayTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
abstract readonly class AbstractArrayTransformer implements TransformerInterface, DependentTransformerInterface
{
public function __construct(
private TransformerInterface $itemTransformer,
protected TransformerInterface $itemTransformer,
) {
}

Expand Down
14 changes: 8 additions & 6 deletions src/Transformer/AbstractUniqueTypeTransformerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use AutoMapper\Metadata\MapperMetadata;
use AutoMapper\Metadata\SourcePropertyMetadata;
use AutoMapper\Metadata\TargetPropertyMetadata;
use AutoMapper\Metadata\TypesMatching;
use Symfony\Component\PropertyInfo\Type;

/**
Expand All @@ -18,20 +19,21 @@
*/
abstract class AbstractUniqueTypeTransformerFactory implements TransformerFactoryInterface
{
public function getTransformer(SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface
public function getTransformer(TypesMatching $types, SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface
{
$sourceTypes = $source->types;
$targetTypes = $target->types;
$sourceType = $types->getSourceUniqueType();

if (0 === \count($sourceTypes) || \count($sourceTypes) > 1 || !$sourceTypes[0] instanceof Type) {
if (null === $sourceType) {
return null;
}

if (0 === \count($targetTypes) || \count($targetTypes) > 1 || !$targetTypes[0] instanceof Type) {
$targetType = $types->getTargetUniqueType($sourceType);

if (null === $targetType) {
return null;
}

return $this->createTransformer($sourceTypes[0], $targetTypes[0], $source, $target, $mapperMetadata);
return $this->createTransformer($sourceType, $targetType, $source, $target, $mapperMetadata);
}

abstract protected function createTransformer(Type $sourceType, Type $targetType, SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): ?TransformerInterface;
Expand Down
Loading
Loading