/src/Symfony/Component/Serializer/Serializer.php

https://github.com/ap0ught/symfony · PHP · 328 lines · 199 code · 48 blank · 81 comment · 37 complexity · bee68fc0a46816894e057101d3029aa2 MD5 · raw file

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