Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 564b44b

Browse filesBrowse files
committed
[ObjectMapper] Target in the transform and condition callback
1 parent 21b4dd7 commit 564b44b
Copy full SHA for 564b44b

File tree

9 files changed

+117
-28
lines changed
Filter options

9 files changed

+117
-28
lines changed

‎src/Symfony/Component/ObjectMapper/Attribute/Map.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/ObjectMapper/Attribute/Map.php
+2-2
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
readonly class Map
2323
{
2424
/**
25-
* @param string|class-string|null $source The property or the class to map from
26-
* @param string|class-string|null $target The property or the class to map to
25+
* @param string|class-string|null $source The property or the class to map from
26+
* @param string|class-string|null $target The property or the class to map to
2727
* @param string|bool|callable(mixed, object): bool|null $if A boolean, a service id or a callable that instructs whether to map
2828
* @param (string|callable(mixed, object): mixed)|(string|callable(mixed, object): mixed)[]|null $transform A service id or a callable that transforms the value during mapping
2929
*/

‎src/Symfony/Component/ObjectMapper/ConditionCallableInterface.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/ObjectMapper/ConditionCallableInterface.php
+4-3
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@
2323
interface ConditionCallableInterface
2424
{
2525
/**
26-
* @param mixed $value The value being mapped
27-
* @param T $object The object we're working on
26+
* @param mixed $value The value being mapped
27+
* @param T $source The object we're working on
28+
* @param T|null $target The target object to which the value will be mapped
2829
*/
29-
public function __invoke(mixed $value, object $object): bool;
30+
public function __invoke(mixed $value, object $source, ?object $target): bool;
3031
}

‎src/Symfony/Component/ObjectMapper/ObjectMapper.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/ObjectMapper/ObjectMapper.php
+7-11
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,6 @@ final class ObjectMapper implements ObjectMapperInterface
3333
*/
3434
private ?\SplObjectStorage $objectMap = null;
3535

36-
/**
37-
* @param ContainerInterface $transformCallableLocator
38-
* @param ContainerInterface $conditionCallableLocator
39-
*/
4036
public function __construct(
4137
private readonly ObjectMapperMetadataFactoryInterface $metadataFactory = new ReflectionObjectMapperMetadataFactory(),
4238
private readonly ?PropertyAccessorInterface $propertyAccessor = null,
@@ -77,7 +73,7 @@ public function map(object $source, object|string|null $target = null): object
7773
$mappedTarget = $this->applyTransforms($map, $mappedTarget, $mappedTarget);
7874

7975
if (!\is_object($mappedTarget)) {
80-
throw new MappingTransformException(sprintf('Cannot map "%s" to a non-object target of type "%s".', get_debug_type($source), get_debug_type($mappedTarget)));
76+
throw new MappingTransformException(\sprintf('Cannot map "%s" to a non-object target of type "%s".', get_debug_type($source), get_debug_type($mappedTarget)));
8177
}
8278
}
8379

@@ -127,7 +123,7 @@ public function map(object $source, object|string|null $target = null): object
127123
}
128124

129125
$value = $this->getRawValue($source, $sourcePropertyName);
130-
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)) {
131127
continue;
132128
}
133129

