8000 feature #50295 [PropertyAccess] Auto-cast from/to DateTime/Immutable … · andersmateusz/symfony@e868f0a · GitHub
[go: up one dir, main page]

Skip to content

Commit e868f0a

Browse files
committed
feature symfony#50295 [PropertyAccess] Auto-cast from/to DateTime/Immutable when appropriate (nicolas-grekas)
This PR was merged into the 6.4 branch. Discussion ---------- [PropertyAccess] Auto-cast from/to DateTime/Immutable when appropriate | Q | A | ------------- | --- | Branch? | 6.4 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | - | License | MIT | Doc PR | - Best reviewed [ignoring whitespaces](https://github.com/symfony/symfony/pull/50295/files?w=1). My starting point is a form with a date defined as such: ```php class MyFormType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('some_date', DateTimeType::class) ; } // ... } class MyEntity { private ?\DateTimeImmutable $someDate = null; //... } ``` Then doing something like: ```php $myEntity = new MyEntity(); $form = $this->createForm(MyFormType::class, $myEntity); ``` If I submit this form, I get: ``` Expected argument of type "DateTimeImmutable", "DateTime" given at property path "some_date". ``` The current solution is to set option `input` to `datetime_immutable` in `buildForm()`. But this is boring. Too much boilerplate. I've tried looking at the Form component to define the `input` option based on the entity provided when building the form, but this entity is not passed to sub-forms, so that we have no way to do this. The only way I found is the one attached: if we cannot set a `DateTime`, we try setting a `DateTimeImmutable`. I thought about changing the default value of option `input` to `datetime_immutable`, but this would need a nasty deprecation that'd force everybody to write even more boilerplate before v7, while what we want is less. So here we are. Commits ------- 3156a60 [PropertyAccess] Auto-cast from/to DateTime/Immutable when appropriate
2 parents aef6429 + 3156a60 commit e868f0a

File tree

3 files changed

+51
-15
lines changed

3 files changed

+51
-15
lines changed

src/Symfony/Component/PropertyAccess/PropertyAccessor.php

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,7 @@ private function writeIndex(array $zval, string|int $index, mixed $value): void
514514
*
515515
* @throws NoSuchPropertyException if the property does not exist or is not public
516516
*/
517-
private function writeProperty(array $zval, string $property, mixed $value): void
517+
private function writeProperty(array $zval, string $property, mixed $value, bool $recursive = false): void
518518
{
519519
if (!\is_object($zval[self::VALUE])) {
520520
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
524524
$class = $object::class;
525525
$mutator = $this->getWriteInfo($class, $property, $value);
526526

527-
if (PropertyWriteInfo::TYPE_NONE !== $mutator->getType()) {
528-
$type = $mutator->getType();
527+
try {
528+
if (PropertyWriteInfo::TYPE_NONE !== $mutator->getType()) {
529+
$type = $mutator->getType();
530+
531+
if (PropertyWriteInfo::TYPE_METHOD === $type) {
532+
$object->{$mutator->getName()}($value);
533+
} elseif (PropertyWriteInfo::TYPE_PROPERTY === $type) {
534+
$object->{$mutator->getName()} = $value;
535+
} elseif (PropertyWriteInfo::TYPE_ADDER_AND_REMOVER === $type) {
536+
$this->writeCollection($zval, $property, $value, $mutator->getAdderInfo(), $mutator->getRemoverInfo());
537+
}
538+
} elseif ($object instanceof \stdClass && property_exists($object, $property)) {
539+
$object->$property = $value;
540+
} elseif (!$this->ignoreInvalidProperty) {
541+
if ($mutator->hasErrors()) {
542+
throw new NoSuchPropertyException(implode('. ', $mutator->getErrors()).'.');
543+
}
529544

530-
if (PropertyWriteInfo::TYPE_METHOD === $type) {
531-
$object->{$mutator->getName()}($value);
532-
} elseif (PropertyWriteInfo::TYPE_PROPERTY === $type) {
533-
$object->{$mutator->getName()} = $value;
534-
} elseif (PropertyWriteInfo::TYPE_ADDER_AND_REMOVER === $type) {
535-
$this->writeCollection($zval, $property, $value, $mutator->getAdderInfo(), $mutator->getRemoverInfo());
545+
throw new NoSuchPropertyException(sprintf('Could not determine access type for property "%s" in class "%s".', $property, get_debug_type($object)));
536546
}
537-
} elseif ($object instanceof \stdClass && property_exists($object, $property)) {
538-
$object->$property = $value;
539-
} elseif (!$this->ignoreInvalidProperty) {
540-
if ($mutator->hasErrors()) {
541-
throw new NoSuchPropertyException(implode('. ', $mutator->getErrors()).'.');
547+
} catch (\TypeError $e) {
548+
if ($recursive || !$value instanceof \DateTimeInterface || !\in_array($value::class, ['DateTime', 'DateTimeImmutable'], true) || __FILE__ !== $e->getTrace()[0]['file']) {
549+
throw $e;
542550
}
543551

544-
throw new NoSuchPropertyException(sprintf('Could not determine access type for property "%s" in class "%s".', $property, get_debug_type($object)));
552+
$value = $value instanceof \DateTimeImmutable ? \DateTime::createFromImmutable($value) : \DateTimeImmutable::createFromMutable($value);
553+
try {
< 10000 code>554+
$this->writeProperty($zval, $property, $value, true);
555+
} catch (\TypeError) {
556+
throw $e; // throw the previous error
557+
}
545558
}
546559
}
547560

src/Symfony/Component/PropertyAccess/Tests/Fixtures/TypeHinted.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ public function setDate(\DateTimeImmutable $date)
2828
$this->date = $date;
2929
}
3030

31+
public function setDateMutable(\DateTime $date)
32+
{
33+
$this->date = $date;
34+
}
35+
3136
public function getDate()
3237
{
3338
return $this->date;

src/Symfony/Component/PropertyAccess/Tests/PropertyAccessorTest.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -940,4 +940,22 @@ public function testSetValueWrongTypeShouldThrowWrappedException()
940940
$this->expectExceptionMessage('Expected argument of type "float", "string" given at property path "publicProperty"');
941941
$this->propertyAccessor->setValue($object, 'publicProperty', 'string');
942942
}
943+
944+
public function testCastDateTime()
945+
{
946+
$object = new TypeHinted();
947+
948+
$this->propertyAccessor->setValue($object, 'date', new \DateTime());
949+
950+
$this->assertInstanceOf(\DateTimeImmutable::class, $object->getDate());
951+
}
952+
953+
public function testCastDateTimeImmutable()
954+
{
955+
$object = new TypeHinted();
956+
957+
$this->propertyAccessor->setValue($object, 'date_mutable', new \DateTimeImmutable());
958+
959+
$this->assertInstanceOf(\DateTime::class, $object->getDate());
960+
}
943961
}

0 commit comments

Comments
 (0)
0