Skip to content

Commit eda649d

Browse files
authored
Merge pull request #320 from jolicode/feat/transformer-compute-value
feat(transformer): add a new interface to compute a value during code generation and use the fixed result during runtime for property transformer
2 parents eadca46 + 7bb338b commit eda649d

File tree

10 files changed

+127
-29
lines changed

10 files changed

+127
-29
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
- Add support for static callable in attribute transformer
1616
- Initial support for nested properties
1717
- Add support for object invokable transformer in attribute transformer
18+
- Add a new interface `PropertyTransformerComputeInterface` to allow property transformers with supports, to compute a value that will be fixed during code generation.
1819

1920
### Changed
2021
- [BC Break] `PropertyTransformerSupportInterface` does not use a `TypesMatching` anymore, you can get the type directly from `SourcePropertyMetadata` or `TargetPropertyMetadata`.

src/EventListener/ApiPlatform/JsonLdListener.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use AutoMapper\Transformer\ApiPlatform\JsonLdIdTransformer;
1717
use AutoMapper\Transformer\FixedValueTransformer;
1818
use AutoMapper\Transformer\PropertyTransformer\PropertyTransformer;
19+
use PhpParser\Node\Scalar;
1920

2021
final readonly class JsonLdListener
2122
{
@@ -60,7 +61,7 @@ public function __invoke(GenerateMapperEvent $event): void
6061
mapperMetadata: $event->mapperMetadata,
6162
source: new SourcePropertyMetadata('@context'),
6263
target: new TargetPropertyMetadata('@context'),
63-
transformer: new PropertyTransformer(JsonLdContextTransformer::class, ['forced_resource_class' => $event->mapperMetadata->source]),
64+
transformer: new PropertyTransformer(JsonLdContextTransformer::class, computedValueExpr: new Scalar\String_($event->mapperMetadata->source)),
6465
if: "(context['normalizer_format'] ?? false) === 'jsonld' and (context['jsonld_has_context'] ?? false) === false and (context['depth'] ?? 0) <= 1",
6566
disableGroupsCheck: true,
6667
);

src/Transformer/ApiPlatform/JsonLdContextTransformer.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,22 @@ public function __construct(
1717
) {
1818
}
1919

