Skip to content

Commit 64d31ed

Browse files
committed
bug #62417 [ObjectMapper] bypass lazy ghost with class transform (soyuka)
This PR was squashed before being merged into the 7.4 branch. Discussion ---------- [ObjectMapper] bypass lazy ghost with class transform | Q | A | ------------- | --- | Branch? | 7.4 | Bug fix? | yes | New feature? | no | Deprecations? | no | Issues | Fix #60610 | License | MIT This allows to fix #60610 in a way that you can create the target in a transformer: ```php #[Map(target: TargetEntity::class, transform: GetTargetEntityReference::class)] class SourceDto { public string $id; } class GetTargetEntityReference implements TransformCallableInterface { public function __construct( private readonly EntityManager $em, private readonly ObjectMapperMetadataFactoryInterface $metadata ){ } public function __invoke(mixed $value, object $source, ?object $target): mixed { $metadata = $this->metadata->create($value); \assert(count($metadata) === 1); return $this->entityManager->getReference($metadata[0]->target, $value->id); } } } ``` I consider this a bug fix as otherwise you're stuck with the Lazy Ghost. At least in a transform you can choose whether to map recursively or not (you can inject the ObjectMapper into a transformer to keep the same behavior). Commits ------- 6dfe8b7 [ObjectMapper] bypass lazy ghost with class transform
2 parents 685dde5 + 6dfe8b7 commit 64d31ed

File tree

8 files changed

+161
-1
lines changed

8 files changed

+161
-1
lines changed