@@ -183,7 +179,7 @@ private function getSourceValue(object $source, object $target, mixed $value, \S
183179
if (
184180
\is_object($value)
185181
&& ($innerMetadata = $this->metadataFactory->create($value))
186-
&& ($mapTo = $this->getMapTarget($innerMetadata, $value, $source))
182+
&& ($mapTo = $this->getMapTarget($innerMetadata, $value, $source, $target))
187183
&& (\is_string($mapTo->target) && class_exists($mapTo->target))
188184
) {
189185
$value = $this->applyTransforms($mapTo, $value, $source);
@@ -220,23 +216,23 @@ private function storeValue(string $propertyName, array &$mapToProperties, array
220216
/**
221217
* @param callable(): mixed $fn
222218
*/
223-
private function call(callable $fn, mixed $value, object $object): mixed
219+
private function call(callable $fn, mixed $value, object $source, ?object $target = null): mixed
224220
{
225221
if (\is_string($fn)) {
226222
return \call_user_func($fn, $value);
227223
}
228224

229-
return $fn($value, $object);
225+
return $fn($value, $source, $target);
230226
}
231227

232228
/**
233229
* @param Mapping[] $metadata
234230
*/
235-
private function getMapTarget(array $metadata, mixed $value, object $source): ?Mapping
231+
private function getMapTarget(array $metadata, mixed $value, object $source, ?object $target = null): ?Mapping
236232
{
237233
$mapTo = null;
238234
foreach ($metadata as $mapAttribute) {
239-
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)) {
240236
continue;
241237
}
242238

‎src/Symfony/Component/ObjectMapper/Tests/Fixtures/ClassWithoutTarget.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/ObjectMapper/Tests/Fixtures/ClassWithoutTarget.php
+9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
<?php
22

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+
312
namespace Symfony\Component\ObjectMapper\Tests\Fixtures;
413

514
class ClassWithoutTarget
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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+
16+
#[Map(target: B::class)]
17+
#[Map(target: C::class)]
18+
class A
19+
{
20+
#[Map(target: 'foo', transform: 'strtoupper', if: [self::class, 'targetsB'])]
21+
#[Map(target: 'bar')]
22+
public string $something = 'test';
23+
24+
public static function targetsB(mixed $value, object $source, ?object $target)
25+
{
26+
return $target instanceof B;
27+
}
28+
29+
public static function targetsA(mixed $value, object $source, ?object $target)
30+
{
31+
return $target instanceof self;
32+
}
33+
}
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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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+
}

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/ObjectMapper/Tests/ObjectMapperTest.php
+23-9
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;
@@ -100,7 +103,7 @@ public function testHasNothingToMapTo()
100103
{
101104
$this->expectException(MappingException::class);
102105
$this->expectExceptionMessage('Mapping target not found for source "class@anonymous".');
103-
(new ObjectMapper())->map(new class () {});
106+
(new ObjectMapper())->map(new class {});
104107
}
105108

106109
public function testHasNothingToMapToWithNamedClass()
@@ -194,7 +197,7 @@ public function testServiceLocator()
194197

195198
protected function getServiceLocator(array $factories): ContainerInterface
196199
{
197-
return new class ($factories) implements ContainerInterface {
200+
return new class($factories) implements ContainerInterface {
198201
public function __construct(private array $factories)
199202
{
200203
}
@@ -220,7 +223,7 @@ public function testSourceOnly(): void
220223
$this->assertInstanceOf(SourceOnly::class, $mapped);
221224
$this->assertSame('test', $mapped->mappedName);
222225

223-
$a = new class () {
226+
$a = new class {
224227
public function __get(string $key): string
225228
{
226229
return match ($key) {
@@ -235,32 +238,43 @@ public function __get(string $key): string
235238
$this->assertSame('test', $mapped->mappedName);
236239
}
237240

238-
239241
public function testTransformToWrongValueType(): void
240242
{
241243
$this->expectException(MappingTransformException::class);
242244
$this->expectExceptionMessage('Cannot map "stdClass" to a non-object target of type "string".');
243245

244-
$u = new \stdClass;
246+
$u = new \stdClass();
245247
$u->foo = 'bar';
246248

247249
$metadata = $this->createStub(ObjectMapperMetadataFactoryInterface::class);
248-
$metadata->method('create')->with($u)->willReturn([new Mapping(target: \stdClass::class, transform: fn() => 'str')]);
250+
$metadata->method('create')->with($u)->willReturn([new Mapping(target: \stdClass::class, transform: fn () => 'str')]);
249251
$mapper = new ObjectMapper($metadata);
250252
$mapper->map($u);
251253
}
252254

253255
public function testTransformToWrongObject(): void
254256
{
255257
$this->expectException(MappingException::class);
256-
$this->expectExceptionMessage(sprintf('Expected the mapped object to be an instance of "%s" but got "stdClass".', ClassWithoutTarget::class));
258+
$this->expectExceptionMessage(\sprintf('Expected the mapped object to be an instance of "%s" but got "stdClass".', ClassWithoutTarget::class));
257259

258-
$u = new \stdClass;
260+
$u = new \stdClass();
259261
$u->foo = 'bar';
260262

261263
$metadata = $this->createStub(ObjectMapperMetadataFactoryInterface::class);
262-
$metadata->method('create')->with($u)->willReturn([new Mapping(target: ClassWithoutTarget::class, transform: fn() => new \stdClass)]);
264+
$metadata->method('create')->with($u)->willReturn([new Mapping(target: ClassWithoutTarget::class, transform: fn () => new \stdClass())]);
263265
$mapper = new ObjectMapper($metadata);
264266
$mapper->map($u);
265267
}
268+
269+
public function testMultipleTargetMapProperty(): void
270+
{
271+
$u = new MultipleTargetPropertyA();
272+
273+
$mapper = new ObjectMapper();
274+
$this->assertInstanceOf(MultipleTargetPropertyB::class, $mapper->map($u, MultipleTargetPropertyB::class));
275+
$c = $mapper->map($u, MultipleTargetPropertyC::class);
276+
$this->assertInstanceOf(MultipleTargetPropertyC::class, $c);
277+
$this->assertEquals($c->bar, 'test');
278+
$this->assertEquals($c->foo, 'donotmap');
279+
}
266280
}

‎src/Symfony/Component/ObjectMapper/TransformCallableInterface.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/ObjectMapper/TransformCallableInterface.php
+4-3
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@
2323
interface TransformCallableInterface
2424
{
2525
/**
26-
* @param mixed $value The value being mapped
27-
* @param T $object The object we're working on
26+
* @param mixed $value The value being mapped
27+
* @param T $source The object we're working on
28+
* @param T|null $target The target object to which the value will be mapped
2829
*/
29-
public function __invoke(mixed $value, object $object): mixed;
30+
public function __invoke(mixed $value, object $source, ?object $target): mixed;
3031
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.