vendor/symfony/serializer/Serializer.php line 161

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;
  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.     /**
  110.      * {@inheritdoc}
  111.      */
  112.     final public function serialize(mixed $datastring $format, array $context = []): string
  113.     {
  114.         if (!$this->supportsEncoding($format$context)) {
  115.             throw new NotEncodableValueException(sprintf('Serialization for the format "%s" is not supported.'$format));
  116.         }
  117.         if ($this->encoder->needsNormalization($format$context)) {
  118.             $data $this->normalize($data$format$context);
  119.         }
  120.         return $this->encode($data$format$context);
  121.     }
  122.     /**
  123.      * {@inheritdoc}
  124.      */
  125.     final public function deserialize(mixed $datastring $typestring $format, array $context = []): mixed
  126.     {
  127.         if (!$this->supportsDecoding($format$context)) {
  128.             throw new NotEncodableValueException(sprintf('Deserialization for the format "%s" is not supported.'$format));
  129.         }
  130.         $data $this->decode($data$format$context);
  131.         return $this->denormalize($data$type$format$context);
  132.     }
  133.     /**
  134.      * {@inheritdoc}
  135.      */
  136.     public function normalize(mixed $datastring $format null, array $context = []): array|string|int|float|bool|\ArrayObject|null
  137.     {
  138.         // If a normalizer supports the given data, use it
  139.         if ($normalizer $this->getNormalizer($data$format$context)) {
  140.             return $normalizer->normalize($data$format$context);
  141.         }
  142.         if (null === $data || \is_scalar($data)) {
  143.             return $data;
  144.         }
  145.         if (\is_array($data) && !$data && ($context[self::EMPTY_ARRAY_AS_OBJECT] ?? false)) {
  146.             return new \ArrayObject();
  147.         }
  148.         if (is_iterable($data)) {
  149.             if ($data instanceof \Countable && ($context[AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS] ?? false) && !\count($data)) {
  150.                 return new \ArrayObject();
  151.             }
  152.             $normalized = [];
  153.             foreach ($data as $key => $val) {
  154.                 $normalized[$key] = $this->normalize($val$format$context);
  155.             }
  156.             return $normalized;
  157.         }
  158.         if (\is_object($data)) {
  159.             if (!$this->normalizers) {
  160.                 throw new LogicException('You must register at least one normalizer to be able to normalize objects.');
  161.             }
  162.             throw new NotNormalizableValueException(sprintf('Could not normalize object of type "%s", no supporting normalizer found.'get_debug_type($data)));
  163.         }
  164.         throw new NotNormalizableValueException('An unexpected value could not be normalized: '.(!\is_resource($data) ? var_export($datatrue) : sprintf('"%s" resource'get_resource_type($data))));
  165.     }
  166.     /**
  167.      * {@inheritdoc}
  168.      *
  169.      * @throws NotNormalizableValueException
  170.      */
  171.     public function denormalize(mixed $datastring $typestring $format null, array $context = []): mixed
  172.     {
  173.         if (isset($context[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS], $context['not_normalizable_value_exceptions'])) {
  174.             throw new LogicException('Passing a value for "not_normalizable_value_exceptions" context key is not allowed.');
  175.         }
  176.         $normalizer $this->getDenormalizer($data$type$format$context);
  177.         // Check for a denormalizer first, e.g. the data is wrapped
  178.         if (!$normalizer && isset(self::SCALAR_TYPES[$type])) {
  179.             if (!('is_'.$type)($data)) {
  180.                 throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Data expected to be of type "%s" ("%s" given).'$typeget_debug_type($data)), $data, [$type], $context['deserialization_path'] ?? nulltrue);
  181.             }
  182.             return $data;
  183.         }
  184.         if (!$this->normalizers) {
  185.             throw new LogicException('You must register at least one normalizer to be able to denormalize objects.');
  186.         }
  187.         if (!$normalizer) {
  188.             throw new NotNormalizableValueException(sprintf('Could not denormalize object of type "%s", no supporting normalizer found.'$type));
  189.         }
  190.         if (isset($context[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS])) {
  191.             unset($context[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS]);
  192.             $context['not_normalizable_value_exceptions'] = [];
  193.             $errors = &$context['not_normalizable_value_exceptions'];
  194.             $denormalized $normalizer->denormalize($data$type$format$context);
  195.             if ($errors) {
  196.                 throw new PartialDenormalizationException($denormalized$errors);
  197.             }
  198.             return $denormalized;
  199.         }
  200.         return $normalizer->denormalize($data$type$format$context);
  201.     }
  202.     /**
  203.      * {@inheritdoc}
  204.      */
  205.     public function supportsNormalization(mixed $datastring $format null, array $context = []): bool
  206.     {
  207.         return null !== $this->getNormalizer($data$format$context);
  208.     }
  209.     /**
  210.      * {@inheritdoc}
  211.      */
  212.     public function supportsDenormalization(mixed $datastring $typestring $format null, array $context = []): bool
  213.     {
  214.         return isset(self::SCALAR_TYPES[$type]) || null !== $this->getDenormalizer($data$type$format$context);
  215.     }
  216.     /**
  217.      * Returns a matching normalizer.
  218.      *
  219.      * @param mixed  $data    Data to get the serializer for
  220.      * @param string $format  Format name, present to give the option to normalizers to act differently based on formats
  221.      * @param array  $context Options available to the normalizer
  222.      */
  223.     private function getNormalizer(mixed $data, ?string $format, array $context): ?NormalizerInterface
  224.     {
  225.         $type \is_object($data) ? \get_class($data) : 'native-'.\gettype($data);
  226.         if (!isset($this->normalizerCache[$format][$type])) {
  227.             $this->normalizerCache[$format][$type] = [];
  228.             foreach ($this->normalizers as $k => $normalizer) {
  229.                 if (!$normalizer instanceof NormalizerInterface) {
  230.                     continue;
  231.                 }
  232.                 if (!$normalizer instanceof CacheableSupportsMethodInterface || !$normalizer->hasCacheableSupportsMethod()) {
  233.                     $this->normalizerCache[$format][$type][$k] = false;
  234.                 } elseif ($normalizer->supportsNormalization($data$format$context)) {
  235.                     $this->normalizerCache[$format][$type][$k] = true;
  236.                     break;
  237.                 }
  238.             }
  239.         }
  240.         foreach ($this->normalizerCache[$format][$type] as $k => $cached) {
  241.             $normalizer $this->normalizers[$k];
  242.             if ($cached || $normalizer->supportsNormalization($data$format$context)) {
  243.                 return $normalizer;
  244.             }
  245.         }
  246.         return null;
  247.     }
  248.     /**
  249.      * Returns a matching denormalizer.
  250.      *
  251.      * @param mixed  $data    Data to restore
  252.      * @param string $class   The expected class to instantiate
  253.      * @param string $format  Format name, present to give the option to normalizers to act differently based on formats
  254.      * @param array  $context Options available to the denormalizer
  255.      */
  256.     private function getDenormalizer(mixed $datastring $class, ?string $format, array $context): ?DenormalizerInterface
  257.     {
  258.         if (!isset($this->denormalizerCache[$format][$class])) {
  259.             $this->denormalizerCache[$format][$class] = [];
  260.             foreach ($this->normalizers as $k => $normalizer) {
  261.                 if (!$normalizer instanceof DenormalizerInterface) {
  262.                     continue;
  263.                 }
  264.                 if (!$normalizer instanceof CacheableSupportsMethodInterface || !$normalizer->hasCacheableSupportsMethod()) {
  265.                     $this->denormalizerCache[$format][$class][$k] = false;
  266.                 } elseif ($normalizer->supportsDenormalization(null$class$format$context)) {
  267.                     $this->denormalizerCache[$format][$class][$k] = true;
  268.                     break;
  269.                 }
  270.             }
  271.         }
  272.         foreach ($this->denormalizerCache[$format][$class] as $k => $cached) {
  273.             $normalizer $this->normalizers[$k];
  274.             if ($cached || $normalizer->supportsDenormalization($data$class$format$context)) {
  275.                 return $normalizer;
  276.             }
  277.         }
  278.         return null;
  279.     }
  280.     /**
  281.      * {@inheritdoc}
  282.      */
  283.     final public function encode(mixed $datastring $format, array $context = []): string
  284.     {
  285.         return $this->encoder->encode($data$format$context);
  286.     }
  287.     /**
  288.      * {@inheritdoc}
  289.      */
  290.     final public function decode(string $datastring $format, array $context = []): mixed
  291.     {
  292.         return $this->decoder->decode($data$format$context);
  293.     }
  294.     /**
  295.      * {@inheritdoc}
  296.      */
  297.     public function supportsEncoding(string $format, array $context = []): bool
  298.     {
  299.         return $this->encoder->supportsEncoding($format$context);
  300.     }
  301.     /**
  302.      * {@inheritdoc}
  303.      */
  304.     public function supportsDecoding(string $format, array $context = []): bool
  305.     {
  306.         return $this->decoder->supportsDecoding($format$context);
  307.     }
  308. }