Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit c7e84cc

Browse filesBrowse files
committed
feature #23747 [Serializer][FrameworkBundle] Add a DateInterval normalizer (Lctrs)
This PR was squashed before being merged into the 3.4 branch (closes #23747). Discussion ---------- [Serializer][FrameworkBundle] Add a DateInterval normalizer | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | | License | MIT | Doc PR | symfony/symfony-docs#8267 Could be useful for API needing to submit a duration. Most code have been adapted from @MisatoTremor's DateInterval form type. Credits to him. Commits ------- 6185cb1 [Serializer][FrameworkBundle] Add a DateInterval normalizer
2 parents e3fa71c + 6185cb1 commit c7e84cc
Copy full SHA for c7e84cc

File tree

5 files changed

+268
-0
lines changed
Filter options

5 files changed

+268
-0
lines changed

‎src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+8Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
use Symfony\Component\Serializer\Encoder\YamlEncoder;
6868
use Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory;
6969
use Symfony\Component\Serializer\Normalizer\DataUriNormalizer;
70+
use Symfony\Component\Serializer\Normalizer\DateIntervalNormalizer;
7071
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
7172
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
7273
use Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer;
@@ -1525,6 +1526,13 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder
15251526
$definition->addTag('serializer.normalizer', array('priority' => -920));
15261527
}
15271528

1529+
if (class_exists(DateIntervalNormalizer::class)) {
1530+
// Run before serializer.normalizer.object
1531+
$definition = $container->register('serializer.normalizer.dateinterval', DateIntervalNormalizer::class);
1532+
$definition->setPublic(false);
1533+
$definition->addTag('serializer.normalizer', array('priority' => -915));
1534+
}
1535+
15281536
if (class_exists('Symfony\Component\Serializer\Normalizer\DateTimeNormalizer')) {
15291537
// Run before serializer.normalizer.object
15301538
$definition = $container->register('serializer.normalizer.datetime', DateTimeNormalizer::class);

‎src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
+16Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
3535
use Symfony\Component\PropertyAccess\PropertyAccessor;
3636
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
37+
use Symfony\Component\Serializer\Normalizer\DateIntervalNormalizer;
3738
use Symfony\Component\Serializer\Serializer;
3839
use Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory;
3940
use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader;
@@ -776,6 +777,21 @@ public function testDataUriNormalizerRegistered()
776777
$this->assertEquals(-920, $tag[0]['priority']);
777778
}
778779

