8000 [ObjectMapper] Condition to target a specific class · symfony/symfony@c826e6d · GitHub
[go: up one dir, main page]

Skip to content

Commit c826e6d

Browse files
committed
[ObjectMapper] Condition to target a specific class
1 parent 1a8b82e commit c826e6d

File tree

11 files changed

+130
-16
lines changed

11 files changed

+130
-16
lines changed

src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/ObjectMapper/TransformCallable.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
*/
1919
final class TransformCallable implements TransformCallableInterface
2020
{
21-
public function __invoke(mixed $value, object $object): mixed
21+
public function __invoke(mixed $value, object $source, ?object $target): mixed
2222
{
2323
return 'transformed';
2424
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\ObjectMapper\Condition;
13+
14+
/**
15+
* @implements ConditionCallableInterface<object, object>
16+
*/
17+
final class TargetClass implements ConditionCallableInterface
18+
{
19+
/**
20+
* @param class-string $className
21+
*/
22+
public function __construct(private readonly string $className)
23+
{
24+
}
25+
26+
public function __invoke(mixed $value, object $source, ?object $target): bool
27+
{
28+
return $target instanceof $this->className;
29+
}
30+
}

src/Symfony/Component/ObjectMapper/ConditionCallableInterface.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* Service used by "Map::if".
1616
*
1717
* @template T of object
18+
* @template T2 of object
1819
*
1920
* @experimental
2021
*
@@ -25,6 +26,7 @@ interface ConditionCallableInterface
2526
/**
2627
* @param mixed $value The value being mapped
2728
* @param T $source The object we're working on
29+
* @param T2|null $target The target we're mapping to
2830
*/
29-
public function __invoke(mixed $value, object $source): bool;
31+
public function __invoke(mixed $value, object $source, ?object $target): bool;
3032
}

src/Symfony/Component/ObjectMapper/ObjectMapper.php

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public function map(object $source, object|string|null $target = null): object
7070

7171
$mappedTarget = $mappingToObject ? $target : $targetRefl->newInstanceWithoutConstructor();
7272
if ($map && $map->transform) {
73-
$mappedTarget = $this->applyTransforms($map, $mappedTarget, $mappedTarget);
73+
$mappedTarget = $this->applyTransforms($map, $mappedTarget, $mappedTarget, null);
7474

7575
if (!\is_object($mappedTarget)) {
7676
throw new MappingTransformException(\sprintf('Cannot map "%s" to a non-object target of type "%s".', get_debug_type($source), get_debug_type($mappedTarget)));
@@ -123,7 +123,7 @@ public function map(object $source, object|string|null $target = null): object
123123
}
124124

125125
$value = $this->getRawValue($source, $sourcePropertyName);
126-
if (($if = $mapping->if) && ($fn = $this->getCallable($if, $this->conditionCallableLocator)) && !$this->call($fn, $value, $source)) {
126+
if (($if = $mapping->if) && ($fn = $this->getCallable($if, $this->conditionCallableLocator)) && !$this->call($fn, $value, $source, $mappedTarget)) {
127127
continue;
128128
}
129129

@@ -173,16 +173,16 @@ private function getRawValue(object $source, string $propertyName): mixed
173173
private function getSourceValue(object $source, object $target, mixed $value, \SplObjectStorage $objectMap, ?Mapping $mapping = null): mixed
174174
{
175175
if ($mapping?->transform) {
176-
$value = $this->applyTransforms($mapping, $value, $source);
176+
$value = $this->applyTransforms($mapping, $value, $source, $target);
177177
}
178178

179179
if (
180180
\is_object($value)
181181
&& ($innerMetadata = $this->metadataFactory->create($value))
182-
&& ($mapTo = $this->getMapTarget($innerMetadata, $value, $source))
182+
&& ($mapTo = $this->getMapTarget($innerMetadata, $value, $source, $target))
183183
&& (\is_string($mapTo->target) && class_exists($mapTo->target))
184184
) {
185-
$value = $this->applyTransforms($mapTo, $value, $source);
185+
$value = $this->applyTransforms($mapTo, $value, $source, $target);
186186

187187
if ($value === $source) {
188188
$value = $target;
@@ -216,23 +216,23 @@ private function storeValue(string $propertyName, array &$mapToProperties, array
216216
/**
217217
* @param callable(): mixed $fn
218218
*/
219-
private function call(callable $fn, mixed $value, object $object): mixed
219+
private function call(callable $fn, mixed $value, object $source, ?object $target = null): mixed
220220
{
221221
if (\is_string($fn)) {
222222
return \call_user_func($fn, $value);
223223
}
224224

225-
return $fn($value, $object);
225+
return $fn($value, $source, $target);
226226
}
227227

228228
/**
229229
* @param Mapping[] $metadata
230230
*/
231-
private function getMapTarget(array $metadata, mixed $value, object $source): ?Mapping
231+
private function getMapTarget(array $metadata, mixed $value, object $source, ?object $target = null): ?Mapping
232232
{
233233
$mapTo = null;
234234
foreach ($metadata as $mapAttribute) {
235-
if (($if = $mapAttribute->if) && ($fn = $this->getCallable($if, $this->conditionCallableLocator)) && !$this->call($fn, $value, $source)) {
235+
if (($if = $mapAttribute->if) && ($fn = $this->getCallable($if, $this->conditionCallableLocator)) && !$this->call($fn, $value, $source, $target)) {
236236
continue;
237237
}
238238

@@ -242,7 +242,7 @@ private function getMapTarget(array $metadata, mixed $value, object $source): ?M
242242
return $mapTo;
243243
}
244244

245-
private function applyTransforms(Mapping $map, mixed $value, object $object): mixed
245+
private function applyTransforms(Mapping $map, mixed $value, object $source, ?object $target): mixed
246246
{
247247
if (!$transforms = $map->transform) {
248248
return $value;
@@ -256,7 +256,7 @@ private function applyTransforms(Mapping $map, mixed $value, object $object): mi
256256

257257
foreach ($transforms as $transform) {
258258
if ($fn = $this->getCallable($transform, $this->transformCallableLocator)) {
259-
$value = $this->call($fn, $value, $object);
259+
$value = $this->call($fn, $value, $source, $target);
260260
}
261261
}
262262

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\ObjectMapper\Tests\Fixtures\MultipleTargetProperty;
13+
14+
use Symfony\Component\ObjectMapper\Attribute\Map;
15+
use Symfony\Component\ObjectMapper\Condition\TargetClass;
16+
17+
#[Map(target: B::class)]
18+
#[Map(target: C::class)]
19+
class A
20+
{
21+
#[Map(target: 'foo', transform: 'strtoupper', if: new TargetClass(B::class))]
22+
#[Map(target: 'bar')]
23+
public string $something = 'test';
24+
25+
public string $doesNotExistInTargetB = 'foo';
26+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\ObjectMapper\Tests\Fixtures\MultipleTargetProperty;
13+
14+
class B
15+
{
16+
public string $foo;
17+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\ObjectMapper\Tests\Fixtures\MultipleTargetProperty;
13+
14+
class C
15+
{
16+
public string $foo = 'donotmap';
17+
public string $bar;
18+
public string $doesNotExistInTargetB;
19+
}

src/Symfony/Component/ObjectMapper/Tests/Fixtures/ServiceLocator/ConditionCallable.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
*/
1919
class ConditionCallable implements ConditionCallableInterface
2020
{
21-
public function __invoke(mixed $value, object $object): bool
21+
public function __invoke(mixed $value, object $source, ?object $target = null): bool
2222
{
2323
return 'ok' === $value;
2424
}

src/Symfony/Component/ObjectMapper/Tests/Fixtures/ServiceLocator/TransformCallable.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
*/
1919
class TransformCallable implements TransformCallableInterface
2020
{
21-
public function __invoke(mixed $value, object $object): mixed
21+
public function __invoke(mixed $value, object $source, ?object $target = null): mixed
2222
{
2323
return "transformed$value";
2424
}

src/Symfony/Component/ObjectMapper/Tests/ObjectMapperTest.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@
3838
use Symfony\Component\ObjectMapper\Tests\Fixtures\MapStruct\MapStructMapperMetadataFactory;
3939
use Symfony\Component\ObjectMapper\Tests\Fixtures\MapStruct\Source;
4040
use Symfony\Component\ObjectMapper\Tests\Fixtures\MapStruct\Target;
41+
use Symfony\Component\ObjectMapper\Tests\Fixtures\MultipleTargetProperty\A as MultipleTargetPropertyA;
42+
use Symfony\Component\ObjectMapper\Tests\Fixtures\MultipleTargetProperty\B as MultipleTargetPropertyB;
43+
use Symfony\Component\ObjectMapper\Tests\Fixtures\MultipleTargetProperty\C as MultipleTargetPropertyC;
4144
use Symfony\Component\ObjectMapper\Tests\Fixtures\MultipleTargets\A as MultipleTargetsA;
4245
use Symfony\Component\ObjectMapper\Tests\Fixtures\MultipleTargets\C as MultipleTargetsC;
4346
use Symfony\Component\ObjectMapper\Tests\Fixtures\Recursion\AB;
@@ -262,4 +265,19 @@ public function testTransformToWrongObject()
262265
$mapper = new ObjectMapper($metadata);
263266
$mapper->map($u);
264267
}
268+
269+
public function testMultipleTargetMapProperty()
270+
{
271+
$u = new MultipleTargetPropertyA();
272+
273+
$mapper = new ObjectMapper();
274+
$b = $mapper->map($u, MultipleTargetPropertyB::class);
275+
$this->assertInstanceOf(MultipleTargetPropertyB::class, $b);
276+
$this->assertEquals($b->foo, 'TEST');
277+
$c = $mapper->map($u, MultipleTargetPropertyC::class);
278+
$this->assertInstanceOf(MultipleTargetPropertyC::class, $c);
279+
$this->assertEquals($c->bar, 'test');
280+
$this->assertEquals($c->foo, 'donotmap');
281+
$this->assertEquals($c->doesNotExistInTargetB, 'foo');
282+
}
265283
}

src/Symfony/Component/ObjectMapper/TransformCallableInterface.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* Service used by "Map::transform".
1616
*
1717
* @template T of object
18+
* @template T2 of object
1819
*
1920
* @experimental
2021
*
@@ -25,6 +26,7 @@ interface TransformCallableInterface
2526
/**
2627
* @param mixed $value The value being mapped
2728
* @param T $source The object we're working on
29+
* @param T2|null $target The target we're mapping to
2830
*/
29-
public function __invoke(mixed $value, object $source): mixed;
31+
public function __invoke(mixed $value, object $source, ?object $target): mixed;
3032
}

0 commit comments

Comments
 (0)
0