From c5e39863d8edd55df94e80e74aa609c654777c5b Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 15 Jul 2019 01:13:05 +0200 Subject: [PATCH 1/6] Handle array format for dateHandler --- src/Handler/DateHandler.php | 92 +++++++++++++++++++++---------- tests/Handler/DateHandlerTest.php | 12 ++++ 2 files changed, 75 insertions(+), 29 deletions(-) diff --git a/src/Handler/DateHandler.php b/src/Handler/DateHandler.php index e20e04a7c..630ccc601 100644 --- a/src/Handler/DateHandler.php +++ b/src/Handler/DateHandler.php @@ -75,16 +75,25 @@ private function serializeDateTimeInterface( array $type, SerializationContext $context ) { - if ($visitor instanceof XmlSerializationVisitor && false === $this->xmlCData) { - return $visitor->visitSimpleString($date->format($this->getFormat($type)), $type); - } + $formats = $this->getFormats($type); + + foreach ($formats as $format) { + $dateFormatted = $date->format($format); + + if (false !== $dateFormatted) { + if ($visitor instanceof XmlSerializationVisitor && false === $this->xmlCData) { + return $visitor->visitSimpleString($dateFormatted, $type); + } + + if ('U' === $format) { + return $visitor->visitInteger((int) $dateFormatted, $type); + } - $format = $this->getFormat($type); - if ('U' === $format) { - return $visitor->visitInteger((int) $date->format($format), $type); + return $visitor->visitString($dateFormatted, $type); + } } - return $visitor->visitString($date->format($this->getFormat($type)), $type); + throw new RuntimeException(sprintf('The date "%s" could not be formatted', $date)); } /** @@ -220,24 +229,35 @@ public function deserializeDateIntervalFromJson(DeserializationVisitorInterface */ private function parseDateTime($data, array $type, bool $immutable = false): \DateTimeInterface { - $timezone = !empty($type['params'][1]) ? new \DateTimeZone($type['params'][1]) : $this->defaultTimezone; - $format = $this->getDeserializationFormat($type); + $formatsAndTimeZones = $this->getDeserializationFormatsAndTimeZones($type); - if ($immutable) { - $datetime = \DateTimeImmutable::createFromFormat($format, (string) $data, $timezone); - } else { - $datetime = \DateTime::createFromFormat($format, (string) $data, $timezone); - } + $formatTried = []; + foreach ($formatsAndTimeZones as $formatsAndTimeZone) { + $format = $formatsAndTimeZone['format']; + $timezone = $formatsAndTimeZone['timezone']; - if (false === $datetime) { - throw new RuntimeException(sprintf('Invalid datetime "%s", expected format %s.', $data, $format)); - } + if ($immutable) { + $datetime = \DateTimeImmutable::createFromFormat($format, (string) $data, $timezone); + } else { + $datetime = \DateTime::createFromFormat($format, (string) $data, $timezone); + } - if ('U' === $format) { - $datetime = $datetime->setTimezone($timezone); + if (false !== $datetime) { + if ('U' === $format) { + $datetime = $datetime->setTimezone($timezone); + } + + return $datetime; + } + + $formatTried[] = $format; } - return $datetime; + throw new RuntimeException(sprintf( + 'Invalid datetime "%s", expected one of the format %s.', + $data, + '"' . implode('", "', $formatTried) . '"' + )); } private function parseDateInterval(string $data): \DateInterval @@ -255,23 +275,37 @@ private function parseDateInterval(string $data): \DateInterval /** * @param array $type */ - private function getDeserializationFormat(array $type): string + private function getDeserializationFormatsAndTimeZones(array $type): array { - if (isset($type['params'][2])) { - return $type['params'][2]; - } - if (isset($type['params'][0])) { - return $type['params'][0]; + if (isset($type['params'][0]) && is_array($type['params'][0])) { + return array_map(function ($param) { + return [ + 'format' => $param[2] ?? $param[0] ?? $this->defaultFormat, + 'timezone' => !empty($param['params'][1]) ? new \DateTimeZone($param['params'][1]) : $this->defaultTimezone, + ]; + }, $type['params']); } - return $this->defaultFormat; + + return [ + [ + 'format' => $type['params'][2] ?? $type['params'][0] ?? $this->defaultFormat, + 'timezone' => !empty($type['params'][1]) ? new \DateTimeZone($type['params'][1]) : $this->defaultTimezone, + ], + ]; } /** * @param array $type */ - private function getFormat(array $type): string + private function getFormats(array $type): array { - return $type['params'][0] ?? $this->defaultFormat; + if (isset($type['params'][0]) && is_array($type['params'][0])) { + return array_map(function ($param) { + return $param[0] ?? $this->defaultFormat; + }, $type['params']); + } + + return [$type['params'][0] ?? $this->defaultFormat]; } public function format(\DateInterval $dateInterval): string diff --git a/tests/Handler/DateHandlerTest.php b/tests/Handler/DateHandlerTest.php index 224ece1a5..dce300ab1 100644 --- a/tests/Handler/DateHandlerTest.php +++ b/tests/Handler/DateHandlerTest.php @@ -33,6 +33,7 @@ public function getParams() [['Y-m-d']], [['Y-m-d', '', 'Y-m-d|']], [['Y-m-d', '', 'Y']], + [[['Y-m-d', '', 'Y'], ['Y/m/d']]], ]; } @@ -65,6 +66,17 @@ public function testTimePartGetsRemoved() ); } + public function testMultiFormatCase() + { + $visitor = new JsonDeserializationVisitor(); + + $type = ['name' => 'DateTime', 'params' => [['Y-m-d', '', 'Y-m-d|'], ['Y/m/d']]]; + self::assertEquals( + \DateTime::createFromFormat('Y/m/d', '2017/06/18', $this->timezone), + $this->handler->deserializeDateTimeFromJson($visitor, '2017/06/18', $type) + ); + } + public function testTimePartGetsPreserved() { $visitor = new JsonDeserializationVisitor(); From 76526d1dc3b364d7a4f54e2ef22469d5afc5c9e5 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 11 Aug 2019 23:04:40 +0200 Subject: [PATCH 2/6] Add tests for array of DateTime --- tests/Serializer/Type/ParserTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/Serializer/Type/ParserTest.php b/tests/Serializer/Type/ParserTest.php index 9ceec0d6d..0d6f4d980 100644 --- a/tests/Serializer/Type/ParserTest.php +++ b/tests/Serializer/Type/ParserTest.php @@ -79,6 +79,10 @@ public function validTypesProvider(): iterable 'array', $type('array', [['name' => 'Foo\Bar', 'params' => []], ['name' => 'Baz\Boo', 'params' => []]]), ]; + yield [ + 'DateTime>', + $type('DateTime', [null, null, ['name' => 'array', 'params' => ['Y-m-d\TH:i:s', 'Y-m-d\TH:i:sP']]]), + ]; yield [ 'a,e>', $type('a', [['name' => 'b', 'params' => [['name' => 'c', 'params' => []], ['name' => 'd', 'params' => []]]], ['name' => 'e', 'params' => []]]), From 7d81f6ae961801416aa3a8a3120316b978a8f133 Mon Sep 17 00:00:00 2001 From: Michael Moravec Date: Thu, 19 Sep 2019 03:21:39 +0200 Subject: [PATCH 3/6] Accept arrays as type attribute values --- src/Type/InnerParser.php | 31 ++++++++++++++++++++-------- src/Type/TypeVisitor.php | 17 +++++++++++++++ src/Type/grammar.pp | 6 ++++++ tests/Serializer/Type/ParserTest.php | 24 +++++++++++++++++++++ 4 files changed, 69 insertions(+), 9 deletions(-) diff --git a/src/Type/InnerParser.php b/src/Type/InnerParser.php index eb05aa448..736f52e42 100644 --- a/src/Type/InnerParser.php +++ b/src/Type/InnerParser.php @@ -23,6 +23,8 @@ public function __construct() [ 'default' => [ 'skip' => '\s+', + 'array_' => '\[', + '_array' => '\]', 'parenthesis_' => '<', '_parenthesis' => '>', 'empty_string' => '""|\'\'', @@ -60,22 +62,33 @@ public function __construct() 14 => new Token(14, 'apostrophed_string', null, -1, true), 15 => new Token(15, '_apostrophe', null, -1, false), 16 => new Concatenation(16, [13, 14, 15], '#simple_type'), - 'simple_type' => new Choice('simple_type', [2, 4, 6, 8, 12, 16], null), - 18 => new Token(18, 'name', null, -1, true), - 19 => new Token(19, 'parenthesis_', null, -1, false), - 20 => new Token(20, 'comma', null, -1, false), - 21 => new Concatenation(21, [20, 'type'], '#compound_type'), - 22 => new Repetition(22, 0, -1, 21, null), - 23 => new Token(23, '_parenthesis', null, -1, false), - 'compound_type' => new Concatenation('compound_type', [18, 19, 'type', 22, 23], null), + 17 => new Concatenation(17, ['array'], '#simple_type'), + 'simple_type' => new Choice('simple_type', [2, 4, 6, 8, 12, 16, 17], null), + 19 => new Token(19, 'name', null, -1, true), + 20 => new Token(20, 'parenthesis_', null, -1, false), + 21 => new Token(21, 'comma', null, -1, false), + 22 => new Concatenation(22, [21, 'type'], '#compound_type'), + 23 => new Repetition(23, 0, -1, 22, null), + 24 => new Token(24, '_parenthesis', null, -1, false), + 'compound_type' => new Concatenation('compound_type', [19, 20, 'type', 23, 24], null), + 26 => new Token(26, 'array_', null, -1, false), + 27 => new Token(27, 'comma', null, -1, false), + 28 => new Concatenation(28, [27, 'simple_type'], '#array'), + 29 => new Repetition(29, 0, -1, 28, null), + 30 => new Concatenation(30, ['simple_type', 29], null), + 31 => new Repetition(31, 0, 1, 30, null), + 32 => new Token(32, '_array', null, -1, false), + 'array' => new Concatenation('array', [26, 31, 32], null), ], [] ); $this->getRule('type')->setPPRepresentation(' simple_type() | compound_type()'); $this->getRule('simple_type')->setDefaultId('#simple_type'); - $this->getRule('simple_type')->setPPRepresentation(' | | | | ::quote_:: ::_quote:: | ::apostrophe_:: ::_apostrophe::'); + $this->getRule('simple_type')->setPPRepresentation(' | | | | ::quote_:: ::_quote:: | ::apostrophe_:: ::_apostrophe:: | array()'); $this->getRule('compound_type')->setDefaultId('#compound_type'); $this->getRule('compound_type')->setPPRepresentation(' ::parenthesis_:: type() ( ::comma:: type() )* ::_parenthesis::'); + $this->getRule('array')->setDefaultId('#array'); + $this->getRule('array')->setPPRepresentation(' ::array_:: ( simple_type() ( ::comma:: simple_type() )* )? ::_array::'); } } diff --git a/src/Type/TypeVisitor.php b/src/Type/TypeVisitor.php index 6b9257c62..ec140b100 100644 --- a/src/Type/TypeVisitor.php +++ b/src/Type/TypeVisitor.php @@ -22,6 +22,8 @@ public function visit(Element $element, &$handle = null, $eldnah = null) return $this->visitSimpleType($element); case '#compound_type': return $this->visitCompoundType($element, $handle, $eldnah); + case '#array': + return $this->visitArrayType($element, $handle, $eldnah); } throw new InvalidNode(); @@ -33,6 +35,11 @@ public function visit(Element $element, &$handle = null, $eldnah = null) private function visitSimpleType(TreeNode $element) { $tokenNode = $element->getChild(0); + + if (!$tokenNode->isToken()) { + return $tokenNode->accept($this); + } + $token = $tokenNode->getValueToken(); $value = $tokenNode->getValueValue(); @@ -76,4 +83,14 @@ function (TreeNode $node) use ($handle, $eldnah) { ), ]; } + + private function visitArrayType(TreeNode $node, ?int &$handle, ?int $eldnah): array + { + return array_map( + function (TreeNode $child) { + return $child->accept($this); + }, + $node->getChildren() + ); + } } diff --git a/src/Type/grammar.pp b/src/Type/grammar.pp index fe98e784e..2192d6c86 100644 --- a/src/Type/grammar.pp +++ b/src/Type/grammar.pp @@ -1,5 +1,7 @@ %skip whitespace \s+ +%token array_ \[ +%token _array \] %token parenthesis_ < %token _parenthesis > %token empty_string ""|'' @@ -26,6 +28,7 @@ | | ::quote_:: ::_quote:: | ::apostrophe_:: ::_apostrophe:: + | array() #compound_type: @@ -33,3 +36,6 @@ type() ( ::comma:: type() )* ::_parenthesis:: + +#array: + ::array_:: ( simple_type() ( ::comma:: simple_type() )* )? ::_array:: diff --git a/tests/Serializer/Type/ParserTest.php b/tests/Serializer/Type/ParserTest.php index 0d6f4d980..1e099228e 100644 --- a/tests/Serializer/Type/ParserTest.php +++ b/tests/Serializer/Type/ParserTest.php @@ -100,6 +100,30 @@ public function validTypesProvider(): iterable 'Foo<"asdf asdf">', $type('Foo', ['asdf asdf']), ]; + yield [ + 'Foo<[]>', + $type('Foo', [[]]), + ]; + yield [ + 'Foo<[[]]>', + $type('Foo', [[[]]]), + ]; + yield [ + 'Foo<[123]>', + $type('Foo', [[123]]), + ]; + yield [ + 'Foo<[123, 456]>', + $type('Foo', [[123, 456]]), + ]; + yield [ + 'Foo<[[123], 456, "bar"]>', + $type('Foo', [[[123], 456, 'bar']]), + ]; + yield [ + 'DateTime', + $type('DateTime', [null, null, ['Y-m-d\TH:i:s', 'Y-m-d\TH:i:sP']]), + ]; } public function testEmptyString(): void From 8f17c4e36a21e739643267cdb5e907af87f1bf3f Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 10 Dec 2019 00:34:03 +0100 Subject: [PATCH 4/6] Update with new syntax --- src/Handler/DateHandler.php | 39 ++++++++++------------------ tests/Handler/DateHandlerTest.php | 4 +-- tests/Serializer/Type/ParserTest.php | 4 --- 3 files changed, 15 insertions(+), 32 deletions(-) diff --git a/src/Handler/DateHandler.php b/src/Handler/DateHandler.php index 630ccc601..a271bee56 100644 --- a/src/Handler/DateHandler.php +++ b/src/Handler/DateHandler.php @@ -229,13 +229,11 @@ public function deserializeDateIntervalFromJson(DeserializationVisitorInterface */ private function parseDateTime($data, array $type, bool $immutable = false): \DateTimeInterface { - $formatsAndTimeZones = $this->getDeserializationFormatsAndTimeZones($type); + $timezone = !empty($type['params'][1]) ? new \DateTimeZone($type['params'][1]) : $this->defaultTimezone; + $formats = $this->getDeserializationFormats($type); $formatTried = []; - foreach ($formatsAndTimeZones as $formatsAndTimeZone) { - $format = $formatsAndTimeZone['format']; - $timezone = $formatsAndTimeZone['timezone']; - + foreach ($formats as $format) { if ($immutable) { $datetime = \DateTimeImmutable::createFromFormat($format, (string) $data, $timezone); } else { @@ -275,23 +273,15 @@ private function parseDateInterval(string $data): \DateInterval /** * @param array $type */ - private function getDeserializationFormatsAndTimeZones(array $type): array + private function getDeserializationFormats(array $type): array { - if (isset($type['params'][0]) && is_array($type['params'][0])) { - return array_map(function ($param) { - return [ - 'format' => $param[2] ?? $param[0] ?? $this->defaultFormat, - 'timezone' => !empty($param['params'][1]) ? new \DateTimeZone($param['params'][1]) : $this->defaultTimezone, - ]; - }, $type['params']); + if (isset($type['params'][2])) { + return is_array($type['params'][2]) ? $type['params'][2] : [$type['params'][2]]; } - - return [ - [ - 'format' => $type['params'][2] ?? $type['params'][0] ?? $this->defaultFormat, - 'timezone' => !empty($type['params'][1]) ? new \DateTimeZone($type['params'][1]) : $this->defaultTimezone, - ], - ]; + if (isset($type['params'][0])) { + return is_array($type['params'][0]) ? $type['params'][0] : [$type['params'][0]]; + } + return [$this->defaultFormat]; } /** @@ -299,13 +289,10 @@ private function getDeserializationFormatsAndTimeZones(array $type): array */ private function getFormats(array $type): array { - if (isset($type['params'][0]) && is_array($type['params'][0])) { - return array_map(function ($param) { - return $param[0] ?? $this->defaultFormat; - }, $type['params']); + if (isset($type['params'][0])) { + return is_array($type['params'][0]) ? $type['params'][0] : [$type['params'][0]]; } - - return [$type['params'][0] ?? $this->defaultFormat]; + return [$this->defaultFormat]; } public function format(\DateInterval $dateInterval): string diff --git a/tests/Handler/DateHandlerTest.php b/tests/Handler/DateHandlerTest.php index dce300ab1..17b69bd16 100644 --- a/tests/Handler/DateHandlerTest.php +++ b/tests/Handler/DateHandlerTest.php @@ -33,7 +33,7 @@ public function getParams() [['Y-m-d']], [['Y-m-d', '', 'Y-m-d|']], [['Y-m-d', '', 'Y']], - [[['Y-m-d', '', 'Y'], ['Y/m/d']]], + [[['Y-m-d', 'Y/m/d'], '', 'Y']], ]; } @@ -70,7 +70,7 @@ public function testMultiFormatCase() { $visitor = new JsonDeserializationVisitor(); - $type = ['name' => 'DateTime', 'params' => [['Y-m-d', '', 'Y-m-d|'], ['Y/m/d']]]; + $type = ['name' => 'DateTime', 'params' => ['Y-m-d', '', ['Y-m-d|', 'Y/m/d']]]; self::assertEquals( \DateTime::createFromFormat('Y/m/d', '2017/06/18', $this->timezone), $this->handler->deserializeDateTimeFromJson($visitor, '2017/06/18', $type) diff --git a/tests/Serializer/Type/ParserTest.php b/tests/Serializer/Type/ParserTest.php index 1e099228e..cab16096e 100644 --- a/tests/Serializer/Type/ParserTest.php +++ b/tests/Serializer/Type/ParserTest.php @@ -79,10 +79,6 @@ public function validTypesProvider(): iterable 'array', $type('array', [['name' => 'Foo\Bar', 'params' => []], ['name' => 'Baz\Boo', 'params' => []]]), ]; - yield [ - 'DateTime>', - $type('DateTime', [null, null, ['name' => 'array', 'params' => ['Y-m-d\TH:i:s', 'Y-m-d\TH:i:sP']]]), - ]; yield [ 'a,e>', $type('a', [['name' => 'b', 'params' => [['name' => 'c', 'params' => []], ['name' => 'd', 'params' => []]]], ['name' => 'e', 'params' => []]]), From 0aefabca78138eb3dde9cea4f115f67c00b964db Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 10 Dec 2019 00:56:22 +0100 Subject: [PATCH 5/6] Update doc --- doc/reference/annotations.rst | 211 ++++++++++++++++++---------------- 1 file changed, 112 insertions(+), 99 deletions(-) diff --git a/doc/reference/annotations.rst b/doc/reference/annotations.rst index 2ada97776..6fc0cb9f7 100644 --- a/doc/reference/annotations.rst +++ b/doc/reference/annotations.rst @@ -45,8 +45,8 @@ This annotation can be defined on a property to define the serialized name for a property. If this is not defined, the property will be translated from camel-case to a lower-cased underscored name, e.g. camelCase -> camel_case. -Note that this annotation is not used when you're using any other naming -strategy than the default configuration (which includes the +Note that this annotation is not used when you're using any other naming +strategy than the default configuration (which includes the ``SerializedNameAnnotationStrategy``). In order to re-enable the annotation, you will need to wrap your custom strategy with the ``SerializedNameAnnotationStrategy``. @@ -142,7 +142,7 @@ be called to retrieve, or set the value of the given property: $this->name = $name; } } - + .. note :: If you need only to serialize your data, you can avoid providing a setter by @@ -327,102 +327,105 @@ used when deserializing them. Available Types: -+----------------------------------------------------------+--------------------------------------------------+ -| Type | Description | -+==========================================================+==================================================+ -| boolean or bool | Primitive boolean | -+----------------------------------------------------------+--------------------------------------------------+ -| integer or int | Primitive integer | -+----------------------------------------------------------+--------------------------------------------------+ -| double or float | Primitive double | -+----------------------------------------------------------+--------------------------------------------------+ -| string | Primitive string | -+----------------------------------------------------------+--------------------------------------------------+ -| array | An array with arbitrary keys, and values. | -+----------------------------------------------------------+--------------------------------------------------+ -| array | A list of type T (T can be any available type). | -| | Examples: | -| | array, array, etc. | -+----------------------------------------------------------+--------------------------------------------------+ -| array | A map of keys of type K to values of type V. | -| | Examples: array, | -| | array, etc. | -+----------------------------------------------------------+--------------------------------------------------+ -| DateTime | PHP's DateTime object (default format*/timezone) | -+----------------------------------------------------------+--------------------------------------------------+ -| DateTime<'format'> | PHP's DateTime object (custom format/default | -| | timezone) | -+----------------------------------------------------------+--------------------------------------------------+ -| DateTime<'format', 'zone'> | PHP's DateTime object (custom format/timezone) | -+----------------------------------------------------------+--------------------------------------------------+ -| DateTime<'format', 'zone', 'deserializeFormat'> | PHP's DateTime object (custom format/timezone, | -| | deserialize format). If you do not want to | -| | specify a specific timezone, use an empty | -| | string (''). | -+----------------------------------------------------------+--------------------------------------------------+ -| DateTimeImmutable | PHP's DateTimeImmutable object (default format*/ | -| | timezone) | -+----------------------------------------------------------+--------------------------------------------------+ -| DateTimeImmutable<'format'> | PHP's DateTimeImmutable object (custom format/ | -| | default timezone) | -+----------------------------------------------------------+--------------------------------------------------+ -| DateTimeImmutable<'format', 'zone'> | PHP's DateTimeImmutable object (custom format/ | -| | timezone) | -+----------------------------------------------------------+--------------------------------------------------+ -| DateTimeImmutable<'format', 'zone', 'deserializeFormat'> | PHP's DateTimeImmutable object (custom format/ | -| | timezone/deserialize format). If you do not want | -| | to specify a specific timezone, use an empty | -| | string (''). | -+----------------------------------------------------------+--------------------------------------------------+ -| DateInterval | PHP's DateInterval object using ISO 8601 format | -+----------------------------------------------------------+--------------------------------------------------+ -| T | Where T is a fully qualified class name. | -+----------------------------------------------------------+--------------------------------------------------+ -| iterable | Similar to array. Will always be deserialized | -| | into array as implementation info is lost during | -| | serialization. | -+----------------------------------------------------------+--------------------------------------------------+ -| iterable | Similar to array. Will always be deserialized | -| | into array as implementation info is lost during | -| | serialization. | -+----------------------------------------------------------+--------------------------------------------------+ -| iterable | Similar to array. Will always be | -| | deserialized into array as implementation info | -| | is lost during serialization. | -+----------------------------------------------------------+--------------------------------------------------+ -| ArrayCollection | Similar to array, but will be deserialized | -| | into Doctrine's ArrayCollection class. | -+----------------------------------------------------------+--------------------------------------------------+ -| ArrayCollection | Similar to array, but will be deserialized | -| | into Doctrine's ArrayCollection class. | -+----------------------------------------------------------+--------------------------------------------------+ -| Generator | Similar to array, but will be deserialized | -| | into Generator class. | -+----------------------------------------------------------+--------------------------------------------------+ -| Generator | Similar to array, but will be deserialized | -| | into Generator class. | -+----------------------------------------------------------+--------------------------------------------------+ -| Generator | Similar to array, but will be deserialized | -| | into Generator class. | -+----------------------------------------------------------+--------------------------------------------------+ -| ArrayIterator | Similar to array, but will be deserialized | -| | into ArrayIterator class. | -+----------------------------------------------------------+--------------------------------------------------+ -| ArrayIterator | Similar to array, but will be deserialized | -| | into ArrayIterator class. | -+----------------------------------------------------------+--------------------------------------------------+ -| ArrayIterator | Similar to array, but will be deserialized | -| | into ArrayIterator class. | -+----------------------------------------------------------+--------------------------------------------------+ -| Iterator | Similar to array, but will be deserialized | -| | into ArrayIterator class. | -+----------------------------------------------------------+--------------------------------------------------+ -| Iterator | Similar to array, but will be deserialized | -| | into ArrayIterator class. | -+----------------------------------------------------------+--------------------------------------------------+ -| Iterator | Similar to array, but will be deserialized | -| | into ArrayIterator class. | -+----------------------------------------------------------+--------------------------------------------------+ ++------------------------------------------------------------+--------------------------------------------------+ +| Type | Description | ++============================================================+==================================================+ +| boolean or bool | Primitive boolean | ++------------------------------------------------------------+--------------------------------------------------+ +| integer or int | Primitive integer | ++------------------------------------------------------------+--------------------------------------------------+ +| double or float | Primitive double | ++------------------------------------------------------------+--------------------------------------------------+ +| string | Primitive string | ++------------------------------------------------------------+--------------------------------------------------+ +| array | An array with arbitrary keys, and values. | ++------------------------------------------------------------+--------------------------------------------------+ +| array | A list of type T (T can be any available type). | +| | Examples: | +| | array, array, etc. | ++------------------------------------------------------------+--------------------------------------------------+ +| array | A map of keys of type K to values of type V. | +| | Examples: array, | +| | array, etc. | ++------------------------------------------------------------+--------------------------------------------------+ +| DateTime | PHP's DateTime object (default format*/timezone) | ++------------------------------------------------------------+--------------------------------------------------+ +| DateTime<'formats'> | PHP's DateTime object (custom format/default | +| | timezone). Formats can either be a string or an | +| | array of string. | ++------------------------------------------------------------+--------------------------------------------------+ +| DateTime<'formats', 'zone'> | PHP's DateTime object (custom format/timezone) | ++------------------------------------------------------------+--------------------------------------------------+ +| DateTime<'formats', 'zone', 'deserializeFormats'> | PHP's DateTime object (custom format/timezone, | +| | deserialize format). If you do not want to | +| | specify a specific timezone, use an empty | +| | string (''). | ++------------------------------------------------------------+--------------------------------------------------+ +| DateTimeImmutable | PHP's DateTimeImmutable object (default format*/ | +| | timezone). Formats can either be a string or an | +| | array of string. | ++------------------------------------------------------------+--------------------------------------------------+ +| DateTimeImmutable<'formats'> | PHP's DateTimeImmutable object (custom format/ | +| | default timezone) | ++------------------------------------------------------------+--------------------------------------------------+ +| DateTimeImmutable<'formats', 'zone'> | PHP's DateTimeImmutable object (custom format/ | +| | timezone) | ++------------------------------------------------------------+--------------------------------------------------+ +| DateTimeImmutable<'formats', 'zone', 'deserializeFormats'> | PHP's DateTimeImmutable object (custom format/ | +| | timezone/deserialize format). If you do not want | +| | to specify a specific timezone, use an empty | +| | string (''). DeserializeFormats can either be a | +| | string or an array of string. | ++------------------------------------------------------------+--------------------------------------------------+ +| DateInterval | PHP's DateInterval object using ISO 8601 format | ++------------------------------------------------------------+--------------------------------------------------+ +| T | Where T is a fully qualified class name. | ++------------------------------------------------------------+--------------------------------------------------+ +| iterable | Similar to array. Will always be deserialized | +| | into array as implementation info is lost during | +| | serialization. | ++------------------------------------------------------------+--------------------------------------------------+ +| iterable | Similar to array. Will always be deserialized | +| | into array as implementation info is lost during | +| | serialization. | ++------------------------------------------------------------+--------------------------------------------------+ +| iterable | Similar to array. Will always be | +| | deserialized into array as implementation info | +| | is lost during serialization. | ++------------------------------------------------------------+--------------------------------------------------+ +| ArrayCollection | Similar to array, but will be deserialized | +| | into Doctrine's ArrayCollection class. | ++------------------------------------------------------------+--------------------------------------------------+ +| ArrayCollection | Similar to array, but will be deserialized | +| | into Doctrine's ArrayCollection class. | ++------------------------------------------------------------+--------------------------------------------------+ +| Generator | Similar to array, but will be deserialized | +| | into Generator class. | ++------------------------------------------------------------+--------------------------------------------------+ +| Generator | Similar to array, but will be deserialized | +| | into Generator class. | ++------------------------------------------------------------+--------------------------------------------------+ +| Generator | Similar to array, but will be deserialized | +| | into Generator class. | ++------------------------------------------------------------+--------------------------------------------------+ +| ArrayIterator | Similar to array, but will be deserialized | +| | into ArrayIterator class. | ++------------------------------------------------------------+--------------------------------------------------+ +| ArrayIterator | Similar to array, but will be deserialized | +| | into ArrayIterator class. | ++------------------------------------------------------------+--------------------------------------------------+ +| ArrayIterator | Similar to array, but will be deserialized | +| | into ArrayIterator class. | ++------------------------------------------------------------+--------------------------------------------------+ +| Iterator | Similar to array, but will be deserialized | +| | into ArrayIterator class. | ++------------------------------------------------------------+--------------------------------------------------+ +| Iterator | Similar to array, but will be deserialized | +| | into ArrayIterator class. | ++------------------------------------------------------------+--------------------------------------------------+ +| Iterator | Similar to array, but will be deserialized | +| | into ArrayIterator class. | ++------------------------------------------------------------+--------------------------------------------------+ (*) If the standalone jms/serializer is used then default format is `\DateTime::ISO8601` (which is not compatible with ISO-8601 despite the name). For jms/serializer-bundle the default format is `\DateTime::ATOM` (the real ISO-8601 format) but it can be changed in `configuration`_. @@ -463,6 +466,11 @@ Examples: */ private $endAt; + /** + * @Type("DateTime<['Y-m-d', 'Y/m/d']>") + */ + private $publishedAt; + /** * @Type("DateTimeImmutable") */ @@ -473,6 +481,11 @@ Examples: */ private $updatedAt; + /** + * @Type("DateTimeImmutable<['Y-m-d', 'Y/m/d']>") + */ + private $deletedAt; + /** * @Type("boolean") */ From a2c5a598439db002f84733a9321bb253dee34d9c Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 19 Jan 2020 17:16:28 +0100 Subject: [PATCH 6/6] Allow feature only for deserialization --- doc/reference/annotations.rst | 25 +++++++------ src/Handler/DateHandler.php | 36 ++++++------------- tests/Handler/DateHandlerTest.php | 2 +- tests/Serializer/BaseSerializationTest.php | 1 + tests/Serializer/JsonSerializationTest.php | 1 + .../Serializer/xml/date_time_multi_format.xml | 2 ++ .../xml/date_time_multi_format_no_cdata.xml | 2 ++ 7 files changed, 30 insertions(+), 39 deletions(-) create mode 100644 tests/Serializer/xml/date_time_multi_format.xml create mode 100644 tests/Serializer/xml/date_time_multi_format_no_cdata.xml diff --git a/doc/reference/annotations.rst b/doc/reference/annotations.rst index 6fc0cb9f7..1094db672 100644 --- a/doc/reference/annotations.rst +++ b/doc/reference/annotations.rst @@ -350,28 +350,27 @@ Available Types: +------------------------------------------------------------+--------------------------------------------------+ | DateTime | PHP's DateTime object (default format*/timezone) | +------------------------------------------------------------+--------------------------------------------------+ -| DateTime<'formats'> | PHP's DateTime object (custom format/default | -| | timezone). Formats can either be a string or an | -| | array of string. | +| DateTime<'format'> | PHP's DateTime object (custom format/default | +| | timezone). | +------------------------------------------------------------+--------------------------------------------------+ -| DateTime<'formats', 'zone'> | PHP's DateTime object (custom format/timezone) | +| DateTime<'format', 'zone'> | PHP's DateTime object (custom format/timezone) | +------------------------------------------------------------+--------------------------------------------------+ -| DateTime<'formats', 'zone', 'deserializeFormats'> | PHP's DateTime object (custom format/timezone, | +| DateTime<'format', 'zone', 'deserializeFormats'> | PHP's DateTime object (custom format/timezone, | | | deserialize format). If you do not want to | | | specify a specific timezone, use an empty | -| | string (''). | +| | string (''). DeserializeFormats can either be a | +| | string or an array of string. | +------------------------------------------------------------+--------------------------------------------------+ | DateTimeImmutable | PHP's DateTimeImmutable object (default format*/ | -| | timezone). Formats can either be a string or an | -| | array of string. | +| | timezone). | +------------------------------------------------------------+--------------------------------------------------+ -| DateTimeImmutable<'formats'> | PHP's DateTimeImmutable object (custom format/ | +| DateTimeImmutable<'format'> | PHP's DateTimeImmutable object (custom format/ | | | default timezone) | +------------------------------------------------------------+--------------------------------------------------+ -| DateTimeImmutable<'formats', 'zone'> | PHP's DateTimeImmutable object (custom format/ | +| DateTimeImmutable<'format', 'zone'> | PHP's DateTimeImmutable object (custom format/ | | | timezone) | +------------------------------------------------------------+--------------------------------------------------+ -| DateTimeImmutable<'formats', 'zone', 'deserializeFormats'> | PHP's DateTimeImmutable object (custom format/ | +| DateTimeImmutable<'format', 'zone', 'deserializeFormats'> | PHP's DateTimeImmutable object (custom format/ | | | timezone/deserialize format). If you do not want | | | to specify a specific timezone, use an empty | | | string (''). DeserializeFormats can either be a | @@ -467,7 +466,7 @@ Examples: private $endAt; /** - * @Type("DateTime<['Y-m-d', 'Y/m/d']>") + * @Type("DateTime<'Y-m-d', '', ['Y-m-d', 'Y/m/d']>") */ private $publishedAt; @@ -482,7 +481,7 @@ Examples: private $updatedAt; /** - * @Type("DateTimeImmutable<['Y-m-d', 'Y/m/d']>") + * @Type("DateTimeImmutable<'Y-m-d', '', ['Y-m-d', 'Y/m/d']>") */ private $deletedAt; diff --git a/src/Handler/DateHandler.php b/src/Handler/DateHandler.php index a271bee56..a004dc15b 100644 --- a/src/Handler/DateHandler.php +++ b/src/Handler/DateHandler.php @@ -75,25 +75,16 @@ private function serializeDateTimeInterface( array $type, SerializationContext $context ) { - $formats = $this->getFormats($type); - - foreach ($formats as $format) { - $dateFormatted = $date->format($format); - - if (false !== $dateFormatted) { - if ($visitor instanceof XmlSerializationVisitor && false === $this->xmlCData) { - return $visitor->visitSimpleString($dateFormatted, $type); - } - - if ('U' === $format) { - return $visitor->visitInteger((int) $dateFormatted, $type); - } + if ($visitor instanceof XmlSerializationVisitor && false === $this->xmlCData) { + return $visitor->visitSimpleString($date->format($this->getFormat($type)), $type); + } - return $visitor->visitString($dateFormatted, $type); - } + $format = $this->getFormat($type); + if ('U' === $format) { + return $visitor->visitInteger((int) $date->format($format), $type); } - throw new RuntimeException(sprintf('The date "%s" could not be formatted', $date)); + return $visitor->visitString($date->format($this->getFormat($type)), $type); } /** @@ -278,21 +269,16 @@ private function getDeserializationFormats(array $type): array if (isset($type['params'][2])) { return is_array($type['params'][2]) ? $type['params'][2] : [$type['params'][2]]; } - if (isset($type['params'][0])) { - return is_array($type['params'][0]) ? $type['params'][0] : [$type['params'][0]]; - } - return [$this->defaultFormat]; + + return [$this->getFormat($type)]; } /** * @param array $type */ - private function getFormats(array $type): array + private function getFormat(array $type): string { - if (isset($type['params'][0])) { - return is_array($type['params'][0]) ? $type['params'][0] : [$type['params'][0]]; - } - return [$this->defaultFormat]; + return $type['params'][0] ?? $this->defaultFormat; } public function format(\DateInterval $dateInterval): string diff --git a/tests/Handler/DateHandlerTest.php b/tests/Handler/DateHandlerTest.php index 17b69bd16..881c664d2 100644 --- a/tests/Handler/DateHandlerTest.php +++ b/tests/Handler/DateHandlerTest.php @@ -33,7 +33,7 @@ public function getParams() [['Y-m-d']], [['Y-m-d', '', 'Y-m-d|']], [['Y-m-d', '', 'Y']], - [[['Y-m-d', 'Y/m/d'], '', 'Y']], + [['Y-m-d', '', ['Y-m-d', 'Y/m/d']]], ]; } diff --git a/tests/Serializer/BaseSerializationTest.php b/tests/Serializer/BaseSerializationTest.php index dc02723a6..a343e8b75 100644 --- a/tests/Serializer/BaseSerializationTest.php +++ b/tests/Serializer/BaseSerializationTest.php @@ -665,6 +665,7 @@ public function getDateTime() { return [ ['date_time', new \DateTime('2011-08-30 00:00', new \DateTimeZone('UTC')), 'DateTime'], + ['date_time_multi_format', new \DateTime('2011-08-30 00:00', new \DateTimeZone('UTC')), "DateTime<'Y-m-d', '', ['Y-m-d','Y-m-d\TH:i:sP']>"], ]; } diff --git a/tests/Serializer/JsonSerializationTest.php b/tests/Serializer/JsonSerializationTest.php index 471d5e378..09f88ddfe 100644 --- a/tests/Serializer/JsonSerializationTest.php +++ b/tests/Serializer/JsonSerializationTest.php @@ -93,6 +93,7 @@ protected function getContent($key) $outputs['object_when_null_and_serialized'] = '{"author":null,"text":"foo"}'; $outputs['date_time'] = '"2011-08-30T00:00:00+00:00"'; $outputs['date_time_immutable'] = '"2011-08-30T00:00:00+00:00"'; + $outputs['date_time_multi_format'] = '"2011-08-30T00:00:00+00:00"'; $outputs['timestamp'] = '{"timestamp":1455148800}'; $outputs['timestamp_prev'] = '{"timestamp":"1455148800"}'; $outputs['date_interval'] = '"PT45M"'; diff --git a/tests/Serializer/xml/date_time_multi_format.xml b/tests/Serializer/xml/date_time_multi_format.xml new file mode 100644 index 000000000..5d0767627 --- /dev/null +++ b/tests/Serializer/xml/date_time_multi_format.xml @@ -0,0 +1,2 @@ + + diff --git a/tests/Serializer/xml/date_time_multi_format_no_cdata.xml b/tests/Serializer/xml/date_time_multi_format_no_cdata.xml new file mode 100644 index 000000000..d596a0f83 --- /dev/null +++ b/tests/Serializer/xml/date_time_multi_format_no_cdata.xml @@ -0,0 +1,2 @@ + +2011-08-30T00:00:00+00:00