Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 32a5233

Browse files
committed
[Serializer] Add support for denormalizing invalid datetime without throwing an exception
1 parent c1c973c commit 32a5233

File tree

6 files changed

+74
-6
lines changed

6 files changed

+74
-6
lines changed

UPGRADE-5.4.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,8 @@ Security
4545
* Deprecate `TokenInterface:isAuthenticated()` and `setAuthenticated()` methods without replacement.
4646
Security tokens won't have an "authenticated" flag anymore, so they will always be considered authenticated
4747
* Deprecate `DeauthenticatedEvent`, use `TokenDeauthenticatedEvent` instead
48+
49+
Serializer
50+
----------
51+
52+
* Deprecate not setting a value for the context key `DateTimeNormalizer::THROW_EXCEPTION_ON_INVALID_KEY`

UPGRADE-6.0.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@ Serializer
349349
* Removed `ArrayDenormalizer::setSerializer()`, call `setDenormalizer()` instead.
350350
* `ArrayDenormalizer` does not implement `SerializerAwareInterface` anymore.
351351
* The annotation classes cannot be constructed by passing an array of parameters as first argument anymore, use named arguments instead
352+
* The default context value for `DateTimeNormalizer::THROW_EXCEPTION_ON_INVALID_KEY` becomes `false`.
352353

353354
TwigBundle
354355
----------

src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/config/framework.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,9 @@ framework:
1313

1414
services:
1515
logger: { class: Psr\Log\NullLogger }
16+
17+
serializer.normalizer.datetime:
18+
class: Symfony\Component\Serializer\Normalizer\DateTimeNormalizer
19+
arguments:
20+
$defaultContext:
21+
throw_exception_on_invalid_key: false

src/Symfony/Component/Serializer/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ CHANGELOG
55
---
66

77
* Add support of PHP backed enumerations
8+
* Add support for denormalizing invalid datetime without throwing an exception
89

910
5.3
1011
---

