diff --git a/src/Symfony/Component/ObjectMapper/Exception/NoSuchPropertyException.php b/src/Symfony/Component/ObjectMapper/Exception/NoSuchPropertyException.php new file mode 100644 index 0000000000000..3b5df303dcc1f --- /dev/null +++ b/src/Symfony/Component/ObjectMapper/Exception/NoSuchPropertyException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ObjectMapper\Exception; + +/** + * Thrown when a property cannot be found. + * + * @author Antoine Bluchet + */ +class NoSuchPropertyException extends MappingException +{ +} diff --git a/src/Symfony/Component/ObjectMapper/ObjectMapper.php b/src/Symfony/Component/ObjectMapper/ObjectMapper.php index 69f02fb7f1160..654ae047c59f3 100644 --- a/src/Symfony/Component/ObjectMapper/ObjectMapper.php +++ b/src/Symfony/Component/ObjectMapper/ObjectMapper.php @@ -14,9 +14,11 @@ use Psr\Container\ContainerInterface; use Symfony\Component\ObjectMapper\Exception\MappingException; use Symfony\Component\ObjectMapper\Exception\MappingTransformException; +use Symfony\Component\ObjectMapper\Exception\NoSuchPropertyException; use Symfony\Component\ObjectMapper\Metadata\Mapping; use Symfony\Component\ObjectMapper\Metadata\ObjectMapperMetadataFactoryInterface; use Symfony\Component\ObjectMapper\Metadata\ReflectionObjectMapperMetadataFactory; +use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException as PropertyAccessorNoSuchPropertyException; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; /** @@ -167,7 +169,19 @@ public function map(object $source, object|string|null $target = null): object private function getRawValue(object $source, string $propertyName): mixed { - return $this->propertyAccessor ? $this->propertyAccessor->getValue($source, $propertyName) : $source->{$propertyName}; + if ($this->propertyAccessor) { + try { + return $this->propertyAccessor->getValue($source, $propertyName); + } catch (PropertyAccessorNoSuchPropertyException $e) { + throw new NoSuchPropertyException($e->getMessage(), $e->getCode(), $e); + } + } + + if (!property_exists($source, $propertyName) && !isset($source->{$propertyName})) { + throw new NoSuchPropertyException(sprintf('The property "%s" does not exist on "%s".', $propertyName, get_debug_type($source))); + } + + return $source->{$propertyName}; } private function getSourceValue(object $source, object $target, mixed $value, \SplObjectStorage $objectMap, ?Mapping $mapping = null): mixed diff --git a/src/Symfony/Component/ObjectMapper/ObjectMapperInterface.php b/src/Symfony/Component/ObjectMapper/ObjectMapperInterface.php index 0df5a0fbfddbd..9eb3bc5d5af0b 100644 --- a/src/Symfony/Component/ObjectMapper/ObjectMapperInterface.php +++ b/src/Symfony/Component/ObjectMapper/ObjectMapperInterface.php @@ -13,6 +13,7 @@ use Symfony\Component\ObjectMapper\Exception\MappingException; use Symfony\Component\ObjectMapper\Exception\MappingTransformException; +use Symfony\Component\ObjectMapper\Exception\NoSuchPropertyException; /** * Object to object mapper. @@ -33,6 +34,7 @@ interface ObjectMapperInterface * * @throws MappingException When the mapping configuration is wrong * @throws MappingTransformException When a transformation on an object does not return an object + * @throws NoSuchPropertyException When a property does not exist */ public function map(object $source, object|string|null $target = null): object; } diff --git a/src/Symfony/Component/ObjectMapper/Tests/Fixtures/DefaultValueStdClass/TargetDto.php b/src/Symfony/Component/ObjectMapper/Tests/Fixtures/DefaultValueStdClass/TargetDto.php new file mode 100644 index 0000000000000..e595c103a4e35 --- /dev/null +++ b/src/Symfony/Component/ObjectMapper/Tests/Fixtures/DefaultValueStdClass/TargetDto.php @@ -0,0 +1,20 @@ +map($a, SourceOnly::class); $this->assertInstanceOf(SourceOnly::class, $mapped); $this->assertSame('test', $mapped->mappedName); + } + public function testSourceOnlyWithMagicMethods() + { + $mapper = new ObjectMapper(); $a = new class { + public function __isset($key): bool + { + return 'name' === $key; + } + public function __get(string $key): string { return match ($key) { @@ -303,4 +314,24 @@ public function testMultipleTargetMapProperty() $this->assertEquals('donotmap', $c->foo); $this->assertEquals('foo', $c->doesNotExistInTargetB); } + + public function testDefaultValueStdClass() + { + $this->expectException(NoSuchPropertyException::class); + $u = new \stdClass(); + $u->id = 'abc'; + $mapper = new ObjectMapper(); + $b = $mapper->map($u, TargetDto::class); + } + + public function testDefaultValueStdClassWithPropertyInfo() + { + $u = new \stdClass(); + $u->id = 'abc'; + $mapper = new ObjectMapper(propertyAccessor: PropertyAccess::createPropertyAccessorBuilder()->disableExceptionOnInvalidPropertyPath()->getPropertyAccessor()); + $b = $mapper->map($u, TargetDto::class); + $this->assertInstanceOf(TargetDto::class, $b); + $this->assertSame('abc', $b->id); + $this->assertNull($b->optional); + } }