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

Open in your IDE?
  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.      * {@inheritdoc}
  43.      *
  44.      * @throws InvalidArgumentException
  45.      */
  46.     public function normalize(mixed $objectstring $format null, array $context = []): string
  47.     {
  48.         if (!$object instanceof \DateTimeInterface) {
  49.             throw new InvalidArgumentException('The object must implement the "\DateTimeInterface".');
  50.         }
  51.         $dateTimeFormat $context[self::FORMAT_KEY] ?? $this->defaultContext[self::FORMAT_KEY];
  52.         $timezone $this->getTimezone($context);
  53.         if (null !== $timezone) {
  54.             $object = clone $object;
  55.             $object $object->setTimezone($timezone);
  56.         }
  57.         return $object->format($dateTimeFormat);
  58.     }
  59.     /**
  60.      * {@inheritdoc}
  61.      *
  62.      * @param array $context
  63.      */
  64.     public function supportsNormalization(mixed $datastring $format null /* , array $context = [] */): bool
  65.     {
  66.         return $data instanceof \DateTimeInterface;
  67.     }
  68.     /**
  69.      * {@inheritdoc}
  70.      *
  71.      * @throws NotNormalizableValueException
  72.      */
  73.     public function denormalize(mixed $datastring $typestring $format null, array $context = []): \DateTimeInterface
  74.     {
  75.         $dateTimeFormat $context[self::FORMAT_KEY] ?? null;
  76.         $timezone $this->getTimezone($context);
  77.         if (null === $data || (\is_string($data) && '' === trim($data))) {
  78.             throw NotNormalizableValueException::createForUnexpectedDataType('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.'$data, [Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? nulltrue);
  79.         }
  80.         if (null !== $dateTimeFormat) {
  81.             $object \DateTime::class === $type \DateTime::createFromFormat($dateTimeFormat$data$timezone) : \DateTimeImmutable::createFromFormat($dateTimeFormat$data$timezone);
  82.             if (false !== $object) {
  83.                 return $object;
  84.             }
  85.             $dateTimeErrors \DateTime::class === $type \DateTime::getLastErrors() : \DateTimeImmutable::getLastErrors();
  86.             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);
  87.         }
  88.         $defaultDateTimeFormat $this->defaultContext[self::FORMAT_KEY] ?? null;
  89.         if (null !== $defaultDateTimeFormat) {
  90.             $object \DateTime::class === $type \DateTime::createFromFormat($defaultDateTimeFormat$data$timezone) : \DateTimeImmutable::createFromFormat($defaultDateTimeFormat$data$timezone);
  91.             if (false !== $object) {
  92.                 return $object;
  93.             }
  94.         }
  95.         try {
  96.             return \DateTime::class === $type ? new \DateTime($data$timezone) : new \DateTimeImmutable($data$timezone);
  97.         } catch (\Exception $e) {
  98.             throw NotNormalizableValueException::createForUnexpectedDataType($e->getMessage(), $data, [Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? nullfalse$e->getCode(), $e);
  99.         }
  100.     }
  101.     /**
  102.      * {@inheritdoc}
  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.     /**
  111.      * {@inheritdoc}
  112.      */
  113.     public function hasCacheableSupportsMethod(): bool
  114.     {
  115.         return __CLASS__ === static::class;
  116.     }
  117.     /**
  118.      * Formats datetime errors.
  119.      *
  120.      * @return string[]
  121.      */
  122.     private function formatDateTimeErrors(array $errors): array
  123.     {
  124.         $formattedErrors = [];
  125.         foreach ($errors as $pos => $message) {
  126.             $formattedErrors[] = sprintf('at position %d: %s'$pos$message);
  127.         }
  128.         return $formattedErrors;
  129.     }
  130.     private function getTimezone(array $context): ?\DateTimeZone
  131.     {
  132.         $dateTimeZone $context[self::TIMEZONE_KEY] ?? $this->defaultContext[self::TIMEZONE_KEY];
  133.         if (null === $dateTimeZone) {
  134.             return null;
  135.         }
  136.         return $dateTimeZone instanceof \DateTimeZone $dateTimeZone : new \DateTimeZone($dateTimeZone);
  137.     }
  138. }