780+
public function testDateIntervalNormalizerRegistered()
781+
{
782+
if (!class_exists(DateIntervalNormalizer::class)) {
783+
$this->markTestSkipped('The DateIntervalNormalizer has been introduced in the Serializer Component version 3.4.');
784+
}
785+
786+
$container = $this->createContainerFromFile('full');
787+
788+
$definition = $container->getDefinition('serializer.normalizer.dateinterval');
789+
$tag = $definition->getTag('serializer.normalizer');
790+
791+
$this->assertEquals(DateIntervalNormalizer::class, $definition->getClass());
792+
$this->assertEquals(-915, $tag[0]['priority']);
793+
}
794+
779795
public function testDateTimeNormalizerRegistered()
780796
{
781797
if (!class_exists('Symfony\Component\Serializer\Normalizer\DateTimeNormalizer')) {

‎src/Symfony/Component/Serializer/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ CHANGELOG
66

77
* added `AbstractObjectNormalizer::DISABLE_TYPE_ENFORCEMENT` context option
88
to disable throwing an `UnexpectedValueException` on a type mismatch
9+
* added support for serializing `DateInterval` objects
910

1011
3.3.0
1112
-----
+106Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Serializer\Normalizer;
13+
14+
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
15+
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
16+
17+
/**
18+
* Normalizes an instance of {@see \DateInterval} to an interval string.
19+
* Denormalizes an interval string to an instance of {@see \DateInterval}.
20+
*
21+
* @author Jérôme Parmentier <jerome@prmntr.me>
22+
*/
23+
class DateIntervalNormalizer implements NormalizerInterface, DenormalizerInterface
24+
{
25+
const FORMAT_KEY = 'dateinterval_format';
26+
27+
/**
28+
* @var string
29+
*/
30+
private $format;
31+
32+
/**
33+
* @param string $format
34+
*/
35+
public function __construct($format = 'P%yY%mM%dDT%hH%iM%sS')
36+
{
37+
$this->format = $format;
38+
}
39+
40+
/**
41+
* {@inheritdoc}
42+
*
43+
* @throws InvalidArgumentException
44+
*/
45+
public function normalize($object, $format = null, array $context = array())
46+
{
47+
if (!$object instanceof \DateInterval) {
48+
throw new InvalidArgumentException('The object must be an instance of "\DateInterval".');
49+
}
50+
51+
$dateIntervalFormat = isset($context[self::FORMAT_KEY]) ? $context[self::FORMAT_KEY] : $this->format;
52+
53+
return $object->format($dateIntervalFormat);
54+
}
55+
56+
/**
57+
* {@inheritdoc}
58+
*/
59+
public function supportsNormalization($data, $format = null)
60+
{
61+
return $data instanceof \DateInterval;
62+
}
63+
64+
/**
65+
* {@inheritdoc}
66+
*
67+
* @throws InvalidArgumentException
68+
* @throws UnexpectedValueException
69+
*/
70+
public function denormalize($data, $class, $format = null, array $context = array())
71+
{
72+
if (!is_string($data)) {
73+
throw new InvalidArgumentException(sprintf('Data expected to be a string, %s given.', gettype($data)));
74+
}
75+
76+
if (!$this->isISO8601($data)) {
77+
throw new UnexpectedValueException('Expected a valid ISO 8601 interval string.');
78+
}
79+
80+
$dateIntervalFormat = isset($context[self::FORMAT_KEY]) ? $context[self::FORMAT_KEY] : $this->format;
81+
82+
$valuePattern = '/^'.preg_replace('/%([yYmMdDhHiIsSwW])(\w)/', '(?P<$1>\d+)$2', $dateIntervalFormat).'$/';
83+
if (!preg_match($valuePattern, $data)) {
84+
throw new UnexpectedValueException(sprintf('Value "%s" contains intervals not accepted by format "%s".', $data, $dateIntervalFormat));
85+
}
86+
87+
try {
88+
return new \DateInterval($data);
89+
} catch (\Exception $e) {
90+
throw new UnexpectedValueException($e->getMessage(), $e->getCode(), $e);
91+
}
92+
}
93+
94+
/**
95+
* {@inheritdoc}
96+
*/
97+
public function supportsDenormalization($data, $type, $format = null)
98+
{
99+
return \DateInterval::class === $type;
100+
}
101+
102+
private function isISO8601($string)
103+
{
104+
return preg_match('/^P(?=\w*(?:\d|%\w))(?:\d+Y|%[yY]Y)?(?:\d+M|%[mM]M)?(?:(?:\d+D|%[dD]D)|(?:\d+W|%[wW]W))?(?:T(?:\d+H|[hH]H)?(?:\d+M|[iI]M)?(?:\d+S|[sS]S)?)?$/', $string);
105+
}
106+
}
+137Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
<?php
2+
3+
namespace Symfony\Component\Serializer\Tests\Normalizer;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use Symfony\Component\Serializer\Normalizer\DateIntervalNormalizer;
7+
8+
/**
9+
* @author Jérôme Parmentier <jerome@prmntr.me>
10+
*/
11+
class DateIntervalNormalizerTest extends TestCase
12+
{
13+
/**
14+
* @var DateIntervalNormalizer
15+
*/
16+
private $normalizer;
17+
18+
protected function setUp()
19+
{
20+
$this->normalizer = new DateIntervalNormalizer();
21+
}
22+
23+
public function dataProviderISO()
24+
{
25+
$data = array(
26+
array('P%YY%MM%DDT%HH%IM%SS', 'P00Y00M00DT00H00M00S', 'PT0S'),
27+
array('P%yY%mM%dDT%hH%iM%sS', 'P0Y0M0DT0H0M0S', 'PT0S'),
28+
array('P%yY%mM%dDT%hH%iM%sS', 'P10Y2M3DT16H5M6S', 'P10Y2M3DT16H5M6S'),
29+
array('P%yY%mM%dDT%hH%iM', 'P10Y2M3DT16H5M', 'P10Y2M3DT16H5M'),
30+
array('P%yY%mM%dDT%hH', 'P10Y2M3DT16H', 'P10Y2M3DT16H'),
31+
array('P%yY%mM%dD', 'P10Y2M3D', 'P10Y2M3DT0H'),
32+
);
33+
34+
return $data;
35+
}
36+
37+
public function testSupportsNormalization()
38+
{
39+
$this->assertTrue($this->normalizer->supportsNormalization(new \DateInterval('P00Y00M00DT00H00M00S')));
40+
$this->assertFalse($this->normalizer->supportsNormalization(new \stdClass()));
41+
}
42+
43+
public function testNormalize()
44+
{
45+
$this->assertEquals('P0Y0M0DT0H0M0S', $this->normalizer->normalize(new \DateInterval('PT0S')));
46+
}
47+
48+
/**
49+
* @dataProvider dataProviderISO
50+
*/
51+
public function testNormalizeUsingFormatPassedInContext($format, $output, $input)
52+
{
53+
$this->assertEquals($output, $this->normalizer->normalize(new \DateInterval($input), null, array(DateIntervalNormalizer::FORMAT_KEY => $format)));
54+
}
55+
56+
/**
57+
* @dataProvider dataProviderISO
58+
*/
59+
public function testNormalizeUsingFormatPassedInConstructor($format, $output, $input)
60+
{
61+
$this->assertEquals($output, (new DateIntervalNormalizer($format))->normalize(new \DateInterval($input)));
62+
}
63+
64+
/**
65+
* @expectedException \Symfony\Component\Serializer\Exception\InvalidArgumentException
66+
* @expectedExceptionMessage The object must be an instance of "\DateInterval".
67+
*/
68+
public function testNormalizeInvalidObjectThrowsException()
69+
{
70+
$this->normalizer->normalize(new \stdClass());
71+
}
72+
73+
public function testSupportsDenormalization()
74+
{
75+
$this->assertTrue($this->normalizer->supportsDenormalization('P00Y00M00DT00H00M00S', \DateInterval::class));
76+
$this->assertFalse($this->normalizer->supportsDenormalization('foo', 'Bar'));
77+
}
78+
79+
public function testDenormalize()
80+
{
81+
$this->assertDateIntervalEquals(new \DateInterval('P00Y00M00DT00H00M00S'), $this->normalizer->denormalize('P00Y00M00DT00H00M00S', \DateInterval::class));
82+
}
83+
84+
/**
85+
* @dataProvider dataProviderISO
86+
*/
87+
public function testDenormalizeUsingFormatPassedInContext($format, $input, $output)
88+
{
89+
$this->assertDateIntervalEquals(new \DateInterval($output), $this->normalizer->denormalize($input, \DateInterval::class, null, array(DateIntervalNormalizer::FORMAT_KEY => $format)));
90+
}
91+
92+
/**
93+
* @dataProvider dataProviderISO
94+
*/
95+
public function testDenormalizeUsingFormatPassedInConstructor($format, $input, $output)
96+
{
97+
$this->assertDateIntervalEquals(new \DateInterval($output), (new DateIntervalNormalizer($format))->denormalize($input, \DateInterval::class));
98+
}
99+
100+
/**
101+
* @expectedException \Symfony\Component\Serializer\Exception\InvalidArgumentException
102+
*/
103+
public function testDenormalizeExpectsString()
104+
{
105+
$this->normalizer->denormalize(1234, \DateInterval::class);
106+
}
107+
108+
/**
109+
* @expectedException \Symfony\Component\Serializer\Exception\UnexpectedValueException
110+
* @expectedExceptionMessage Expected a valid ISO 8601 interval string.
111+
*/
112+
public function testDenormalizeNonISO8601IntervalStringThrowsException()
113+
{
114+
$this->normalizer->denormalize('10 years 2 months 3 days', \DateInterval::class, null);
115+
}
116+
117+
/**
118+
* @expectedException \Symfony\Component\Serializer\Exception\UnexpectedValueException
119+
*/
120+
public function testDenormalizeInvalidDataThrowsException()
121+
{
122+
$this->normalizer->denormalize('invalid interval', \DateInterval::class);
123+
}
124+
125+
/**
126+
* @expectedException \Symfony\Component\Serializer\Exception\UnexpectedValueException
127+
*/
128+
public function testDenormalizeFormatMismatchThrowsException()
129+
{
130+
$this->normalizer->denormalize('P00Y00M00DT00H00M00S', \DateInterval::class, null, array(DateIntervalNormalizer::FORMAT_KEY => 'P%yY%mM%dD'));
131+
}
132+
133+
private function assertDateIntervalEquals(\DateInterval $expected, \DateInterval $actual)
134+
{
135+
$this->assertEquals($expected->format('%RP%yY%mM%dDT%hH%iM%sS'), $actual->format('%RP%yY%mM%dDT%hH%iM%sS'));
136+
}
137+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.