jolicode / jolicode/automapper

feature proposal: NoOp transformer

Open
#270 5 comments 0 reactions 0 assignees View on GitHub
Dominant language
PHP
Stars
217
Forks
26
Avg merge
1h 35m
Merged PRs (30d)
2

Description

Hi,

thank you for your work,

by default, automapper creates a clone of mapped objects properties.
This is an issue when mapping doctrine entities (typical use case: mapping an entity with one to many relationships to a symfony form model). The mapped entity object isn't managed by doctrine.

Using the `MaxDepth` attribute might bes an option in some cases, but doesn't allow fine control of the mapped values.

This feature proposal introduces a "No Operation" transformer that plop the original object property value in the target class property.

## Implementation proposal

Here is a possible (and working) but probably incomplete implementation

A `NoOp` attribute to identify properties to map as is (without recursion):

```php
getSourceUniqueType();

if (!$sourceUniqueType instanceof Type) {
return false;
}

$targetUniqueType = $types->getTargetUniqueType($sourceUniqueType);

if (!$targetUniqueType instanceof Type) {
return false;
}

$isSameClass = $this->isSameClass($sourceUniqueType, $targetUniqueType);
$hasSourceNoOpAttribute = $this->hasNoOpAttribute($source, $mapperMetadata);
$hasTargetNoOpAttribute = $this->hasNoOpAttribute($target, $mapperMetadata);

return $isSameClass && ($hasSourceNoOpAttribute || $hasTargetNoOpAttribute);
}

#[\Override]
public function transform(mixed $value, object|array $source, array $context): mixed
{
return $value;
}

private function isSameClass(Type $source, Type $target): bool
{
$sourceClassName = $source->getClassName();
$targetClassName = $target->getClassName();

return $sourceClassName === $targetClassName
&& null !== $sourceClassName
&& null !== $targetClassName;
}

private function hasNoOpAttribute(
SourcePropertyMetadata|TargetPropertyMetadata $propertyMetadata,
MapperMetadata $mapperMetadata,
): bool {
$propertyAttributes = $this->getMappedPropertyAttributes($propertyMetadata, $mapperMetadata);

foreach ($propertyAttributes as $attribute) {
if (NoOp::class === $attribute->getName()) {
return true;
}
}

return false;
}

/**
* @return \ReflectionAttribute[]
*/
private function getMappedPropertyAttributes(
SourcePropertyMetadata|TargetPropertyMetadata $propertyMetadata,
MapperMetadata $mapperMetadata,
): array {
$propertyName = $propertyMetadata->property;

$reflectionClass = match (true) {
$propertyMetadata instanceof SourcePropertyMetadata => $mapperMetadata->sourceReflectionClass,
$propertyMetadata instanceof TargetPropertyMetadata => $mapperMetadata->targetReflectionClass,
default => throw new \LogicException('Invalid property metadata type'),
};

if (!$reflectionClass instanceof \ReflectionClass) {
return [];
}

return $this->getPropertyAttributes($reflectionClass, $propertyName);
}

/**
* @return \ReflectionAttribute[]
*/
private function getPropertyAttributes(
\ReflectionClass $reflectionClass,
string $propertyName,
): array {
$reflectionProperty = $this->getReflectionProperty($reflectionClass, $propertyName);

if ($reflectionProperty instanceof \ReflectionProperty) {
return $reflectionProperty->getAttributes();
}

return [];
}

private function getReflectionProperty(
\ReflectionClass $reflectionClass,
string $propertyName,
): ?\ReflectionProperty {
if (!$reflectionClass->hasProperty($propertyName)) {
return null;
}

return $reflectionClass->getProperty($propertyName);
}
}
```

## Usage

Consider a `BlogPost` entity with a mandatory `Category` property:

```php

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.