8000 [ObjectMapper] handle non existing property errors · symfony/symfony@88a4a59 · GitHub
[go: up one dir, main page]

Skip to content

Commit 88a4a59

Browse files
committed
[ObjectMapper] handle non existing property errors
1 parent ae30d7c commit 88a4a59

File tree

4 files changed

+84
-1
lines changed

4 files changed

+84
-1
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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\Exception;
13+
14+
/**
15+
* Thrown when a property cannot be found.
16+
*
17+
* @author Antoine Bluchet <soyuka@gmail.com>
18+
*/
19+
class NoSuchPropertyException extends MappingException
20+
{
21+
}

src/Symfony/Component/ObjectMapper/ObjectMapper.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Psr\Container\ContainerInterface;
1515
use Symfony\Component\ObjectMapper\Exception\MappingException;
1616
use Symfony\Component\ObjectMapper\Exception\MappingTransformException;
17+
use Symfony\Component\ObjectMapper\Exception\NoSuchPropertyException;
1718
use Symfony\Component\ObjectMapper\Metadata\Mapping;
1819
use Symfony\Component\ObjectMapper\Metadata\ObjectMapperMetadataFactoryInterface;
1920
use Symfony\Component\ObjectMapper\Metadata\ReflectionObjectMapperMetadataFactory;
@@ -167,7 +168,15 @@ public function map(object $source, object|string|null $target = null): object
167168

168169
private function getRawValue(object $source, string $propertyName): mixed
169170
{
170-
return $this->propertyAccessor ? $this->propertyAccessor->getValue($source, $propertyName) : $source->{$propertyName};
171+
if ($this->propertyAccessor) {
172+
return $this->propertyAccessor->getValue($source, $propertyName);
173+
}
174+
175+
if (!property_exists($source, $propertyName) && !(method_exists($source, '__isset') && true === $source->__isset($propertyName))) {
176+
throw new NoSuchPropertyException(sprintf('The property "%s" does not exist on "%s".', $propertyName, get_debug_type($source)));
177+
}
178+
179+
return $source->{$propertyName};
171180
}
172181

173182
private function getSourceValue(object $source, object $target, mixed $value, \SplObjectStorage $objectMap, ?Mapping $mapping = null): mixed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace Symfony\Component\ObjectMapper\Tests\Fixtures\DefaultValueStdClass;
4+
5+
use Symfony\Component\ObjectMapper\Attribute\Map;
6+
7+
class TargetDto
8+
{
9+
public function __construct(
10+
public string $id,
11+
#[Map(source: 'optional', if: [self::class, 'isDefined'])]
12+
public ?string $optional = null,
13+
)
14+
{
15+
}
16+
17+
public static function isDefined($source): bool {
18+
return isset($source);
19+
}
20+
}

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

Lines changed: 33 additions & 0 deletions
241
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Psr\Container\ContainerInterface;
1616
use Symfony\Component\ObjectMapper\Exception\MappingException;
1717
use Symfony\Component\ObjectMapper\Exception\MappingTransformException;
18+
use Symfony\Component\ObjectMapper\Exception\NoSuchPropertyException;
1819
use Symfony\Component\ObjectMapper\Metadata\Mapping;
1920
use Symfony\Component\ObjectMapper\Metadata\ObjectMapperMetadataFactoryInterface;
2021
use Symfony\Component\ObjectMapper\Metadata\ReflectionObjectMapperMetadataFactory;
@@ -28,6 +29,7 @@
2829
use Symfony\Component\ObjectMapper\Tests\Fixtures\DeeperRecursion\RecursiveDto;
2930
use Symfony\Component\ObjectMapper\Tests\Fixtures\DeeperRecursion\Relation;
3031
use Symfony\Component\ObjectMapper\Tests\Fixtures\DeeperRecursion\RelationDto;
32+
use Symfony\Component\ObjectMapper\Tests\Fixtures\DefaultValueStdClass\TargetDto;
3133
use Symfony\Component\ObjectMapper\Tests\Fixtures\Flatten\TargetUser;
3234
use Symfony\Component\ObjectMapper\Tests\Fixtures\Flatten\User;
3335
use Symfony\Component\ObjectMapper\Tests\Fixtures\Flatten\UserProfile;
@@ -236,8 +238,16 @@ public function testSourceOnly()
236238
$mapped = $mapper->map($a, SourceOnly::class);
237239
$this->assertInstanceOf(SourceOnly::class, $mapped);
238240
$this->assertSame('test', $mapped->mappedName);
241+
}
239242

243+
public function testSourceOnlyWithMagicMethods()
244+
{
245+
$mapper = new ObjectMapper();
240246
$a = new class {
247+
public function __isset(string $key): bool {
248+
return $key === 'name';
249+
}
250+
251
public function __get(string $key): string
242252
{
243253
return match ($key) {
@@ -303,4 +313,27 @@ public function testMultipleTargetMapProperty()
303313
$this->assertEquals('donotmap', $c->foo);
304314
$this->assertEquals('foo', $c->doesNotExistInTargetB);
305315
}
316+
317+
public function testDefaultValueStdClass()
318+
{
319+
$this->expectException(NoSuchPropertyException::class);
320+
$u = new \stdClass();
321+
$u->id = 'abc';
322+
$mapper = new ObjectMapper();
323+
$b = $mapper->map($u, TargetDto::class);
324+
$this->assertInstanceOf(TargetDto::class, $b);
325+
$this->assertEquals('abc', $b->id);
326+
$this->assertEquals(null, $b->optional);
327+
}
328+
329+
public function testDefaultValueStdClassWithPropertyInfo()
330+
{
331+
$u = new \stdClass();
332+
$u->id = 'abc';
333+
$mapper = new ObjectMapper(propertyAccessor: PropertyAccess::createPropertyAccessorBuilder()->disableExceptionOnInvalidPropertyPath()->getPropertyAccessor());
334+
$b = $mapper->map($u, TargetDto::class);
335+
$this->assertInstanceOf(TargetDto::class, $b);
336+
$this->assertEquals('abc', $b->id);
337+
$this->assertEquals(null, $b->optional);
338+
}
306339
}

0 commit comments

Comments
 (0)
0