src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,21 @@
1717
/**
1818
* Normalizes an object implementing the {@see \DateTimeInterface} to a date string.
1919
* Denormalizes a date string to an instance of {@see \DateTime} or {@see \DateTimeImmutable}.
20+
* The denormalization may return the raw data if invalid according to the value of $context[self::THROW_EXCEPTION_ON_INVALID_KEY].
2021
*
2122
* @author Kévin Dunglas <[email protected]>
2223
*/
2324
class DateTimeNormalizer implements NormalizerInterface, DenormalizerInterface, CacheableSupportsMethodInterface
2425
{
2526
public const FORMAT_KEY = 'datetime_format';
2627
public const TIMEZONE_KEY = 'datetime_timezone';
28+
public const THROW_EXCEPTION_ON_INVALID_KEY = 'throw_exception_on_invalid_key';
2729

2830
private $defaultContext = [
2931
self::FORMAT_KEY => \DateTime::RFC3339,
3032
self::TIMEZONE_KEY => null,
33+
// BC layer to be moved to "false" in 6.0
34+
self::THROW_EXCEPTION_ON_INVALID_KEY => null,
3135
];
3236

3337
private const SUPPORTED_TYPES = [
@@ -39,6 +43,10 @@ class DateTimeNormalizer implements NormalizerInterface, DenormalizerInterface,
3943
public function __construct(array $defaultContext = [])
4044
{
4145
$this->defaultContext = array_merge($this->defaultContext, $defaultContext);
46+
47+
if (null === $this->defaultContext[self::THROW_EXCEPTION_ON_INVALID_KEY]) {
48+
trigger_deprecation('symfony/serializer', '5.4', 'The key context "%s" of "%s" must be defined. The value will be "false" in Symfony 6.0.', self::THROW_EXCEPTION_ON_INVALID_KEY, __CLASS__);
49+
}
4250
}
4351

4452
/**
@@ -77,15 +85,22 @@ public function supportsNormalization($data, string $format = null)
7785
* {@inheritdoc}
7886
*
7987
* @throws NotNormalizableValueException
80-
*
81-
* @return \DateTimeInterface
8288
*/
8389
public function denormalize($data, string $type, string $format = null, array $context = [])
8490
{
8591
$dateTimeFormat = $context[self::FORMAT_KEY] ?? null;
92+
$throwExceptionOnInvalid = $context[self::THROW_EXCEPTION_ON_INVALID_KEY] ?? $this->defaultContext[self::THROW_EXCEPTION_ON_INVALID_KEY];
93+
// BC layer to be removed in 6.0
94+
if (null === $throwExceptionOnInvalid) {
95+
$throwExceptionOnInvalid = true;
96+
}
8697
$timezone = $this->getTimezone($context);
8798

8899
if (null === $data || (\is_string($data) && '' === trim($data))) {
100+
if (!$throwExceptionOnInvalid) {
101+
return $data;
102+
}
103+
89104
throw new NotNormalizableValueException('The data is either an empty string or null, you should pass a string that can be parsed with the passed format or a valid DateTime string.');
90105
}
91106

@@ -98,12 +113,20 @@ public function denormalize($data, string $type, string $format = null, array $c
98113

99114
$dateTimeErrors = \DateTime::class === $type ? \DateTime::getLastErrors() : \DateTimeImmutable::getLastErrors();
100115

116+
if (!$throwExceptionOnInvalid) {
117+
return $data;
118+
}
119+
101120
throw new NotNormalizableValueException(sprintf('Parsing datetime string "%s" using format "%s" resulted in %d errors: ', $data, $dateTimeFormat, $dateTimeErrors['error_count'])."\n".implode("\n", $this->formatDateTimeErrors($dateTimeErrors['errors'])));
102121
}
103122

104123
try {
105124
return \DateTime::class === $type ? new \DateTime($data, $timezone) : new \DateTimeImmutable($data, $timezone);
106125
} catch (\Exception $e) {
126+
if (!$throwExceptionOnInvalid) {
127+
return $data;
128+
}
129+
107130
throw new NotNormalizableValueException($e->getMessage(), $e->getCode(), $e);
108131
}
109132
}

src/Symfony/Component/Serializer/Tests/Normalizer/DateTimeNormalizerTest.php

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\Serializer\Tests\Normalizer;
1313

1414
use PHPUnit\Framework\TestCase;
15+
use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
1516
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
1617
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
1718
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
@@ -21,14 +22,18 @@
2122
*/
2223
class DateTimeNormalizerTest extends TestCase
2324
{
25+
use ExpectDeprecationTrait;
26+
2427
/**
2528
* @var DateTimeNormalizer
2629
*/
2730
private $normalizer;
2831

2932
protected function setUp(): void
3033
{
31-
$this->normalizer = new DateTimeNormalizer();
34+
$this->normalizer = new DateTimeNormalizer([
35+
DateTimeNormalizer::THROW_EXCEPTION_ON_INVALID_KEY => true,
36+
]);
3237
}
3338

3439
public function testSupportsNormalization()
@@ -51,13 +56,13 @@ public function testNormalizeUsingFormatPassedInContext()
5156

5257
public function testNormalizeUsingFormatPassedInConstructor()
5358
{
54-
$normalizer = new DateTimeNormalizer([DateTimeNormalizer::FORMAT_KEY => 'y']);
59+
$normalizer = new DateTimeNormalizer([DateTimeNormalizer::FORMAT_KEY => 'y', DateTimeNormalizer::THROW_EXCEPTION_ON_INVALID_KEY => true]);
5560
$this->assertEquals('16', $normalizer->normalize(new \DateTime('2016/01/01', new \DateTimeZone('UTC'))));
5661
}
5762

5863
public function testNormalizeUsingTimeZonePassedInConstructor()
5964
{
60-
$normalizer = new DateTimeNormalizer([DateTimeNormalizer::TIMEZONE_KEY => new \DateTimeZone('Japan')]);
65+
$normalizer = new DateTimeNormalizer([DateTimeNormalizer::TIMEZONE_KEY => new \DateTimeZone('Japan'), DateTimeNormalizer::THROW_EXCEPTION_ON_INVALID_KEY => true]);
6166

6267
$this->assertSame('2016-12-01T00:00:00+09:00', $normalizer->normalize(new \DateTime('2016/12/01', new \DateTimeZone('Japan'))));
6368
$this->assertSame('2016-12-01T09:00:00+09:00', $normalizer->normalize(new \DateTime('2016/12/01', new \DateTimeZone('UTC'))));
@@ -184,7 +189,7 @@ public function testDenormalizeUsingTimezonePassedInConstructor()
184189
{
185190
$timezone = new \DateTimeZone('Japan');
186191
$expected = new \DateTime('2016/12/01 17:35:00', $timezone);
187-
$normalizer = new DateTimeNormalizer([DateTimeNormalizer::TIMEZONE_KEY => $timezone]);
192+
$normalizer = new DateTimeNormalizer([DateTimeNormalizer::TIMEZONE_KEY => $timezone, DateTimeNormalizer::THROW_EXCEPTION_ON_INVALID_KEY => true]);
188193

189194
$this->assertEquals($expected, $normalizer->denormalize('2016.12.01 17:35:00', \DateTime::class, null, [
190195
DateTimeNormalizer::FORMAT_KEY => 'Y.m.d H:i:s',
@@ -276,4 +281,31 @@ public function testDenormalizeFormatMismatchThrowsException()
276281
$this->expectException(UnexpectedValueException::class);
277282
$this->normalizer->denormalize('2016-01-01T00:00:00+00:00', \DateTimeInterface::class, null, [DateTimeNormalizer::FORMAT_KEY => 'Y-m-d|']);
278283
}
284+
285+
public function provideDenormalizeInvalidDataDontThrowsExceptionTests()
286+
{
287+
yield ['invalid date'];
288+
yield [null];
289+
yield [''];
290+
yield [' '];
291+
yield [' 2016.01.01 ', [DateTimeNormalizer::FORMAT_KEY => 'Y.m.d|']];
292+
yield ['2016-01-01T00:00:00+00:00', [DateTimeNormalizer::FORMAT_KEY => 'Y.m.d|']];
293+
}
294+
295+
/** @dataProvider provideDenormalizeInvalidDataDontThrowsExceptionTests */
296+
public function testDenormalizeInvalidDataDontThrowsException($data, array $context = [])
297+
{
298+
$normalizer = new DateTimeNormalizer([DateTimeNormalizer::THROW_EXCEPTION_ON_INVALID_KEY => false]);
299+
$this->assertSame($data, $normalizer->denormalize($data, \DateTimeInterface::class, null, $context));
300+
}
301+
302+
/**
303+
* @group legacy
304+
*/
305+
public function testLegacyConstructor()
306+
{
307+
$this->expectDeprecation('Since symfony/serializer 5.4: The key context "throw_exception_on_invalid_key" of "Symfony\Component\Serializer\Normalizer\DateTimeNormalizer" must be defined. The value will be "false" in Symfony 6.0.');
308+
309+
new DateTimeNormalizer();
310+
}
279311
}

0 commit comments

Comments
 (0)