vendor/symfony/serializer/Normalizer/DateTimeNormalizer.php line 53

  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\PropertyInfo\Type;
  12. use Symfony\Component\Serializer\Exception\InvalidArgumentException;
  13. use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
  14. /**
  15.  * Normalizes an object implementing the {@see \DateTimeInterface} to a date string.
  16.  * Denormalizes a date string to an instance of {@see \DateTime} or {@see \DateTimeImmutable}.
  17.  *
  18.  * @author Kévin Dunglas <dunglas@gmail.com>
  19.  */
  20. class DateTimeNormalizer implements NormalizerInterfaceDenormalizerInterfaceCacheableSupportsMethodInterface
  21. {
  22.     public const FORMAT_KEY 'datetime_format';
  23.     public const TIMEZONE_KEY 'datetime_timezone';
  24.     private $defaultContext = [
  25.         self::FORMAT_KEY => \DateTime::RFC3339,
  26.         self::TIMEZONE_KEY => null,
  27.     ];
  28.     private const SUPPORTED_TYPES = [
  29.         \DateTimeInterface::class => true,
  30.         \DateTimeImmutable::class => true,
  31.         \DateTime::class => true,
  32.     ];
  33.     public function __construct(array $defaultContext = [])
  34.     {
  35.         $this->setDefaultContext($defaultContext);
  36.     }
  37.     public function setDefaultContext(array $defaultContext): void
  38.     {
  39.         $this->defaultContext array_merge($this->defaultContext$defaultContext);
  40.     }
  41.     /**
  42.      * @throws InvalidArgumentException
  43.      */
  44.     public function normalize(mixed $objectstring $format null, array $context = []): string
  45.     {
  46.         if (!$object instanceof \DateTimeInterface) {
  47.             throw new InvalidArgumentException('The object must implement the "\DateTimeInterface".');
  48.         }
  49.         $dateTimeFormat $context[self::FORMAT_KEY] ?? $this->defaultContext[self::FORMAT_KEY];
  50.         $timezone $this->getTimezone($context);
  51.         if (null !== $timezone) {
  52.             $object = clone $object;
  53.             $object $object->setTimezone($timezone);
  54.         }
  55.         return $object->format($dateTimeFormat);
  56.     }
  57.     /**
  58.      * @param array $context
  59.      */
  60.     public function supportsNormalization(mixed $datastring $format null /* , array $context = [] */): bool
  61.     {
  62.         return $data instanceof \DateTimeInterface;
  63.     }
  64.     /**
  65.      * @throws NotNormalizableValueException
  66.      */
  67.     public function denormalize(mixed $datastring $typestring $format null, array $context = []): \DateTimeInterface
  68.     {
  69.         $dateTimeFormat $context[self::FORMAT_KEY] ?? null;
  70.         $timezone $this->getTimezone($context);
  71.         if (\is_int($data) || \is_float($data)) {
  72.             switch ($dateTimeFormat) {
  73.                 case 'U'$data sprintf('%d'$data); break;
  74.                 case 'U.u'$data sprintf('%.6F'$data); break;
  75.             }
  76.         }
  77.         if (!\is_string($data) || '' === trim($data)) {
  78.             throw NotNormalizableValueException::createForUnexpectedDataType('The data is either not an string, an empty string, or null; you should pass a string that can be parsed with the passed format or a valid DateTime string.'$data, [Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? nulltrue);
  79.         }
  80.         try {
  81.             if (null !== $dateTimeFormat) {
  82.                 $object \DateTime::class === $type \DateTime::createFromFormat($dateTimeFormat$data$timezone) : \DateTimeImmutable::createFromFormat($dateTimeFormat$data$timezone);
  83.                 if (false !== $object) {
  84.                     return $object;
  85.                 }
  86.                 $dateTimeErrors \DateTime::class === $type \DateTime::getLastErrors() : \DateTimeImmutable::getLastErrors();
  87.                 throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Parsing datetime string "%s" using format "%s" resulted in %d errors: '$data$dateTimeFormat$dateTimeErrors['error_count'])."\n".implode("\n"$this->formatDateTimeErrors($dateTimeErrors['errors'])), $data, [Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? nulltrue);
  88.             }
  89.             $defaultDateTimeFormat $this->defaultContext[self::FORMAT_KEY] ?? null;
  90.             if (null !== $defaultDateTimeFormat) {
  91.                 $object \DateTime::class === $type \DateTime::createFromFormat($defaultDateTimeFormat$data$timezone) : \DateTimeImmutable::createFromFormat($defaultDateTimeFormat$data$timezone);
  92.                 if (false !== $object) {
  93.                     return $object;
  94.                 }
  95.             }
  96.             return \DateTime::class === $type ? new \DateTime($data$timezone) : new \DateTimeImmutable($data$timezone);
  97.         } catch (NotNormalizableValueException $e) {
  98.             throw $e;
  99.         } catch (\Exception $e) {
  100.             throw NotNormalizableValueException::createForUnexpectedDataType($e->getMessage(), $data, [Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? nullfalse$e->getCode(), $e);
  101.         }
  102.     }
  103.     /**
  104.      * @param array $context
  105.      */
  106.     public function supportsDenormalization(mixed $datastring $typestring $format null /* , array $context = [] */): bool
  107.     {
  108.         return isset(self::SUPPORTED_TYPES[$type]);
  109.     }
  110.     public function hasCacheableSupportsMethod(): bool
  111.     {
  112.         return __CLASS__ === static::class;
  113.     }
  114.     /**
  115.      * Formats datetime errors.
  116.      *
  117.      * @return string[]
  118.      */
  119.     private function formatDateTimeErrors(array $errors): array
  120.     {
  121.         $formattedErrors = [];
  122.         foreach ($errors as $pos => $message) {
  123.             $formattedErrors[] = sprintf('at position %d: %s'$pos$message);
  124.         }
  125.         return $formattedErrors;
  126.     }
  127.     private function getTimezone(array $context): ?\DateTimeZone
  128.     {
  129.         $dateTimeZone $context[self::TIMEZONE_KEY] ?? $this->defaultContext[self::TIMEZONE_KEY];
  130.         if (null === $dateTimeZone) {
  131.             return null;
  132.         }
  133.         return $dateTimeZone instanceof \DateTimeZone $dateTimeZone : new \DateTimeZone($dateTimeZone);
  134.     }
  135. }