vendor/symfony/serializer/Serializer.php line 230

  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;
  11. use Symfony\Component\Serializer\Encoder\ChainDecoder;
  12. use Symfony\Component\Serializer\Encoder\ChainEncoder;
  13. use Symfony\Component\Serializer\Encoder\ContextAwareDecoderInterface;
  14. use Symfony\Component\Serializer\Encoder\ContextAwareEncoderInterface;
  15. use Symfony\Component\Serializer\Encoder\DecoderInterface;
  16. use Symfony\Component\Serializer\Encoder\EncoderInterface;
  17. use Symfony\Component\Serializer\Exception\InvalidArgumentException;
  18. use Symfony\Component\Serializer\Exception\LogicException;
  19. use Symfony\Component\Serializer\Exception\NotEncodableValueException;
  20. use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
  21. use Symfony\Component\Serializer\Exception\PartialDenormalizationException;
  22. use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
  23. use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface;
  24. use Symfony\Component\Serializer\Normalizer\ContextAwareDenormalizerInterface;
  25. use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
  26. use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
  27. use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
  28. use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
  29. use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
  30. /**
  31.  * Serializer serializes and deserializes data.
  32.  *
  33.  * objects are turned into arrays by normalizers.
  34.  * arrays are turned into various output formats by encoders.
  35.  *
  36.  *     $serializer->serialize($obj, 'xml')
  37.  *     $serializer->decode($data, 'xml')
  38.  *     $serializer->denormalize($data, 'Class', 'xml')
  39.  *
  40.  * @author Jordi Boggiano <j.boggiano@seld.be>
  41.  * @author Johannes M. Schmitt <schmittjoh@gmail.com>
  42.  * @author Lukas Kahwe Smith <smith@pooteeweet.org>
  43.  * @author Kévin Dunglas <dunglas@gmail.com>
  44.  */
  45. class Serializer implements SerializerInterfaceContextAwareNormalizerInterfaceContextAwareDenormalizerInterfaceContextAwareEncoderInterfaceContextAwareDecoderInterface
  46. {
  47.     /**
  48.      * Flag to control whether an empty array should be transformed to an
  49.      * object (in JSON: {}) or to a list (in JSON: []).
  50.      */
  51.     public const EMPTY_ARRAY_AS_OBJECT 'empty_array_as_object';
  52.     private const SCALAR_TYPES = [
  53.         'int' => true,
  54.         'bool' => true,
  55.         'float' => true,
  56.         'string' => true,
  57.     ];
  58.     /**
  59.      * @var Encoder\ChainEncoder
  60.      */
  61.     protected $encoder;
  62.     /**
  63.      * @var Encoder\ChainDecoder
  64.      */
  65.     protected $decoder;
  66.     private $normalizers = [];
  67.     private $denormalizerCache = [];
  68.     private $normalizerCache = [];
  69.     /**
  70.      * @param array<NormalizerInterface|DenormalizerInterface> $normalizers
  71.      * @param array<EncoderInterface|DecoderInterface>         $encoders
  72.      */
  73.     public function __construct(array $normalizers = [], array $encoders = [])
  74.     {
  75.         foreach ($normalizers as $normalizer) {
  76.             if ($normalizer instanceof SerializerAwareInterface) {
  77.                 $normalizer->setSerializer($this);
  78.             }
  79.             if ($normalizer instanceof DenormalizerAwareInterface) {
  80.                 $normalizer->setDenormalizer($this);
  81.             }
  82.             if ($normalizer instanceof NormalizerAwareInterface) {
  83.                 $normalizer->setNormalizer($this);
  84.             }
  85.             if (!($normalizer instanceof NormalizerInterface || $normalizer instanceof DenormalizerInterface)) {
  86.                 throw new InvalidArgumentException(sprintf('The class "%s" neither implements "%s" nor "%s".'get_debug_type($normalizer), NormalizerInterface::class, DenormalizerInterface::class));
  87.             }
  88.         }
  89.         $this->normalizers $normalizers;
  90.         $decoders = [];
  91.         $realEncoders = [];
  92.         foreach ($encoders as $encoder) {
  93.             if ($encoder instanceof SerializerAwareInterface) {
  94.                 $encoder->setSerializer($this);
  95.             }
  96.             if ($encoder instanceof DecoderInterface) {
  97.                 $decoders[] = $encoder;
  98.             }
  99.             if ($encoder instanceof EncoderInterface) {
  100.                 $realEncoders[] = $encoder;
  101.             }
  102.             if (!($encoder instanceof EncoderInterface || $encoder instanceof DecoderInterface)) {
  103.                 throw new InvalidArgumentException(sprintf('The class "%s" neither implements "%s" nor "%s".'get_debug_type($encoder), EncoderInterface::class, DecoderInterface::class));
  104.             }
  105.         }
  106.         $this->encoder = new ChainEncoder($realEncoders);
  107.         $this->decoder = new ChainDecoder($decoders);
  108.     }
  109.     final public function serialize(mixed $datastring $format, array $context = []): string
  110.     {
  111.         if (!$this->supportsEncoding($format$context)) {
  112.             throw new NotEncodableValueException(sprintf('Serialization for the format "%s" is not supported.'$format));
  113.         }
  114.         if ($this->encoder->needsNormalization($format$context)) {
  115.             $data $this->normalize($data$format$context);
  116.         }
  117.         return $this->encode($data$format$context);
  118.     }
  119.     final public function deserialize(mixed $datastring $typestring $format, array $context = []): mixed
  120.     {
  121.         if (!$this->supportsDecoding($format$context)) {
  122.             throw new NotEncodableValueException(sprintf('Deserialization for the format "%s" is not supported.'$format));
  123.         }
  124.         $data $this->decode($data$format$context);
  125.         return $this->denormalize($data$type$format$context);
  126.     }
  127.     public function normalize(mixed $datastring $format null, array $context = []): array|string|int|float|bool|\ArrayObject|null
  128.     {
  129.         // If a normalizer supports the given data, use it
  130.         if ($normalizer $this->getNormalizer($data$format$context)) {
  131.             return $normalizer->normalize($data$format$context);
  132.         }
  133.         if (null === $data || \is_scalar($data)) {
  134.             return $data;
  135.         }
  136.         if (\is_array($data) && !$data && ($context[self::EMPTY_ARRAY_AS_OBJECT] ?? false)) {
  137.             return new \ArrayObject();
  138.         }
  139.         if (is_iterable($data)) {
  140.             if ($data instanceof \Countable && ($context[AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS] ?? false) && !\count($data)) {
  141.                 return new \ArrayObject();
  142.             }
  143.             $normalized = [];
  144.             foreach ($data as $key => $val) {
  145.                 $normalized[$key] = $this->normalize($val$format$context);
  146.             }
  147.             return $normalized;
  148.         }
  149.         if (\is_object($data)) {
  150.             if (!$this->normalizers) {
  151.                 throw new LogicException('You must register at least one normalizer to be able to normalize objects.');
  152.             }
  153.             throw new NotNormalizableValueException(sprintf('Could not normalize object of type "%s", no supporting normalizer found.'get_debug_type($data)));
  154.         }
  155.         throw new NotNormalizableValueException('An unexpected value could not be normalized: '.(!\is_resource($data) ? var_export($datatrue) : sprintf('"%s" resource'get_resource_type($data))));
  156.     }
  157.     /**
  158.      * @throws NotNormalizableValueException
  159.      */
  160.     public function denormalize(mixed $datastring $typestring $format null, array $context = []): mixed
  161.     {
  162.         if (isset($context[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS], $context['not_normalizable_value_exceptions'])) {
  163.             throw new LogicException('Passing a value for "not_normalizable_value_exceptions" context key is not allowed.');
  164.         }
  165.         $normalizer $this->getDenormalizer($data$type$format$context);
  166.         // Check for a denormalizer first, e.g. the data is wrapped
  167.         if (!$normalizer && isset(self::SCALAR_TYPES[$type])) {
  168.             if (!('is_'.$type)($data)) {
  169.                 throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Data expected to be of type "%s" ("%s" given).'$typeget_debug_type($data)), $data, [$type], $context['deserialization_path'] ?? nulltrue);
  170.             }
  171.             return $data;
  172.         }
  173.         if (!$this->normalizers) {
  174.             throw new LogicException('You must register at least one normalizer to be able to denormalize objects.');
  175.         }
  176.         if (!$normalizer) {
  177.             throw new NotNormalizableValueException(sprintf('Could not denormalize object of type "%s", no supporting normalizer found.'$type));
  178.         }
  179.         if (isset($context[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS])) {
  180.             unset($context[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS]);
  181.             $context['not_normalizable_value_exceptions'] = [];
  182.             $errors = &$context['not_normalizable_value_exceptions'];
  183.             $denormalized $normalizer->denormalize($data$type$format$context);
  184.             if ($errors) {
  185.                 throw new PartialDenormalizationException($denormalized$errors);
  186.             }
  187.             return $denormalized;
  188.         }
  189.         return $normalizer->denormalize($data$type$format$context);
  190.     }
  191.     public function supportsNormalization(mixed $datastring $format null, array $context = []): bool
  192.     {
  193.         return null !== $this->getNormalizer($data$format$context);
  194.     }
  195.     public function supportsDenormalization(mixed $datastring $typestring $format null, array $context = []): bool
  196.     {
  197.         return isset(self::SCALAR_TYPES[$type]) || null !== $this->getDenormalizer($data$type$format$context);
  198.     }
  199.     /**
  200.      * Returns a matching normalizer.
  201.      *
  202.      * @param mixed       $data    Data to get the serializer for
  203.      * @param string|null $format  Format name, present to give the option to normalizers to act differently based on formats
  204.      * @param array       $context Options available to the normalizer
  205.      */
  206.     private function getNormalizer(mixed $data, ?string $format, array $context): ?NormalizerInterface
  207.     {
  208.         $type \is_object($data) ? $data::class : 'native-'.\gettype($data);
  209.         if (!isset($this->normalizerCache[$format][$type])) {
  210.             $this->normalizerCache[$format][$type] = [];
  211.             foreach ($this->normalizers as $k => $normalizer) {
  212.                 if (!$normalizer instanceof NormalizerInterface) {
  213.                     continue;
  214.                 }
  215.                 if (!$normalizer instanceof CacheableSupportsMethodInterface || !$normalizer->hasCacheableSupportsMethod()) {
  216.                     $this->normalizerCache[$format][$type][$k] = false;
  217.                 } elseif ($normalizer->supportsNormalization($data$format$context)) {
  218.                     $this->normalizerCache[$format][$type][$k] = true;
  219.                     break;
  220.                 }
  221.             }
  222.         }
  223.         foreach ($this->normalizerCache[$format][$type] as $k => $cached) {
  224.             $normalizer $this->normalizers[$k];
  225.             if ($cached || $normalizer->supportsNormalization($data$format$context)) {
  226.                 return $normalizer;
  227.             }
  228.         }
  229.         return null;
  230.     }
  231.     /**
  232.      * Returns a matching denormalizer.
  233.      *
  234.      * @param mixed       $data    Data to restore
  235.      * @param string      $class   The expected class to instantiate
  236.      * @param string|null $format  Format name, present to give the option to normalizers to act differently based on formats
  237.      * @param array       $context Options available to the denormalizer
  238.      */
  239.     private function getDenormalizer(mixed $datastring $class, ?string $format, array $context): ?DenormalizerInterface
  240.     {
  241.         if (!isset($this->denormalizerCache[$format][$class])) {
  242.             $this->denormalizerCache[$format][$class] = [];
  243.             foreach ($this->normalizers as $k => $normalizer) {
  244.                 if (!$normalizer instanceof DenormalizerInterface) {
  245.                     continue;
  246.                 }
  247.                 if (!$normalizer instanceof CacheableSupportsMethodInterface || !$normalizer->hasCacheableSupportsMethod()) {
  248.                     $this->denormalizerCache[$format][$class][$k] = false;
  249.                 } elseif ($normalizer->supportsDenormalization(null$class$format$context)) {
  250.                     $this->denormalizerCache[$format][$class][$k] = true;
  251.                     break;
  252.                 }
  253.             }
  254.         }
  255.         foreach ($this->denormalizerCache[$format][$class] as $k => $cached) {
  256.             $normalizer $this->normalizers[$k];
  257.             if ($cached || $normalizer->supportsDenormalization($data$class$format$context)) {
  258.                 return $normalizer;
  259.             }
  260.         }
  261.         return null;
  262.     }
  263.     final public function encode(mixed $datastring $format, array $context = []): string
  264.     {
  265.         return $this->encoder->encode($data$format$context);
  266.     }
  267.     final public function decode(string $datastring $format, array $context = []): mixed
  268.     {
  269.         return $this->decoder->decode($data$format$context);
  270.     }
  271.     public function supportsEncoding(string $format, array $context = []): bool
  272.     {
  273.         return $this->encoder->supportsEncoding($format$context);
  274.     }
  275.     public function supportsDecoding(string $format, array $context = []): bool
  276.     {
  277.         return $this->decoder->supportsDecoding($format$context);
  278.     }
  279. }