Skip to content

Commit 818e7e8

Browse files
soyukanicolas-grekas
authored andcommitted
[ObjectMapper] handle non existing property errors
1 parent d8a673a commit 818e7e8

File tree

5 files changed

+89
-1
lines changed

5 files changed

+89
-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 <[email protected]>
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 <[email protected]>
18+
*/
19+
class NoSuchPropertyException extends MappingException
20+
{
21+
}

src/Symfony/Component/ObjectMapper/ObjectMapper.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
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;
21+
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException as PropertyAccessorNoSuchPropertyException;
2022
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
2123

2224
/**
@@ -167,7 +169,19 @@ public function map(object $source, object|string|null $target = null): object
167169

168170
private function getRawValue(object $source, string $propertyName): mixed
169171
{
170-
return $this->propertyAccessor ? $this->propertyAccessor->getValue($source, $propertyName) : $source->{$propertyName};
172+
if ($this->propertyAccessor) {
173+
try {
174+
return $this->propertyAccessor->getValue($source, $propertyName);
175+
} catch (PropertyAccessorNoSuchPropertyException $e) {
176+
throw new NoSuchPropertyException($e->getMessage(), $e->getCode(), $e);
177+
}
178+
}
179+
180+
if (!property_exists($source, $propertyName) && !isset($source->{$propertyName})) {
181+
throw new NoSuchPropertyException(sprintf('The property "%s" does not exist on "%s".', $propertyName, get_debug_type($source)));
182+
}
183+
184+
return $source->{$propertyName};
171185
}
172186

173187
private function getSourceValue(object $source, object $target, mixed $value, \SplObjectStorage $objectMap, ?Mapping $mapping = null): mixed

src/Symfony/Component/ObjectMapper/ObjectMapperInterface.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Component\ObjectMapper\Exception\MappingException;
1515
use Symfony\Component\ObjectMapper\Exception\MappingTransformException;
16+
use Symfony\Component\ObjectMapper\Exception\NoSuchPropertyException;
1617

1718
/**
1819
* Object to object mapper.
@@ -33,6 +34,7 @@ interface ObjectMapperInterface
3334
*
3435
* @throws MappingException When the mapping configuration is wrong
3536
* @throws MappingTransformException When a transformation on an object does not return an object
37+
* @throws NoSuchPropertyException When a property does not exist
3638
*/
3739
public function map(object $source, object|string|null $target = null): object;
3840
}
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+
public static function isDefined($source): bool
17+
{
18+
return isset($source);
19+
}
20+
}

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

Lines changed: 31 additions & 0 deletions
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,17 @@ 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($key): bool
248+
{
249+
return 'name' === $key;
250+
}
251+
241252
public function __get(string $key): string
242253
{
243254
return match ($key) {
@@ -303,4 +314,24 @@ public function testMultipleTargetMapProperty()
303314
$this->assertEquals('donotmap', $c->foo);
304315
$this->assertEquals('foo', $c->doesNotExistInTargetB);
305316
}
317+
318+
public function testDefaultValueStdClass()
319+
{
320+
$this->expectException(NoSuchPropertyException::class);
321+
$u = new \stdClass();
322+
$u->id = 'abc';
323+
$mapper = new ObjectMapper();
324+
$b = $mapper->map($u, TargetDto::class);
325+
}
326+
327+
public function testDefaultValueStdClassWithPropertyInfo()
328+
{
329+
$u = new \stdClass();
330+
$u->id = 'abc';
331+
$mapper = new ObjectMapper(propertyAccessor: PropertyAccess::createPropertyAccessorBuilder()->disableExceptionOnInvalidPropertyPath()->getPropertyAccessor());
332+
$b = $mapper->map($u, TargetDto::class);
333+
$this->assertInstanceOf(TargetDto::class, $b);
334+
$this->assertSame('abc', $b->id);
335+
$this->assertNull($b->optional);
336+
}
306337
}

0 commit comments

Comments
 (0)