src/Symfony/Component/ObjectMapper/ObjectMapper.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,8 +241,16 @@ private function getSourceValue(object $source, object $target, mixed $value, \W
241241
} elseif ($objectMap->offsetExists($value)) {
242242
$value = $objectMap[$value];
243243
} elseif (\PHP_VERSION_ID < 80400) {
244+
if ($mapTo->transform) {
245+
return $value;
246+
}
247+
244248
return ($this->objectMapper ?? $this)->map($value, $mapTo->target);
245249
} else {
250+
if ($mapTo->transform) {
251+
return $value;
252+
}
253+
246254
$refl = new \ReflectionClass($mapTo->target);
247255
$mapper = $this->objectMapper ?? $this;
248256

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\ServiceLoadedValue;
13+
14+
class LoadedValue
15+
{
16+
public function __construct(public string $name)
17+
{
18+
}
19+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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\ServiceLoadedValue;
13+
14+
class LoadedValueService
15+
{
16+
public function __construct(private ?LoadedValue $value = null)
17+
{
18+
}
19+
20+
public function load(): void
21+
{
22+
$this->value = new LoadedValue(name: 'loaded');
23+
}
24+
25+
public function get(): LoadedValue
26+
{
27+
return $this->value;
28+
}
29+
}
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\ServiceLoadedValue;
13+
14+
class LoadedValueTarget
15+
{
16+
public function __construct(public ?LoadedValue $relation = null)
17+
{
18+
}
19+
}
Lines changed: 33 additions & 0 deletions
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\ServiceLoadedValue;
13+
14+
use Symfony\Component\ObjectMapper\Metadata\ObjectMapperMetadataFactoryInterface;
15+
use Symfony\Component\ObjectMapper\TransformCallableInterface;
16+
17+
/**
18+
* @implements TransformCallableInterface<object,object>
19+
*/
20+
class ServiceLoadedValueTransformer implements TransformCallableInterface
21+
{
22+
public function __construct(private readonly LoadedValueService $serviceLoadedValue, private readonly ObjectMapperMetadataFactoryInterface $metadata)
23+
{
24+
}
25+
26+
public function __invoke(mixed $value, object $source, ?object $target): mixed
27+
{
28+
$metadata = $this->metadata->create($value);
29+
\assert(count($metadata) === 1);
30+
\assert($metadata[0]->target === LoadedValue::class);
31+
return $this->serviceLoadedValue->get();
32+
}
33+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Symfony\Component\ObjectMapper\Tests\Fixtures\ServiceLoadedValue;
4+
5+
use Symfony\Component\ObjectMapper\Attribute\Map;
6+
7+
#[Map(target: LoadedValueTarget::class)]
8+
final class ValueToMap
9+
{
10+
public ?ValueToMapRelation $relation;
11+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Symfony\Component\ObjectMapper\Tests\Fixtures\ServiceLoadedValue;
4+
5+
use Symfony\Component\ObjectMapper\Attribute\Map;
6+
7+
#[Map(target: LoadedValue::class, transform: ServiceLoadedValueTransformer::class)]
8+
class ValueToMapRelation
9+
{
10+
public function __construct(public string $name)
11+
{
12+
}
13+
}

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

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@
7070
use Symfony\Component\ObjectMapper\Tests\Fixtures\PromotedConstructorWithMetadata\Target as PromotedConstructorWithMetadataTarget;
7171
use Symfony\Component\ObjectMapper\Tests\Fixtures\Recursion\AB;
7272
use Symfony\Component\ObjectMapper\Tests\Fixtures\Recursion\Dto;
73+
use Symfony\Component\ObjectMapper\Tests\Fixtures\ServiceLoadedValue\LoadedValueService;
74+
use Symfony\Component\ObjectMapper\Tests\Fixtures\ServiceLoadedValue\ServiceLoadedValueTransformer;
75+
use Symfony\Component\ObjectMapper\Tests\Fixtures\ServiceLoadedValue\ValueToMap;
76+
use Symfony\Component\ObjectMapper\Tests\Fixtures\ServiceLoadedValue\ValueToMapRelation;
7377
use Symfony\Component\ObjectMapper\Tests\Fixtures\ServiceLocator\A as ServiceLocatorA;
7478
use Symfony\Component\ObjectMapper\Tests\Fixtures\ServiceLocator\B as ServiceLocatorB;
7579
use Symfony\Component\ObjectMapper\Tests\Fixtures\ServiceLocator\ConditionCallable;
@@ -90,7 +94,7 @@ public function testMap($expect, $args, array $deps = [])
9094
$mapper = new ObjectMapper(...$deps);
9195
$mapped = $mapper->map(...$args);
9296

93-
if (\PHP_VERSION_ID >= 80400 && isset($mapped->relation) && $mapped->relation instanceof D ) {
97+
if (\PHP_VERSION_ID >= 80400 && isset($mapped->relation) && $mapped->relation instanceof D) {
9498
$mapped->relation->baz;
9599
}
96100

@@ -583,4 +587,28 @@ public function testEmbedsAreLazyLoadedByDefault()
583587
$this->assertSame('Test User', $target->user->name);
584588
$this->assertFalse($refl->isUninitializedLazyObject($target->user));
585589
}
590+
591+
public function testSkipLazyGhostWithClassTransform()
592+
{
593+
$service = new LoadedValueService();
594+
$service->load();
595+
596+
$metadataFactory = new ReflectionObjectMapperMetadataFactory();
597+
$mapper = new ObjectMapper(
598+
metadataFactory: $metadataFactory,
599+
transformCallableLocator: $this->getServiceLocator([ServiceLoadedValueTransformer::class => new ServiceLoadedValueTransformer($service, $metadataFactory)])
600+
);
601+
602+
$value = new ValueToMap();
603+
$value->relation = new ValueToMapRelation('test');
604+
605+
$result = $mapper->map($value);
606+
if (\PHP_VERSION_ID >= 80400) {
607+
$refl = new \ReflectionClass($result->relation);
608+
$this->assertFalse($refl->isUninitializedLazyObject($result->relation));
609+
}
610+
611+
$this->assertSame($result->relation, $service->get());
612+
$this->assertSame($result->relation->name, 'loaded');
613+
}
586614
}

0 commit comments

Comments
 (0)