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($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($datastring $typestring $format, array $context = [])
  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($data, ?string $format null, array $context = [])
  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.      * @throws PartialDenormalizationException Occurs when one or more properties of $type fails to denormalize
  171.      */
  172.     public function denormalize($datastring $type, ?string $format null, array $context = [])
  173.     {
  174.         if (isset($context[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS], $context['not_normalizable_value_exceptions'])) {
  175.             throw new LogicException('Passing a value for "not_normalizable_value_exceptions" context key is not allowed.');
  176.         }
  177.         $normalizer $this->getDenormalizer($data$type$format$context);
  178.         // Check for a denormalizer first, e.g. the data is wrapped
  179.         if (!$normalizer && isset(self::SCALAR_TYPES[$type])) {
  180.             if (!('is_'.$type)($data)) {
  181.                 throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Data expected to be of type "%s" ("%s" given).'$typeget_debug_type($data)), $data, [$type], $context['deserialization_path'] ?? nulltrue);
  182.             }
  183.             return $data;
  184.         }
  185.         if (!$this->normalizers) {
  186.             throw new LogicException('You must register at least one normalizer to be able to denormalize objects.');
  187.         }
  188.         if (!$normalizer) {
  189.             throw new NotNormalizableValueException(sprintf('Could not denormalize object of type "%s", no supporting normalizer found.'$type));
  190.         }
  191.         if (isset($context[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS])) {
  192.             unset($context[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS]);
  193.             $context['not_normalizable_value_exceptions'] = [];
  194.             $errors = &$context['not_normalizable_value_exceptions'];
  195.             $denormalized $normalizer->denormalize($data$type$format$context);
  196.             if ($errors) {
  197.                 // merge errors so that one path has only one error
  198.                 $uniqueErrors = [];
  199.                 foreach ($errors as $error) {
  200.                     if (null === $error->getPath()) {
  201.                         $uniqueErrors[] = $error;
  202.                         continue;
  203.                     }
  204.                     $uniqueErrors[$error->getPath()] = $uniqueErrors[$error->getPath()] ?? $error;
  205.                 }
  206.                 throw new PartialDenormalizationException($denormalizedarray_values($uniqueErrors));
  207.             }
  208.             return $denormalized;
  209.         }
  210.         return $normalizer->denormalize($data$type$format$context);
  211.     }
  212.     /**
  213.      * {@inheritdoc}
  214.      */
  215.     public function supportsNormalization($data, ?string $format null, array $context = [])
  216.     {
  217.         return null !== $this->getNormalizer($data$format$context);
  218.     }
  219.     /**
  220.      * {@inheritdoc}
  221.      */
  222.     public function supportsDenormalization($datastring $type, ?string $format null, array $context = [])
  223.     {
  224.         return isset(self::SCALAR_TYPES[$type]) || null !== $this->getDenormalizer($data$type$format$context);
  225.     }
  226.     /**
  227.      * Returns a matching normalizer.
  228.      *
  229.      * @param mixed       $data    Data to get the serializer for
  230.      * @param string|null $format  Format name, present to give the option to normalizers to act differently based on formats
  231.      * @param array       $context Options available to the normalizer
  232.      */
  233.     private function getNormalizer($data, ?string $format, array $context): ?NormalizerInterface
  234.     {
  235.         $type = \is_object($data) ? \get_class($data) : 'native-'.\gettype($data);
  236.         if (!isset($this->normalizerCache[$format][$type])) {
  237.             $this->normalizerCache[$format][$type] = [];
  238.             foreach ($this->normalizers as $k => $normalizer) {
  239.                 if (!$normalizer instanceof NormalizerInterface) {
  240.                     continue;
  241.                 }
  242.                 if (!$normalizer instanceof CacheableSupportsMethodInterface || !$normalizer->hasCacheableSupportsMethod()) {
  243.                     $this->normalizerCache[$format][$type][$k] = false;
  244.                 } elseif ($normalizer->supportsNormalization($data$format$context)) {
  245.                     $this->normalizerCache[$format][$type][$k] = true;
  246.                     break;
  247.                 }
  248.             }
  249.         }
  250.         foreach ($this->normalizerCache[$format][$type] as $k => $cached) {
  251.             $normalizer $this->normalizers[$k];
  252.             if ($cached || $normalizer->supportsNormalization($data$format$context)) {
  253.                 return $normalizer;
  254.             }
  255.         }
  256.         return null;
  257.     }
  258.     /**
  259.      * Returns a matching denormalizer.
  260.      *
  261.      * @param mixed       $data    Data to restore
  262.      * @param string      $class   The expected class to instantiate
  263.      * @param string|null $format  Format name, present to give the option to normalizers to act differently based on formats
  264.      * @param array       $context Options available to the denormalizer
  265.      */
  266.     private function getDenormalizer($datastring $class, ?string $format, array $context): ?DenormalizerInterface
  267.     {
  268.         if (!isset($this->denormalizerCache[$format][$class])) {
  269.             $this->denormalizerCache[$format][$class] = [];
  270.             foreach ($this->normalizers as $k => $normalizer) {
  271.                 if (!$normalizer instanceof DenormalizerInterface) {
  272.                     continue;
  273.                 }
  274.                 if (!$normalizer instanceof CacheableSupportsMethodInterface || !$normalizer->hasCacheableSupportsMethod()) {
  275.                     $this->denormalizerCache[$format][$class][$k] = false;
  276.                 } elseif ($normalizer->supportsDenormalization(null$class$format$context)) {
  277.                     $this->denormalizerCache[$format][$class][$k] = true;
  278.                     break;
  279.                 }
  280.             }
  281.         }
  282.         foreach ($this->denormalizerCache[$format][$class] as $k => $cached) {
  283.             $normalizer $this->normalizers[$k];
  284.             if ($cached || $normalizer->supportsDenormalization($data$class$format$context)) {
  285.                 return $normalizer;
  286.             }
  287.         }
  288.         return null;
  289.     }
  290.     /**
  291.      * {@inheritdoc}
  292.      */
  293.     final public function encode($datastring $format, array $context = []): string
  294.     {
  295.         return $this->encoder->encode($data$format$context);
  296.     }
  297.     /**
  298.      * {@inheritdoc}
  299.      */
  300.     final public function decode(string $datastring $format, array $context = [])
  301.     {
  302.         return $this->decoder->decode($data$format$context);
  303.     }
  304.     /**
  305.      * {@inheritdoc}
  306.      */
  307.     public function supportsEncoding(string $format, array $context = [])
  308.     {
  309.         return $this->encoder->supportsEncoding($format$context);
  310.     }
  311.     /**
  312.      * {@inheritdoc}
  313.      */
  314.     public function supportsDecoding(string $format, array $context = [])
  315.     {
  316.         return $this->decoder->supportsDecoding($format$context);
  317.     }
  318. }