20-
public function transform(mixed $value, object|array $source, array $context): mixed
20+
public function transform(mixed $value, object|array $source, array $context, mixed $computed = null): mixed
2121
{
2222
if (!\is_object($source)) {
2323
return null;
2424
}
2525

26-
$resourceClass = $context['forced_resource_class'] ?? $this->resourceClassResolver->isResourceClass($source::class) ? $this->resourceClassResolver->getResourceClass($source) : null;
26+
$resourceClass = $computed ?? $this->resourceClassResolver->isResourceClass(
27+
$source::class
28+
) ? $this->resourceClassResolver->getResourceClass($source) : null;
2729

2830
if (null === $resourceClass) {
2931
if ($this->contextBuilder instanceof AnonymousContextBuilderInterface) {
30-
return $this->contextBuilder->getAnonymousResourceContext($source, ($context['output'] ?? []) + ['api_resource' => $context['api_resource'] ?? null]);
32+
return $this->contextBuilder->getAnonymousResourceContext(
33+
$source,
34+
($context['output'] ?? []) + ['api_resource' => $context['api_resource'] ?? null]
35+
);
3136
}
3237

3338
return null;

src/Transformer/PropertyTransformer/PrioritizedPropertyTransformerInterface.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
/**
88
* @experimental
99
*/
10-
interface PrioritizedPropertyTransformerInterface
10+
interface PrioritizedPropertyTransformerInterface extends PropertyTransformerSupportInterface
1111
{
1212
public function getPriority(): int;
1313
}

src/Transformer/PropertyTransformer/PropertyTransformer.php

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,43 +10,25 @@
1010
use AutoMapper\Transformer\TransformerInterface;
1111
use PhpParser\Node\Arg;
1212
use PhpParser\Node\Expr;
13+
use PhpParser\Node\Name;
1314
use PhpParser\Node\Scalar;
1415
use PhpParser\Node\Stmt;
15-
use PhpParser\Parser;
16-
use PhpParser\ParserFactory;
1716

1817
/**
1918
* @internal
2019
*/
21-
final readonly class PropertyTransformer implements TransformerInterface, AllowNullValueTransformerInterface
20+
final class PropertyTransformer implements TransformerInterface, AllowNullValueTransformerInterface
2221
{
23-
private Parser $parser;
24-
25-
/**
26-
* @param array<mixed> $extraContext
27-
*/
2822
public function __construct(
29-
private string $propertyTransformerId,
30-
private array $extraContext = [],
31-
?Parser $parser = null,
23+
private readonly string $propertyTransformerId,
24+
private ?Expr $computedValueExpr = null,
3225
) {
33-
$this->parser = $parser ?? (new ParserFactory())->createForHostVersion();
3426
}
3527

3628
public function transform(Expr $input, Expr $target, PropertyMetadata $propertyMapping, UniqueVariableScope $uniqueVariableScope, Expr $source, ?Expr $existingValue = null): array
3729
{
3830
$context = new Expr\Variable('context');
39-
40-
if ($this->extraContext) {
41-
$expr = $this->parser->parse('<?php ' . var_export($this->extraContext, true) . ';')[0] ?? null;
42-
43-
if ($expr instanceof Stmt\Expression) {
44-
$context = new Expr\BinaryOp\Plus(
45-
$context,
46-
$expr->expr
47-
);
48-
}
49-
}
31+
$computeValueExpr = $this->computedValueExpr ?? new Expr\ConstFetch(new Name('null'));
5032

5133
$statements = [];
5234
$transformExpr = new Expr\MethodCall(
@@ -58,6 +40,7 @@ public function transform(Expr $input, Expr $target, PropertyMetadata $propertyM
5840
new Arg($input),
5941
new Arg($source),
6042
new Arg($context),
43+
new Arg($computeValueExpr),
6144
]
6245
);
6346

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace AutoMapper\Transformer\PropertyTransformer;
6+
7+
use AutoMapper\Metadata\MapperMetadata;
8+
use AutoMapper\Metadata\SourcePropertyMetadata;
9+
use AutoMapper\Metadata\TargetPropertyMetadata;
10+
11+
interface PropertyTransformerComputeInterface extends PropertyTransformerSupportInterface
12+
{
13+
/**
14+
* When implemented with a PropertyTransformerSupportInterface, this method is called to compute a value that would be passed to the `transform` method.
15+
*
16+
* This value is exported by using `var_export` and used directly in the generated code.
17+
*
18+
* @param SourcePropertyMetadata $source The source property metadata
19+
* @param TargetPropertyMetadata $target The target property metadata
20+
* @param MapperMetadata $mapperMetadata The mapper metadata
21+
*/
22+
public function compute(SourcePropertyMetadata $source, TargetPropertyMetadata $target, MapperMetadata $mapperMetadata): mixed;
23+
24+
/**
25+
* @param mixed $value the value of the property to transform, can be null if there is no way to read the data from the mapping
26+
* @param object|array<string, mixed> $source the source input on which the custom transformation applies
27+
* @param array<string, mixed> $context Context during mapping
28+
* @param mixed $computed The computed value from PropertyTransformerComputeInterface, if applicable, otherwise null
29+
*/
30+
public function transform(mixed $value, object|array $source, array $context, mixed $computed = null): mixed;
31+
}

src/Transformer/PropertyTransformer/PropertyTransformerFactory.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
use AutoMapper\Transformer\PrioritizedTransformerFactoryInterface;
1111
use AutoMapper\Transformer\TransformerFactoryInterface;
1212
use AutoMapper\Transformer\TransformerInterface;
13+
use PhpParser\Node\Stmt;
14+
use PhpParser\Parser;
15+
use PhpParser\ParserFactory;
1316

1417
/**
1518
* @internal
@@ -19,10 +22,14 @@ final class PropertyTransformerFactory implements PrioritizedTransformerFactoryI
1922
/** @var array<string, PropertyTransformerSupportInterface>|null */
2023
private $prioritizedPropertyTransformers;
2124

25+
private Parser $parser;
26+
2227
public function __construct(
2328
/** @var iterable<string, PropertyTransformerSupportInterface> */
2429
private readonly iterable $propertyTransformersSupportList,
30+
?Parser $parser = null,
2531
) {
32+
$this->parser = $parser ?? (new ParserFactory())->createForHostVersion();
2633
}
2734

2835
public function getPriority(): int
@@ -34,6 +41,14 @@ public function getTransformer(SourcePropertyMetadata $source, TargetPropertyMet
3441
{
3542
foreach ($this->prioritizedPropertyTransformers() as $id => $propertyTransformer) {
3643
if ($propertyTransformer instanceof PropertyTransformerSupportInterface && $propertyTransformer->supports($source, $target, $mapperMetadata)) {
44+
if ($propertyTransformer instanceof PropertyTransformerComputeInterface) {
45+
$computedValueCode = $propertyTransformer->compute($source, $target, $mapperMetadata);
46+
$stmts = $this->parser->parse('<?php ' . var_export($computedValueCode, true) . ';');
47+
$computedValueExpr = $stmts && $stmts[0] instanceof Stmt\Expression ? $stmts[0]->expr : null;
48+
49+
return new PropertyTransformer($id, $computedValueExpr);
50+
}
51+
3752
return new PropertyTransformer($id);
3853
}
3954
}

src/Transformer/PropertyTransformer/PropertyTransformerSupportInterface.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
*
1414
* @experimental
1515
*/
16-
interface PropertyTransformerSupportInterface
16+
interface PropertyTransformerSupportInterface extends PropertyTransformerInterface
1717
{
1818
/**
1919
* When implemented with a PropertyTransformerInterface, this method is called to check if the transformer supports the given properties.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
AutoMapper\Tests\AutoMapperTest\TransformerWithComputedValue\Foo {
2+
+foo: "computed value"
3+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace AutoMapper\Tests\AutoMapperTest\TransformerWithComputedValue;
6+
7+
use AutoMapper\Metadata\MapperMetadata;
8+
use AutoMapper\Metadata\SourcePropertyMetadata;
9+
use AutoMapper\Metadata\TargetPropertyMetadata;
10+
use AutoMapper\Tests\AutoMapperBuilder;
11+
use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerComputeInterface;
12+
use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerInterface;
13+
use AutoMapper\Transformer\PropertyTransformer\PropertyTransformerSupportInterface;
14+
15+
class FooDto
16+
{
17+
public string $foo;
18+
}
19+
20+
class Foo
21+
{
22+
public string $foo;
23+
}
24+
25+
class ComputeValueTransformer implements PropertyTransformerInterface, PropertyTransformerSupportInterface, PropertyTransformerComputeInterface
26+
{
27+
public function transform(mixed $value, object|array $source, array $context, mixed $computed = null): mixed
28+
{
29+
return $computed ?? $value;
30+
}
31+
32+
public function supports(
33+
SourcePropertyMetadata $source,
34+
TargetPropertyMetadata $target,
35+
MapperMetadata $mapperMetadata,
36+
): bool {
37+
return true;
38+
}
39+
40+
public function compute(
41+
SourcePropertyMetadata $source,
42+
TargetPropertyMetadata $target,
43+
MapperMetadata $mapperMetadata,
44+
): mixed {
45+
return 'computed value';
46+
}
47+
}
48+
49+
$fooDto = new FooDto();
50+
$fooDto->foo = 'original value';
51+
52+
$mapper = AutoMapperBuilder::buildAutoMapper(
53+
propertyTransformers: [new ComputeValueTransformer()]
54+
);
55+
56+
return $mapper->map(
57+
$fooDto,
58+
Foo::class,
59+
);

0 commit comments

Comments
 (0)