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..dc493c0e6a4c8 100644 --- a/src/Symfony/Component/ObjectMapper/ObjectMapper.php +++ b/src/Symfony/Component/ObjectMapper/ObjectMapper.php @@ -14,6 +14,7 @@ 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; @@ -167,7 +168,15 @@ 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) { + return $this->propertyAccessor->getValue($source, $propertyName); + } + + if (!property_exists($source, $propertyName) && !(method_exists($source, '__isset') && true === $source->__isset($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/Tests/Fixtures/DefaultValueStdClass/TargetDto.php b/src/Symfony/Component/ObjectMapper/Tests/Fixtures/DefaultValueStdClass/TargetDto.php new file mode 100644 index 0000000000000..d4aa289184d50 --- /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(string $key): bool { + return $key === 'name'; + } + public function __get(string $key): string { return match ($key) { @@ -303,4 +313,27 @@ 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); + $this->assertInstanceOf(TargetDto::class, $b); + $this->assertEquals('abc', $b->id); + $this->assertEquals(null, $b->optional); + } + + 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->assertEquals('abc', $b->id); + $this->assertEquals(null, $b->optional); + } }