From c485aff377a7f87b4093ddabdfd60c16ae031872 Mon Sep 17 00:00:00 2001 From: Elliot Date: Sat, 11 Nov 2023 23:54:14 +0100 Subject: [PATCH 01/10] improvement(DateTime): handle multiple deserialization formats and timezones --- src/DeserializerGenerator.php | 7 ++----- src/Template/Deserialization.php | 36 ++++++++++++++++++++++++++++---- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/DeserializerGenerator.php b/src/DeserializerGenerator.php index 3997c29..f59c699 100644 --- a/src/DeserializerGenerator.php +++ b/src/DeserializerGenerator.php @@ -212,12 +212,9 @@ private function generateInnerCodeForFieldType( return $this->generateCodeForArray($type, $arrayPath, $modelPropertyPath, $stack); case $type instanceof PropertyTypeDateTime: - if (null !== $type->getZone()) { - throw new \RuntimeException('Timezone support is not implemented'); - } - $format = $type->getDeserializeFormat() ?: $type->getFormat(); + $format = $type->getDeserializeFormats() ?: (is_string($type->getFormat()) ? [$type->getFormat()] : $type->getFormat()); if (null !== $format) { - return $this->templating->renderAssignDateTimeFromFormat($type->isImmutable(), (string) $modelPropertyPath, (string) $arrayPath, $format); + return $this->templating->renderAssignDateTimeFromFormat($type->isImmutable(), (string) $modelPropertyPath, (string) $arrayPath, $format, $type->getZone()); } return $this->templating->renderAssignDateTimeToField($type->isImmutable(), (string) $modelPropertyPath, (string) $arrayPath); diff --git a/src/Template/Deserialization.php b/src/Template/Deserialization.php index ea22821..c9bf6c1 100644 --- a/src/Template/Deserialization.php +++ b/src/Template/Deserialization.php @@ -62,7 +62,16 @@ function {{functionName}}(array {{jsonPath}}): {{className}} EOT; private const TMPL_ASSIGN_DATETIME_FROM_FORMAT = <<<'EOT' -{{modelPath}} = \DateTime::createFromFormat('{{format}}', {{jsonPath}}); +foreach([{{formats|join(', ')}}] as {{formatVariable}}) { + if (({{formatVariable}} = \DateTime::createFromFormat({{formatVariable}}, {{jsonPath}}, {{timezone}}))) { + {{modelPath}} = {{formatVariable}}; + break; + } +} + +if (false === ({{formatVariable}} ?? null)) { + throw new \Exception('Invalid datetime string '.({{jsonPath}}).' matches none of the deserialization formats '.({{formats|join('|')}})); +} EOT; @@ -72,7 +81,16 @@ function {{functionName}}(array {{jsonPath}}): {{className}} EOT; private const TMPL_ASSIGN_DATETIME_IMMUTABLE_FROM_FORMAT = <<<'EOT' -{{modelPath}} = \DateTimeImmutable::createFromFormat('{{format}}', {{jsonPath}}); +foreach([{{formats|join(', ')}}] as {{formatVariable}}) { + if (({{formatVariable}} = \DateTimeImmutable::createFromFormat({{formatVariable}}, {{jsonPath}}, {{timezone}}))) { + {{modelPath}} = {{formatVariable}}; + break; + } +} + +if (false === ({{formatVariable}} ?? null)) { + throw new \Exception('Invalid datetime string '.({{jsonPath}}).' matches none of the deserialization formats '.({{formats|join('|')}})); +} EOT; @@ -190,14 +208,24 @@ public function renderAssignDateTimeToField(bool $immutable, string $modelPath, ]); } - public function renderAssignDateTimeFromFormat(bool $immutable, string $modelPath, string $jsonPath, string $format): string + public function renderAssignDateTimeFromFormat(bool $immutable, string $modelPath, string $jsonPath, array|string $formats, ?string $timezone = null): string { $template = $immutable ? self::TMPL_ASSIGN_DATETIME_IMMUTABLE_FROM_FORMAT : self::TMPL_ASSIGN_DATETIME_FROM_FORMAT; + $formatVariable = preg_replace_callback( + '/(^|[^a-zA-Z]+|\d+)([a-zA-Z])/', + fn($match) => (ctype_digit($match[1]) ? $match[1] : null).mb_strtoupper($match[2]), + $modelPath + ); return $this->render($template, [ 'modelPath' => $modelPath, 'jsonPath' => $jsonPath, - 'format' => $format, + 'formats' => array_map( + static fn (string $f) => var_export($f, true), + is_string($formats) ? [$formats] : $formats + ), + 'formatVariable' => '$'.$formatVariable, + 'timezone' => $timezone ? 'new \DateTimeZone('.var_export($timezone, true).')' : 'null', ]); } From 9305d1a4f74035579aa6d7c5d5af02dfccf74b06 Mon Sep 17 00:00:00 2001 From: Elliot Date: Sun, 12 Nov 2023 00:21:55 +0100 Subject: [PATCH 02/10] fix(cs): PhpCS fixes --- src/DeserializerGenerator.php | 2 +- src/Template/Deserialization.php | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/DeserializerGenerator.php b/src/DeserializerGenerator.php index f59c699..4f2c07d 100644 --- a/src/DeserializerGenerator.php +++ b/src/DeserializerGenerator.php @@ -212,7 +212,7 @@ private function generateInnerCodeForFieldType( return $this->generateCodeForArray($type, $arrayPath, $modelPropertyPath, $stack); case $type instanceof PropertyTypeDateTime: - $format = $type->getDeserializeFormats() ?: (is_string($type->getFormat()) ? [$type->getFormat()] : $type->getFormat()); + $format = $type->getDeserializeFormats() ?: (\is_string($type->getFormat()) ? [$type->getFormat()] : $type->getFormat()); if (null !== $format) { return $this->templating->renderAssignDateTimeFromFormat($type->isImmutable(), (string) $modelPropertyPath, (string) $arrayPath, $format, $type->getZone()); } diff --git a/src/Template/Deserialization.php b/src/Template/Deserialization.php index c9bf6c1..d363665 100644 --- a/src/Template/Deserialization.php +++ b/src/Template/Deserialization.php @@ -208,12 +208,12 @@ public function renderAssignDateTimeToField(bool $immutable, string $modelPath, ]); } - public function renderAssignDateTimeFromFormat(bool $immutable, string $modelPath, string $jsonPath, array|string $formats, ?string $timezone = null): string + public function renderAssignDateTimeFromFormat(bool $immutable, string $modelPath, string $jsonPath, array|string $formats, string $timezone = null): string { $template = $immutable ? self::TMPL_ASSIGN_DATETIME_IMMUTABLE_FROM_FORMAT : self::TMPL_ASSIGN_DATETIME_FROM_FORMAT; $formatVariable = preg_replace_callback( - '/(^|[^a-zA-Z]+|\d+)([a-zA-Z])/', - fn($match) => (ctype_digit($match[1]) ? $match[1] : null).mb_strtoupper($match[2]), + '/([^a-zA-Z]+|\d+)([a-zA-Z])/', + static fn ($match) => (ctype_digit($match[1]) ? $match[1] : null).mb_strtoupper($match[2]), $modelPath ); @@ -222,9 +222,9 @@ public function renderAssignDateTimeFromFormat(bool $immutable, string $modelPat 'jsonPath' => $jsonPath, 'formats' => array_map( static fn (string $f) => var_export($f, true), - is_string($formats) ? [$formats] : $formats + \is_string($formats) ? [$formats] : $formats ), - 'formatVariable' => '$'.$formatVariable, + 'formatVariable' => '$'.lcfirst($formatVariable), 'timezone' => $timezone ? 'new \DateTimeZone('.var_export($timezone, true).')' : 'null', ]); } From f5e59e1a3d9227d267b8a19dc967f5fcbd8078bc Mon Sep 17 00:00:00 2001 From: Elliot Date: Sun, 12 Nov 2023 00:29:32 +0100 Subject: [PATCH 03/10] fix(DateTime): preserve BC with liip/serializer:1.1 --- src/DeserializerGenerator.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/DeserializerGenerator.php b/src/DeserializerGenerator.php index 4f2c07d..c8d1747 100644 --- a/src/DeserializerGenerator.php +++ b/src/DeserializerGenerator.php @@ -212,9 +212,12 @@ private function generateInnerCodeForFieldType( return $this->generateCodeForArray($type, $arrayPath, $modelPropertyPath, $stack); case $type instanceof PropertyTypeDateTime: - $format = $type->getDeserializeFormats() ?: (\is_string($type->getFormat()) ? [$type->getFormat()] : $type->getFormat()); - if (null !== $format) { - return $this->templating->renderAssignDateTimeFromFormat($type->isImmutable(), (string) $modelPropertyPath, (string) $arrayPath, $format, $type->getZone()); + // todo: remove use of deprecated method {@link \Liip\MetadataParser\Metadata\PropertyTypeDateTime::getDeserializeFormat} + $formats = method_exists($type, 'getDeserializeFormats') ? $type->getDeserializeFormats() : null; + $singleFormat = $type->getDeserializeFormat() ?: $type->getFormat(); + $formats = $formats ?: (\is_string($singleFormat) ? [$singleFormat] : $singleFormat); + if (null !== $formats) { + return $this->templating->renderAssignDateTimeFromFormat($type->isImmutable(), (string) $modelPropertyPath, (string) $arrayPath, $formats, $type->getZone()); } return $this->templating->renderAssignDateTimeToField($type->isImmutable(), (string) $modelPropertyPath, (string) $arrayPath); From 5d4df16ff8623eefd4b89f414f915fd5143e8682 Mon Sep 17 00:00:00 2001 From: Elliot Date: Sun, 12 Nov 2023 00:32:13 +0100 Subject: [PATCH 04/10] fix(phpstan): phpstan code correctness fixes --- src/Template/Deserialization.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Template/Deserialization.php b/src/Template/Deserialization.php index d363665..64ad666 100644 --- a/src/Template/Deserialization.php +++ b/src/Template/Deserialization.php @@ -208,6 +208,9 @@ public function renderAssignDateTimeToField(bool $immutable, string $modelPath, ]); } + /** + * @param list|string $formats + */ public function renderAssignDateTimeFromFormat(bool $immutable, string $modelPath, string $jsonPath, array|string $formats, string $timezone = null): string { $template = $immutable ? self::TMPL_ASSIGN_DATETIME_IMMUTABLE_FROM_FORMAT : self::TMPL_ASSIGN_DATETIME_FROM_FORMAT; From c7796e0af3682f5ec33116883ab266d263a9d370 Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 13 Nov 2023 11:06:29 +0100 Subject: [PATCH 05/10] fix(metadata): require liip/metadata-parser:^1.2 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 7c1a9b3..fc6a911 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ "require": { "php": "^8.0", "ext-json": "*", - "liip/metadata-parser": "^1.1", + "liip/metadata-parser": "^1.2", "pnz/json-exception": "^1.0", "symfony/filesystem": "^4.4 || ^5.0 || ^6.0", "symfony/finder": "^4.4 || ^5.0 || ^6.0", From 3c2b5d8b07fe4c88d356d2ebb58a5e78ee28cd81 Mon Sep 17 00:00:00 2001 From: Elliot Date: Mon, 13 Nov 2023 11:07:06 +0100 Subject: [PATCH 06/10] fix(datetime): small code style improvements --- src/DeserializerGenerator.php | 4 +--- src/Template/Deserialization.php | 30 ++++++++++++++++-------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/DeserializerGenerator.php b/src/DeserializerGenerator.php index c8d1747..b51130c 100644 --- a/src/DeserializerGenerator.php +++ b/src/DeserializerGenerator.php @@ -213,9 +213,7 @@ private function generateInnerCodeForFieldType( case $type instanceof PropertyTypeDateTime: // todo: remove use of deprecated method {@link \Liip\MetadataParser\Metadata\PropertyTypeDateTime::getDeserializeFormat} - $formats = method_exists($type, 'getDeserializeFormats') ? $type->getDeserializeFormats() : null; - $singleFormat = $type->getDeserializeFormat() ?: $type->getFormat(); - $formats = $formats ?: (\is_string($singleFormat) ? [$singleFormat] : $singleFormat); + $formats = $type->getDeserializeFormats() ?: (\is_string($type->getFormat()) ? [$type->getFormat()] : $type->getFormat()); if (null !== $formats) { return $this->templating->renderAssignDateTimeFromFormat($type->isImmutable(), (string) $modelPropertyPath, (string) $arrayPath, $formats, $type->getZone()); } diff --git a/src/Template/Deserialization.php b/src/Template/Deserialization.php index 64ad666..bc40a59 100644 --- a/src/Template/Deserialization.php +++ b/src/Template/Deserialization.php @@ -62,14 +62,14 @@ function {{functionName}}(array {{jsonPath}}): {{className}} EOT; private const TMPL_ASSIGN_DATETIME_FROM_FORMAT = <<<'EOT' -foreach([{{formats|join(', ')}}] as {{formatVariable}}) { - if (({{formatVariable}} = \DateTime::createFromFormat({{formatVariable}}, {{jsonPath}}, {{timezone}}))) { - {{modelPath}} = {{formatVariable}}; +foreach([{{formats|join(', ')}}] as {{format}}) { + if (({{date}} = \DateTime::createFromFormat({{format}}, {{jsonPath}}, {{timezone}}))) { + {{modelPath}} = {{date}}; break; } } -if (false === ({{formatVariable}} ?? null)) { +if (false === ({{date}} ?? null)) { throw new \Exception('Invalid datetime string '.({{jsonPath}}).' matches none of the deserialization formats '.({{formats|join('|')}})); } @@ -81,14 +81,14 @@ function {{functionName}}(array {{jsonPath}}): {{className}} EOT; private const TMPL_ASSIGN_DATETIME_IMMUTABLE_FROM_FORMAT = <<<'EOT' -foreach([{{formats|join(', ')}}] as {{formatVariable}}) { - if (({{formatVariable}} = \DateTimeImmutable::createFromFormat({{formatVariable}}, {{jsonPath}}, {{timezone}}))) { - {{modelPath}} = {{formatVariable}}; +foreach([{{formats|join(', ')}}] as {{format}}) { + if (({{date}} = \DateTimeImmutable::createFromFormat({{format}}, {{jsonPath}}, {{timezone}}))) { + {{modelPath}} = {{date}}; break; } } -if (false === ({{formatVariable}} ?? null)) { +if (false === ({{date}} ?? null)) { throw new \Exception('Invalid datetime string '.({{jsonPath}}).' matches none of the deserialization formats '.({{formats|join('|')}})); } @@ -214,20 +214,22 @@ public function renderAssignDateTimeToField(bool $immutable, string $modelPath, public function renderAssignDateTimeFromFormat(bool $immutable, string $modelPath, string $jsonPath, array|string $formats, string $timezone = null): string { $template = $immutable ? self::TMPL_ASSIGN_DATETIME_IMMUTABLE_FROM_FORMAT : self::TMPL_ASSIGN_DATETIME_FROM_FORMAT; + $formats = array_map( + static fn (string $f): string => var_export($f, true), + \is_string($formats) ? [$formats] : $formats + ); $formatVariable = preg_replace_callback( '/([^a-zA-Z]+|\d+)([a-zA-Z])/', - static fn ($match) => (ctype_digit($match[1]) ? $match[1] : null).mb_strtoupper($match[2]), + static fn ($match): string => (ctype_digit($match[1]) ? $match[1] : null).mb_strtoupper($match[2]), $modelPath ); return $this->render($template, [ 'modelPath' => $modelPath, 'jsonPath' => $jsonPath, - 'formats' => array_map( - static fn (string $f) => var_export($f, true), - \is_string($formats) ? [$formats] : $formats - ), - 'formatVariable' => '$'.lcfirst($formatVariable), + 'formats' => $formats, + 'format' => '$'.lcfirst($formatVariable), + 'date' => '$'.lcfirst($formatVariable).'Date', 'timezone' => $timezone ? 'new \DateTimeZone('.var_export($timezone, true).')' : 'null', ]); } From 3b44eebea89fc5cbabc2696cf9e8741d97f00cb3 Mon Sep 17 00:00:00 2001 From: Elliot Date: Tue, 30 Jan 2024 13:45:11 +0100 Subject: [PATCH 07/10] refactor(deserializer): small code cleanup --- src/DeserializerGenerator.php | 1 - src/Template/Deserialization.php | 25 +++++++++++++++++-------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/DeserializerGenerator.php b/src/DeserializerGenerator.php index b51130c..68a4e20 100644 --- a/src/DeserializerGenerator.php +++ b/src/DeserializerGenerator.php @@ -212,7 +212,6 @@ private function generateInnerCodeForFieldType( return $this->generateCodeForArray($type, $arrayPath, $modelPropertyPath, $stack); case $type instanceof PropertyTypeDateTime: - // todo: remove use of deprecated method {@link \Liip\MetadataParser\Metadata\PropertyTypeDateTime::getDeserializeFormat} $formats = $type->getDeserializeFormats() ?: (\is_string($type->getFormat()) ? [$type->getFormat()] : $type->getFormat()); if (null !== $formats) { return $this->templating->renderAssignDateTimeFromFormat($type->isImmutable(), (string) $modelPropertyPath, (string) $arrayPath, $formats, $type->getZone()); diff --git a/src/Template/Deserialization.php b/src/Template/Deserialization.php index bc40a59..e1cf480 100644 --- a/src/Template/Deserialization.php +++ b/src/Template/Deserialization.php @@ -62,6 +62,7 @@ function {{functionName}}(array {{jsonPath}}): {{className}} EOT; private const TMPL_ASSIGN_DATETIME_FROM_FORMAT = <<<'EOT' +{{date}} = false; foreach([{{formats|join(', ')}}] as {{format}}) { if (({{date}} = \DateTime::createFromFormat({{format}}, {{jsonPath}}, {{timezone}}))) { {{modelPath}} = {{date}}; @@ -69,9 +70,10 @@ function {{functionName}}(array {{jsonPath}}): {{className}} } } -if (false === ({{date}} ?? null)) { - throw new \Exception('Invalid datetime string '.({{jsonPath}}).' matches none of the deserialization formats '.({{formats|join('|')}})); +if (false === {{date}}) { + throw new \Exception('Invalid datetime string '.({{jsonPath}}).' matches none of the deserialization formats: '.({{formats|join('|')}})); } +unset({{format}}, {{date}}); EOT; @@ -81,6 +83,7 @@ function {{functionName}}(array {{jsonPath}}): {{className}} EOT; private const TMPL_ASSIGN_DATETIME_IMMUTABLE_FROM_FORMAT = <<<'EOT' +{{date}} = false; foreach([{{formats|join(', ')}}] as {{format}}) { if (({{date}} = \DateTimeImmutable::createFromFormat({{format}}, {{jsonPath}}, {{timezone}}))) { {{modelPath}} = {{date}}; @@ -88,9 +91,10 @@ function {{functionName}}(array {{jsonPath}}): {{className}} } } -if (false === ({{date}} ?? null)) { - throw new \Exception('Invalid datetime string '.({{jsonPath}}).' matches none of the deserialization formats '.({{formats|join('|')}})); +if (false === {{date}}) { + throw new \Exception('Invalid datetime string '.({{jsonPath}}).' matches none of the deserialization formats: '.({{formats|join('|')}})); } +unset({{format}}, {{date}}); EOT; @@ -213,12 +217,17 @@ public function renderAssignDateTimeToField(bool $immutable, string $modelPath, */ public function renderAssignDateTimeFromFormat(bool $immutable, string $modelPath, string $jsonPath, array|string $formats, string $timezone = null): string { + if (is_string($formats)) { + @trigger_error('Passing a string for argument $formats is deprecated, please pass an array of strings instead', \E_USER_DEPRECATED); + $formats = [$formats]; + } + $template = $immutable ? self::TMPL_ASSIGN_DATETIME_IMMUTABLE_FROM_FORMAT : self::TMPL_ASSIGN_DATETIME_FROM_FORMAT; $formats = array_map( static fn (string $f): string => var_export($f, true), - \is_string($formats) ? [$formats] : $formats + $formats ); - $formatVariable = preg_replace_callback( + $dateVariable = preg_replace_callback( '/([^a-zA-Z]+|\d+)([a-zA-Z])/', static fn ($match): string => (ctype_digit($match[1]) ? $match[1] : null).mb_strtoupper($match[2]), $modelPath @@ -228,8 +237,8 @@ public function renderAssignDateTimeFromFormat(bool $immutable, string $modelPat 'modelPath' => $modelPath, 'jsonPath' => $jsonPath, 'formats' => $formats, - 'format' => '$'.lcfirst($formatVariable), - 'date' => '$'.lcfirst($formatVariable).'Date', + 'format' => '$'.lcfirst($dateVariable).'Format', + 'date' => '$'.lcfirst($dateVariable), 'timezone' => $timezone ? 'new \DateTimeZone('.var_export($timezone, true).')' : 'null', ]); } From edac0c4c51c98d3d281f7a3452dd0c125a0c5438 Mon Sep 17 00:00:00 2001 From: Elliot Date: Wed, 31 Jan 2024 15:52:02 +0100 Subject: [PATCH 08/10] improvement(datetime): added more tests --- src/SerializerGenerator.php | 5 +---- src/Template/Deserialization.php | 9 ++++++--- tests/Fixtures/Model.php | 21 +++++++++++++++++++++ tests/Unit/DeserializerGeneratorTest.php | 9 +++++++++ 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/SerializerGenerator.php b/src/SerializerGenerator.php index fe4d556..59be154 100644 --- a/src/SerializerGenerator.php +++ b/src/SerializerGenerator.php @@ -181,10 +181,7 @@ private function generateCodeForFieldType( ): string { switch ($type) { case $type instanceof PropertyTypeDateTime: - if (null !== $type->getZone()) { - throw new \RuntimeException('Timezone support is not implemented'); - } - $dateFormat = $type->getFormat() ?: \DateTime::ISO8601; + $dateFormat = $type->getFormat() ?: \DateTimeInterface::ISO8601; return $this->templating->renderAssign( $fieldPath, diff --git a/src/Template/Deserialization.php b/src/Template/Deserialization.php index e1cf480..b73a001 100644 --- a/src/Template/Deserialization.php +++ b/src/Template/Deserialization.php @@ -71,7 +71,7 @@ function {{functionName}}(array {{jsonPath}}): {{className}} } if (false === {{date}}) { - throw new \Exception('Invalid datetime string '.({{jsonPath}}).' matches none of the deserialization formats: '.({{formats|join('|')}})); + throw new \Exception('Invalid datetime string '.({{jsonPath}}).' matches none of the deserialization formats: '.{{formatsError}}); } unset({{format}}, {{date}}); @@ -92,7 +92,7 @@ function {{functionName}}(array {{jsonPath}}): {{className}} } if (false === {{date}}) { - throw new \Exception('Invalid datetime string '.({{jsonPath}}).' matches none of the deserialization formats: '.({{formats|join('|')}})); + throw new \Exception('Invalid datetime string '.({{jsonPath}}).' matches none of the deserialization formats: '.{{formatsError}}); } unset({{format}}, {{date}}); @@ -217,7 +217,7 @@ public function renderAssignDateTimeToField(bool $immutable, string $modelPath, */ public function renderAssignDateTimeFromFormat(bool $immutable, string $modelPath, string $jsonPath, array|string $formats, string $timezone = null): string { - if (is_string($formats)) { + if (\is_string($formats)) { @trigger_error('Passing a string for argument $formats is deprecated, please pass an array of strings instead', \E_USER_DEPRECATED); $formats = [$formats]; } @@ -227,16 +227,19 @@ public function renderAssignDateTimeFromFormat(bool $immutable, string $modelPat static fn (string $f): string => var_export($f, true), $formats ); + $formatsError = var_export(implode(',', $formats), true); $dateVariable = preg_replace_callback( '/([^a-zA-Z]+|\d+)([a-zA-Z])/', static fn ($match): string => (ctype_digit($match[1]) ? $match[1] : null).mb_strtoupper($match[2]), $modelPath ); + return $this->render($template, [ 'modelPath' => $modelPath, 'jsonPath' => $jsonPath, 'formats' => $formats, + 'formatsError' => $formatsError, 'format' => '$'.lcfirst($dateVariable).'Format', 'date' => '$'.lcfirst($dateVariable), 'timezone' => $timezone ? 'new \DateTimeZone('.var_export($timezone, true).')' : 'null', diff --git a/tests/Fixtures/Model.php b/tests/Fixtures/Model.php index 045ed75..7297f8f 100644 --- a/tests/Fixtures/Model.php +++ b/tests/Fixtures/Model.php @@ -45,6 +45,27 @@ class Model */ public $dateWithFormat; + /** + * @Serializer\Type("DateTime<'Y-m-d', '', 'd/m/Y'>") + * + * @var \DateTime + */ + public $dateWithOneDeserializationFormat; + + /** + * @Serializer\Type("DateTime<'Y-m-d', '', ['m/d/Y', 'Y-m-d']>") + * + * @var \DateTime + */ + public $dateWithMultipleDeserializationFormats; + + /** + * @Serializer\Type("DateTime<'Y-m-d', '+0600', '!d/m/Y'>") + * + * @var \DateTime + */ + public $dateWithTimezone; + /** * @Serializer\Type("DateTimeImmutable") * diff --git a/tests/Unit/DeserializerGeneratorTest.php b/tests/Unit/DeserializerGeneratorTest.php index 9171538..2372931 100644 --- a/tests/Unit/DeserializerGeneratorTest.php +++ b/tests/Unit/DeserializerGeneratorTest.php @@ -52,6 +52,9 @@ public function testNested(): void 'nested_field' => ['nested_string' => 'nested'], 'date' => '2018-08-03T00:00:00+02:00', 'date_with_format' => '2018-08-04', + 'date_with_one_deserialization_format' => '15/05/2019', + 'date_with_multiple_deserialization_formats' => '05/16/2019', + 'date_with_timezone' => '04/08/2018', // Defined timezone offset is +6 hours, so bringing it back to UTC removes a day 'date_immutable' => '2016-06-01T00:00:00+02:00', ]; @@ -67,6 +70,12 @@ public function testNested(): void self::assertSame('2018-08-03', $model->date->format('Y-m-d')); self::assertInstanceOf(\DateTime::class, $model->dateWithFormat); self::assertSame('2018-08-04', $model->dateWithFormat->format('Y-m-d')); + self::assertInstanceOf(\DateTime::class, $model->dateWithOneDeserializationFormat); + self::assertSame('2019-05-15', $model->dateWithOneDeserializationFormat->format('Y-m-d')); + self::assertInstanceOf(\DateTime::class, $model->dateWithMultipleDeserializationFormats); + self::assertSame('2019-05-16', $model->dateWithMultipleDeserializationFormats->format('Y-m-d')); + self::assertInstanceOf(\DateTime::class, $model->dateWithTimezone); + self::assertSame('2018-08-03', $model->dateWithTimezone->setTimezone(new \DateTimeZone('UTC'))->format('Y-m-d')); self::assertInstanceOf(\DateTimeImmutable::class, $model->dateImmutable); self::assertSame('2016-06-01', $model->dateImmutable->format('Y-m-d')); } From b682b3191da546b42fd85ee623d50503c475f032 Mon Sep 17 00:00:00 2001 From: Elliot Date: Wed, 31 Jan 2024 23:15:42 +0100 Subject: [PATCH 09/10] update CHANGELOG.md --- CHANGELOG.md | 3 +++ src/DeserializerGenerator.php | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4fb259..ca66f37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +# 2.6.0 +* (De)serialization now accepts timezones, and lists of deserialization formats + # 2.5.1 * Generalized the improvement on arrays with primitive types to generate more efficient code. diff --git a/src/DeserializerGenerator.php b/src/DeserializerGenerator.php index 68a4e20..99db281 100644 --- a/src/DeserializerGenerator.php +++ b/src/DeserializerGenerator.php @@ -205,7 +205,7 @@ private function generateInnerCodeForFieldType( switch ($type) { case $type instanceof PropertyTypeArray: - if ($type->isCollection()) { + if ($type->isTraversable()) { return $this->generateCodeForArrayCollection($propertyMetadata, $type, $arrayPath, $modelPropertyPath, $stack); } From 3a88756042d8c62bf6c35614d43db352379eae82 Mon Sep 17 00:00:00 2001 From: Elliot Date: Thu, 1 Feb 2024 14:09:59 +0100 Subject: [PATCH 10/10] fix(cs): code cleanup from php-cs-fixer --- src/Template/Deserialization.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Template/Deserialization.php b/src/Template/Deserialization.php index b73a001..1d8d1a5 100644 --- a/src/Template/Deserialization.php +++ b/src/Template/Deserialization.php @@ -234,7 +234,6 @@ public function renderAssignDateTimeFromFormat(bool $immutable, string $modelPat $modelPath ); - return $this->render($template, [ 'modelPath' => $modelPath, 'jsonPath' => $jsonPath,