DateTimeNormalizer.php 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Serializer\Normalizer;
  11. use Symfony\Component\Serializer\Exception\InvalidArgumentException;
  12. use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
  13. /**
  14. * Normalizes an object implementing the {@see \DateTimeInterface} to a date string.
  15. * Denormalizes a date string to an instance of {@see \DateTime} or {@see \DateTimeImmutable}.
  16. *
  17. * @author Kévin Dunglas <dunglas@gmail.com>
  18. */
  19. class DateTimeNormalizer implements NormalizerInterface, DenormalizerInterface, CacheableSupportsMethodInterface
  20. {
  21. public const FORMAT_KEY = 'datetime_format';
  22. public const TIMEZONE_KEY = 'datetime_timezone';
  23. private $defaultContext = [
  24. self::FORMAT_KEY => \DateTime::RFC3339,
  25. self::TIMEZONE_KEY => null,
  26. ];
  27. private const SUPPORTED_TYPES = [
  28. \DateTimeInterface::class => true,
  29. \DateTimeImmutable::class => true,
  30. \DateTime::class => true,
  31. ];
  32. public function __construct(array $defaultContext = [])
  33. {
  34. $this->defaultContext = array_merge($this->defaultContext, $defaultContext);
  35. }
  36. /**
  37. * {@inheritdoc}
  38. *
  39. * @throws InvalidArgumentException
  40. *
  41. * @return string
  42. */
  43. public function normalize($object, string $format = null, array $context = [])
  44. {
  45. if (!$object instanceof \DateTimeInterface) {
  46. throw new InvalidArgumentException('The object must implement the "\DateTimeInterface".');
  47. }
  48. $dateTimeFormat = $context[self::FORMAT_KEY] ?? $this->defaultContext[self::FORMAT_KEY];
  49. $timezone = $this->getTimezone($context);
  50. if (null !== $timezone) {
  51. $object = clone $object;
  52. $object = $object->setTimezone($timezone);
  53. }
  54. return $object->format($dateTimeFormat);
  55. }
  56. /**
  57. * {@inheritdoc}
  58. */
  59. public function supportsNormalization($data, string $format = null)
  60. {
  61. return $data instanceof \DateTimeInterface;
  62. }
  63. /**
  64. * {@inheritdoc}
  65. *
  66. * @throws NotNormalizableValueException
  67. *
  68. * @return \DateTimeInterface
  69. */
  70. public function denormalize($data, string $type, string $format = null, array $context = [])
  71. {
  72. $dateTimeFormat = $context[self::FORMAT_KEY] ?? null;
  73. $timezone = $this->getTimezone($context);
  74. if ('' === $data || null === $data) {
  75. 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.');
  76. }
  77. if (null !== $dateTimeFormat) {
  78. $object = \DateTime::class === $type ? \DateTime::createFromFormat($dateTimeFormat, $data, $timezone) : \DateTimeImmutable::createFromFormat($dateTimeFormat, $data, $timezone);
  79. if (false !== $object) {
  80. return $object;
  81. }
  82. $dateTimeErrors = \DateTime::class === $type ? \DateTime::getLastErrors() : \DateTimeImmutable::getLastErrors();
  83. 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'])));
  84. }
  85. try {
  86. return \DateTime::class === $type ? new \DateTime($data, $timezone) : new \DateTimeImmutable($data, $timezone);
  87. } catch (\Exception $e) {
  88. throw new NotNormalizableValueException($e->getMessage(), $e->getCode(), $e);
  89. }
  90. }
  91. /**
  92. * {@inheritdoc}
  93. */
  94. public function supportsDenormalization($data, string $type, string $format = null)
  95. {
  96. return isset(self::SUPPORTED_TYPES[$type]);
  97. }
  98. /**
  99. * {@inheritdoc}
  100. */
  101. public function hasCacheableSupportsMethod(): bool
  102. {
  103. return __CLASS__ === static::class;
  104. }
  105. /**
  106. * Formats datetime errors.
  107. *
  108. * @return string[]
  109. */
  110. private function formatDateTimeErrors(array $errors): array
  111. {
  112. $formattedErrors = [];
  113. foreach ($errors as $pos => $message) {
  114. $formattedErrors[] = sprintf('at position %d: %s', $pos, $message);
  115. }
  116. return $formattedErrors;
  117. }
  118. private function getTimezone(array $context): ?\DateTimeZone
  119. {
  120. $dateTimeZone = $context[self::TIMEZONE_KEY] ?? $this->defaultContext[self::TIMEZONE_KEY];
  121. if (null === $dateTimeZone) {
  122. return null;
  123. }
  124. return $dateTimeZone instanceof \DateTimeZone ? $dateTimeZone : new \DateTimeZone($dateTimeZone);
  125. }
  126. }