[Serializer][FrameworkBundle] Add a DateInterval normalizer
This commit is contained in:
parent
30e3b6d27e
commit
6185cb1991
@ -67,6 +67,7 @@ use Symfony\Component\Serializer\Encoder\EncoderInterface;
|
||||
use Symfony\Component\Serializer\Encoder\YamlEncoder;
|
||||
use Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory;
|
||||
use Symfony\Component\Serializer\Normalizer\DataUriNormalizer;
|
||||
use Symfony\Component\Serializer\Normalizer\DateIntervalNormalizer;
|
||||
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
|
||||
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer;
|
||||
@ -1522,6 +1523,13 @@ class FrameworkExtension extends Extension
|
||||
$definition->addTag('serializer.normalizer', array('priority' => -920));
|
||||
}
|
||||
|
||||
if (class_exists(DateIntervalNormalizer::class)) {
|
||||
// Run before serializer.normalizer.object
|
||||
$definition = $container->register('serializer.normalizer.dateinterval', DateIntervalNormalizer::class);
|
||||
$definition->setPublic(false);
|
||||
$definition->addTag('serializer.normalizer', array('priority' => -915));
|
||||
}
|
||||
|
||||
if (class_exists('Symfony\Component\Serializer\Normalizer\DateTimeNormalizer')) {
|
||||
// Run before serializer.normalizer.object
|
||||
$definition = $container->register('serializer.normalizer.datetime', DateTimeNormalizer::class);
|
||||
|
@ -34,6 +34,7 @@ use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\PropertyAccess\PropertyAccessor;
|
||||
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
|
||||
use Symfony\Component\Serializer\Normalizer\DateIntervalNormalizer;
|
||||
use Symfony\Component\Serializer\Serializer;
|
||||
use Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory;
|
||||
use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader;
|
||||
@ -776,6 +777,21 @@ abstract class FrameworkExtensionTest extends TestCase
|
||||
$this->assertEquals(-920, $tag[0]['priority']);
|
||||
}
|
||||
|
||||
public function testDateIntervalNormalizerRegistered()
|
||||
{
|
||||
if (!class_exists(DateIntervalNormalizer::class)) {
|
||||
$this->markTestSkipped('The DateIntervalNormalizer has been introduced in the Serializer Component version 3.4.');
|
||||
}
|
||||
|
||||
$container = $this->createContainerFromFile('full');
|
||||
|
||||
$definition = $container->getDefinition('serializer.normalizer.dateinterval');
|
||||
$tag = $definition->getTag('serializer.normalizer');
|
||||
|
||||
$this->assertEquals(DateIntervalNormalizer::class, $definition->getClass());
|
||||
$this->assertEquals(-915, $tag[0]['priority']);
|
||||
}
|
||||
|
||||
public function testDateTimeNormalizerRegistered()
|
||||
{
|
||||
if (!class_exists('Symfony\Component\Serializer\Normalizer\DateTimeNormalizer')) {
|
||||
|
@ -6,6 +6,7 @@ CHANGELOG
|
||||
|
||||
* added `AbstractObjectNormalizer::DISABLE_TYPE_ENFORCEMENT` context option
|
||||
to disable throwing an `UnexpectedValueException` on a type mismatch
|
||||
* added support for serializing `DateInterval` objects
|
||||
|
||||
3.3.0
|
||||
-----
|
||||
|
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Serializer\Normalizer;
|
||||
|
||||
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
|
||||
|
||||
/**
|
||||
* Normalizes an instance of {@see \DateInterval} to an interval string.
|
||||
* Denormalizes an interval string to an instance of {@see \DateInterval}.
|
||||
*
|
||||
* @author Jérôme Parmentier <jerome@prmntr.me>
|
||||
*/
|
||||
class DateIntervalNormalizer implements NormalizerInterface, DenormalizerInterface
|
||||
{
|
||||
const FORMAT_KEY = 'dateinterval_format';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $format;
|
||||
|
||||
/**
|
||||
* @param string $format
|
||||
*/
|
||||
public function __construct($format = 'P%yY%mM%dDT%hH%iM%sS')
|
||||
{
|
||||
$this->format = $format;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function normalize($object, $format = null, array $context = array())
|
||||
{
|
||||
if (!$object instanceof \DateInterval) {
|
||||
throw new InvalidArgumentException('The object must be an instance of "\DateInterval".');
|
||||
}
|
||||
|
||||
$dateIntervalFormat = isset($context[self::FORMAT_KEY]) ? $context[self::FORMAT_KEY] : $this->format;
|
||||
|
||||
return $object->format($dateIntervalFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function supportsNormalization($data, $format = null)
|
||||
{
|
||||
return $data instanceof \DateInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
* @throws UnexpectedValueException
|
||||
*/
|
||||
public function denormalize($data, $class, $format = null, array $context = array())
|
||||
{
|
||||
if (!is_string($data)) {
|
||||
throw new InvalidArgumentException(sprintf('Data expected to be a string, %s given.', gettype($data)));
|
||||
}
|
||||
|
||||
if (!$this->isISO8601($data)) {
|
||||
throw new UnexpectedValueException('Expected a valid ISO 8601 interval string.');
|
||||
}
|
||||
|
||||
$dateIntervalFormat = isset($context[self::FORMAT_KEY]) ? $context[self::FORMAT_KEY] : $this->format;
|
||||
|
||||
$valuePattern = '/^'.preg_replace('/%([yYmMdDhHiIsSwW])(\w)/', '(?P<$1>\d+)$2', $dateIntervalFormat).'$/';
|
||||
if (!preg_match($valuePattern, $data)) {
|
||||
throw new UnexpectedValueException(sprintf('Value "%s" contains intervals not accepted by format "%s".', $data, $dateIntervalFormat));
|
||||
}
|
||||
|
||||
try {
|
||||
return new \DateInterval($data);
|
||||
} catch (\Exception $e) {
|
||||
throw new UnexpectedValueException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function supportsDenormalization($data, $type, $format = null)
|
||||
{
|
||||
return \DateInterval::class === $type;
|
||||
}
|
||||
|
||||
private function isISO8601($string)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\Serializer\Tests\Normalizer;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Serializer\Normalizer\DateIntervalNormalizer;
|
||||
|
||||
/**
|
||||
* @author Jérôme Parmentier <jerome@prmntr.me>
|
||||
*/
|
||||
class DateIntervalNormalizerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var DateIntervalNormalizer
|
||||
*/
|
||||
private $normalizer;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->normalizer = new DateIntervalNormalizer();
|
||||
}
|
||||
|
||||
public function dataProviderISO()
|
||||
{
|
||||
$data = array(
|
||||
array('P%YY%MM%DDT%HH%IM%SS', 'P00Y00M00DT00H00M00S', 'PT0S'),
|
||||
array('P%yY%mM%dDT%hH%iM%sS', 'P0Y0M0DT0H0M0S', 'PT0S'),
|
||||
array('P%yY%mM%dDT%hH%iM%sS', 'P10Y2M3DT16H5M6S', 'P10Y2M3DT16H5M6S'),
|
||||
array('P%yY%mM%dDT%hH%iM', 'P10Y2M3DT16H5M', 'P10Y2M3DT16H5M'),
|
||||
array('P%yY%mM%dDT%hH', 'P10Y2M3DT16H', 'P10Y2M3DT16H'),
|
||||
array('P%yY%mM%dD', 'P10Y2M3D', 'P10Y2M3DT0H'),
|
||||
);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function testSupportsNormalization()
|
||||
{
|
||||
$this->assertTrue($this->normalizer->supportsNormalization(new \DateInterval('P00Y00M00DT00H00M00S')));
|
||||
$this->assertFalse($this->normalizer->supportsNormalization(new \stdClass()));
|
||||
}
|
||||
|
||||
public function testNormalize()
|
||||
{
|
||||
$this->assertEquals('P0Y0M0DT0H0M0S', $this->normalizer->normalize(new \DateInterval('PT0S')));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataProviderISO
|
||||
*/
|
||||
public function testNormalizeUsingFormatPassedInContext($format, $output, $input)
|
||||
{
|
||||
$this->assertEquals($output, $this->normalizer->normalize(new \DateInterval($input), null, array(DateIntervalNormalizer::FORMAT_KEY => $format)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataProviderISO
|
||||
*/
|
||||
public function testNormalizeUsingFormatPassedInConstructor($format, $output, $input)
|
||||
{
|
||||
$this->assertEquals($output, (new DateIntervalNormalizer($format))->normalize(new \DateInterval($input)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Serializer\Exception\InvalidArgumentException
|
||||
* @expectedExceptionMessage The object must be an instance of "\DateInterval".
|
||||
*/
|
||||
public function testNormalizeInvalidObjectThrowsException()
|
||||
{
|
||||
$this->normalizer->normalize(new \stdClass());
|
||||
}
|
||||
|
||||
public function testSupportsDenormalization()
|
||||
{
|
||||
$this->assertTrue($this->normalizer->supportsDenormalization('P00Y00M00DT00H00M00S', \DateInterval::class));
|
||||
$this->assertFalse($this->normalizer->supportsDenormalization('foo', 'Bar'));
|
||||
}
|
||||
|
||||
public function testDenormalize()
|
||||
{
|
||||
$this->assertDateIntervalEquals(new \DateInterval('P00Y00M00DT00H00M00S'), $this->normalizer->denormalize('P00Y00M00DT00H00M00S', \DateInterval::class));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataProviderISO
|
||||
*/
|
||||
public function testDenormalizeUsingFormatPassedInContext($format, $input, $output)
|
||||
{
|
||||
$this->assertDateIntervalEquals(new \DateInterval($output), $this->normalizer->denormalize($input, \DateInterval::class, null, array(DateIntervalNormalizer::FORMAT_KEY => $format)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataProviderISO
|
||||
*/
|
||||
public function testDenormalizeUsingFormatPassedInConstructor($format, $input, $output)
|
||||
{
|
||||
$this->assertDateIntervalEquals(new \DateInterval($output), (new DateIntervalNormalizer($format))->denormalize($input, \DateInterval::class));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Serializer\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function testDenormalizeExpectsString()
|
||||
{
|
||||
$this->normalizer->denormalize(1234, \DateInterval::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Serializer\Exception\UnexpectedValueException
|
||||
* @expectedExceptionMessage Expected a valid ISO 8601 interval string.
|
||||
*/
|
||||
public function testDenormalizeNonISO8601IntervalStringThrowsException()
|
||||
{
|
||||
$this->normalizer->denormalize('10 years 2 months 3 days', \DateInterval::class, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Serializer\Exception\UnexpectedValueException
|
||||
*/
|
||||
public function testDenormalizeInvalidDataThrowsException()
|
||||
{
|
||||
$this->normalizer->denormalize('invalid interval', \DateInterval::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Serializer\Exception\UnexpectedValueException
|
||||
*/
|
||||
public function testDenormalizeFormatMismatchThrowsException()
|
||||
{
|
||||
$this->normalizer->denormalize('P00Y00M00DT00H00M00S', \DateInterval::class, null, array(DateIntervalNormalizer::FORMAT_KEY => 'P%yY%mM%dD'));
|
||||
}
|
||||
|
||||
private function assertDateIntervalEquals(\DateInterval $expected, \DateInterval $actual)
|
||||
{
|
||||
$this->assertEquals($expected->format('%RP%yY%mM%dDT%hH%iM%sS'), $actual->format('%RP%yY%mM%dDT%hH%iM%sS'));
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user