diff --git a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php index 0bf5c0afa903..ba676b9de326 100644 --- a/src/Symfony/Component/PropertyAccess/PropertyAccessor.php +++ b/src/Symfony/Component/PropertyAccess/PropertyAccessor.php @@ -514,7 +514,7 @@ private function writeIndex(array $zval, string|int $index, mixed $value): void * * @throws NoSuchPropertyException if the property does not exist or is not public */ - private function writeProperty(array $zval, string $property, mixed $value): void + private function writeProperty(array $zval, string $property, mixed $value, bool $recursive = false): void { if (!\is_object($zval[self::VALUE])) { throw new NoSuchPropertyException(sprintf('Cannot write property "%s" to an array. Maybe you should write the property path as "[%1$s]" instead?', $property)); @@ -524,24 +524,37 @@ private function writeProperty(array $zval, string $property, mixed $value): voi $class = $object::class; $mutator = $this->getWriteInfo($class, $property, $value); - if (PropertyWriteInfo::TYPE_NONE !== $mutator->getType()) { - $type = $mutator->getType(); + try { + if (PropertyWriteInfo::TYPE_NONE !== $mutator->getType()) { + $type = $mutator->getType(); + + if (PropertyWriteInfo::TYPE_METHOD === $type) { + $object->{$mutator->getName()}($value); + } elseif (PropertyWriteInfo::TYPE_PROPERTY === $type) { + $object->{$mutator->getName()} = $value; + } elseif (PropertyWriteInfo::TYPE_ADDER_AND_REMOVER === $type) { + $this->writeCollection($zval, $property, $value, $mutator->getAdderInfo(), $mutator->getRemoverInfo()); + } + } elseif ($object instanceof \stdClass && property_exists($object, $property)) { + $object->$property = $value; + } elseif (!$this->ignoreInvalidProperty) { + if ($mutator->hasErrors()) { + throw new NoSuchPropertyException(implode('. ', $mutator->getErrors()).'.'); + } - if (PropertyWriteInfo::TYPE_METHOD === $type) { - $object->{$mutator->getName()}($value); - } elseif (PropertyWriteInfo::TYPE_PROPERTY === $type) { - $object->{$mutator->getName()} = $value; - } elseif (PropertyWriteInfo::TYPE_ADDER_AND_REMOVER === $type) { - $this->writeCollection($zval, $property, $value, $mutator->getAdderInfo(), $mutator->getRemoverInfo()); + throw new NoSuchPropertyException(sprintf('Could not determine access type for property "%s" in class "%s".', $property, get_debug_type($object))); } - } elseif ($object instanceof \stdClass && property_exists($object, $property)) { - $object->$property = $value; - } elseif (!$this->ignoreInvalidProperty) { - if ($mutator->hasErrors()) { - throw new NoSuchPropertyException(implode('. ', $mutator->getErrors()).'.'); + } catch (\TypeError $e) { + if ($recursive || !$value instanceof \DateTimeInterface || !\in_array($value::class, ['DateTime', 'DateTimeImmutable'], true) || __FILE__ !== $e->getTrace()[0]['file']) { + throw $e; } - throw new NoSuchPropertyException(sprintf('Could not determine access type for property "%s" in class "%s".', $property, get_debug_type($object))); + $value = $value instanceof \DateTimeImmutable ? \DateTime::createFromImmutable($value) : \DateTimeImmutable::createFromMutable($value); + try { + $this->writeProperty($zval, $property, $value, true); + } catch (\TypeError) { + throw $e; // throw the previous error + } } } diff --git a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TypeHinted.php b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TypeHinted.php index bb0a26dbc509..42b87df1ae39 100644 --- a/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TypeHinted.php +++ b/src/Symfony/Component/PropertyAccess/Tests/Fixtures/TypeHinted.php @@ -28,6 +28,11 @@ public function setDate(\DateTimeImmutable $date) $this->date = $date; } + public function setDateMutable(\DateTime $date) + { + $this->date = $date; + } + public function getDate() { return $this->date; diff --git a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php index ae773e91d87e..7b4dfba0759f 100644 --- a/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php +++ b/src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php @@ -940,4 +940,22 @@ public function testSetValueWrongTypeShouldThrowWrappedException() $this->expectExceptionMessage('Expected argument of type "float", "string" given at property path "publicProperty"'); $this->propertyAccessor->setValue($object, 'publicProperty', 'string'); } + + public function testCastDateTime() + { + $object = new TypeHinted(); + + $this->propertyAccessor->setValue($object, 'date', new \DateTime()); + + $this->assertInstanceOf(\DateTimeImmutable::class, $object->getDate()); + } + + public function testCastDateTimeImmutable() + { + $object = new TypeHinted(); + + $this->propertyAccessor->setValue($object, 'date_mutable', new \DateTimeImmutable()); + + $this->assertInstanceOf(\DateTime::class, $object->getDate()); + } }