diff --git a/src/Symfony/Component/Serializer/CHANGELOG.md b/src/Symfony/Component/Serializer/CHANGELOG.md index 9c1dccb5d9fe9..983bb9f5a0654 100644 --- a/src/Symfony/Component/Serializer/CHANGELOG.md +++ b/src/Symfony/Component/Serializer/CHANGELOG.md @@ -4,6 +4,8 @@ CHANGELOG 4.1.0 ----- +* added `CacheableSupportsMethodInterface` for normalizers and denormalizers that use + only the type and the format in their `supports*()` methods * added `MissingConstructorArgumentsException` new exception for deserialization failure of objects that needs data insertion in constructor * added an optional `default_constructor_arguments` option of context to specify a default data in diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php index abac37770e333..a7055498d5ff8 100644 --- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php @@ -30,7 +30,7 @@ * * @author Kévin Dunglas */ -abstract class AbstractObjectNormalizer extends AbstractNormalizer +abstract class AbstractObjectNormalizer extends AbstractNormalizer implements CacheableSupportsMethodInterface { const ENABLE_MAX_DEPTH = 'enable_max_depth'; const DEPTH_KEY_PATTERN = 'depth_%s::%s'; @@ -38,7 +38,6 @@ abstract class AbstractObjectNormalizer extends AbstractNormalizer private $propertyTypeExtractor; private $attributesCache = array(); - private $cache = array(); /** * @var callable|null @@ -225,11 +224,7 @@ public function setMaxDepthHandler(?callable $handler): void */ public function supportsDenormalization($data, $type, $format = null) { - if (!isset($this->cache[$type])) { - $this->cache[$type] = class_exists($type) || (interface_exists($type) && null !== $this->classDiscriminatorResolver && null !== $this->classDiscriminatorResolver->getMappingForClass($type)); - } - - return $this->cache[$type]; + return \class_exists($type) || (\interface_exists($type, false) && $this->classDiscriminatorResolver && null !== $this->classDiscriminatorResolver->getMappingForClass($type)); } /** diff --git a/src/Symfony/Component/Serializer/Normalizer/CacheableSupportsMethodInterface.php b/src/Symfony/Component/Serializer/Normalizer/CacheableSupportsMethodInterface.php new file mode 100644 index 0000000000000..f3b50be7cdcd3 --- /dev/null +++ b/src/Symfony/Component/Serializer/Normalizer/CacheableSupportsMethodInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Normalizer; + +/** + * Marker interface for normalizers and denormalizers that use + * only the type and the format in their supports*() methods. + * + * By implementing this interface, the return value of the + * supports*() methods will be cached by type and format. + * + * @author Kévin Dunglas + */ +interface CacheableSupportsMethodInterface +{ +} diff --git a/src/Symfony/Component/Serializer/Normalizer/ConstraintViolationListNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ConstraintViolationListNormalizer.php index 68a4cb9213279..cf1c6616fa58c 100644 --- a/src/Symfony/Component/Serializer/Normalizer/ConstraintViolationListNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/ConstraintViolationListNormalizer.php @@ -22,7 +22,7 @@ * @author Grégoire Pineau * @author Kévin Dunglas */ -class ConstraintViolationListNormalizer implements NormalizerInterface +class ConstraintViolationListNormalizer implements NormalizerInterface, CacheableSupportsMethodInterface { /** * {@inheritdoc} diff --git a/src/Symfony/Component/Serializer/Normalizer/CustomNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/CustomNormalizer.php index 6a15d8da90270..87ad14102281f 100644 --- a/src/Symfony/Component/Serializer/Normalizer/CustomNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/CustomNormalizer.php @@ -17,13 +17,11 @@ /** * @author Jordi Boggiano */ -class CustomNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface +class CustomNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface, CacheableSupportsMethodInterface { use ObjectToPopulateTrait; use SerializerAwareTrait; - private $cache = array(); - /** * {@inheritdoc} */ @@ -67,14 +65,6 @@ public function supportsNormalization($data, $format = null) */ public function supportsDenormalization($data, $type, $format = null) { - if (isset($this->cache[$type])) { - return $this->cache[$type]; - } - - if (!class_exists($type)) { - return $this->cache[$type] = false; - } - - return $this->cache[$type] = is_subclass_of($type, 'Symfony\Component\Serializer\Normalizer\DenormalizableInterface'); + return \is_subclass_of($type, DenormalizableInterface::class); } } diff --git a/src/Symfony/Component/Serializer/Normalizer/DataUriNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/DataUriNormalizer.php index 995bdf1441776..9b637a269ca33 100644 --- a/src/Symfony/Component/Serializer/Normalizer/DataUriNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/DataUriNormalizer.php @@ -23,7 +23,7 @@ * * @author Kévin Dunglas */ -class DataUriNormalizer implements NormalizerInterface, DenormalizerInterface +class DataUriNormalizer implements NormalizerInterface, DenormalizerInterface, CacheableSupportsMethodInterface { private static $supportedTypes = array( \SplFileInfo::class => true, diff --git a/src/Symfony/Component/Serializer/Normalizer/DateIntervalNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/DateIntervalNormalizer.php index 7ab102ed7a9ec..ee30dddcada6d 100644 --- a/src/Symfony/Component/Serializer/Normalizer/DateIntervalNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/DateIntervalNormalizer.php @@ -20,7 +20,7 @@ * * @author Jérôme Parmentier */ -class DateIntervalNormalizer implements NormalizerInterface, DenormalizerInterface +class DateIntervalNormalizer implements NormalizerInterface, DenormalizerInterface, CacheableSupportsMethodInterface { const FORMAT_KEY = 'dateinterval_format'; diff --git a/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php index 3dd94e07e029b..aced67e37a881 100644 --- a/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/DateTimeNormalizer.php @@ -20,7 +20,7 @@ * * @author Kévin Dunglas */ -class DateTimeNormalizer implements NormalizerInterface, DenormalizerInterface +class DateTimeNormalizer implements NormalizerInterface, DenormalizerInterface, CacheableSupportsMethodInterface { const FORMAT_KEY = 'datetime_format'; const TIMEZONE_KEY = 'datetime_timezone'; diff --git a/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php index a6b8327be1ff9..4df997cbbcf39 100644 --- a/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/GetSetMethodNormalizer.php @@ -35,14 +35,13 @@ class GetSetMethodNormalizer extends AbstractObjectNormalizer { private static $setterAccessibleCache = array(); - private $cache = array(); /** * {@inheritdoc} */ public function supportsNormalization($data, $format = null) { - return parent::supportsNormalization($data, $format) && (isset($this->cache[$type = \get_class($data)]) ? $this->cache[$type] : $this->cache[$type] = $this->supports($type)); + return parent::supportsNormalization($data, $format) && $this->supports(\get_class($data)); } /** @@ -50,7 +49,7 @@ public function supportsNormalization($data, $format = null) */ public function supportsDenormalization($data, $type, $format = null) { - return parent::supportsDenormalization($data, $type, $format) && (isset($this->cache[$type]) ? $this->cache[$type] : $this->cache[$type] = $this->supports($type)); + return parent::supportsDenormalization($data, $type, $format) && $this->supports($type); } /** diff --git a/src/Symfony/Component/Serializer/Normalizer/JsonSerializableNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/JsonSerializableNormalizer.php index 27ccf8023cba3..912546b4b03c9 100644 --- a/src/Symfony/Component/Serializer/Normalizer/JsonSerializableNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/JsonSerializableNormalizer.php @@ -19,7 +19,7 @@ * * @author Fred Cox */ -class JsonSerializableNormalizer extends AbstractNormalizer +class JsonSerializableNormalizer extends AbstractNormalizer implements CacheableSupportsMethodInterface { /** * {@inheritdoc} diff --git a/src/Symfony/Component/Serializer/Normalizer/PropertyNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/PropertyNormalizer.php index fe0d3521e890e..5690d07d461ea 100644 --- a/src/Symfony/Component/Serializer/Normalizer/PropertyNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/PropertyNormalizer.php @@ -30,14 +30,12 @@ */ class PropertyNormalizer extends AbstractObjectNormalizer { - private $cache = array(); - /** * {@inheritdoc} */ public function supportsNormalization($data, $format = null) { - return parent::supportsNormalization($data, $format) && (isset($this->cache[$type = \get_class($data)]) ? $this->cache[$type] : $this->cache[$type] = $this->supports($type)); + return parent::supportsNormalization($data, $format) && $this->supports(\get_class($data)); } /** @@ -45,7 +43,7 @@ public function supportsNormalization($data, $format = null) */ public function supportsDenormalization($data, $type, $format = null) { - return parent::supportsDenormalization($data, $type, $format) && (isset($this->cache[$type]) ? $this->cache[$type] : $this->cache[$type] = $this->supports($type)); + return parent::supportsDenormalization($data, $type, $format) && $this->supports($type); } /** diff --git a/src/Symfony/Component/Serializer/Serializer.php b/src/Symfony/Component/Serializer/Serializer.php index 1a7087042567a..488b269d4fa6d 100644 --- a/src/Symfony/Component/Serializer/Serializer.php +++ b/src/Symfony/Component/Serializer/Serializer.php @@ -26,6 +26,7 @@ use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Exception\LogicException; +use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; /** * Serializer serializes and deserializes data. @@ -55,10 +56,14 @@ class Serializer implements SerializerInterface, ContextAwareNormalizerInterface protected $decoder; /** - * @var array + * @internal since Symfony 4.1 */ protected $normalizers = array(); + private $cachedNormalizers; + private $denormalizerCache = array(); + private $normalizerCache = array(); + public function __construct(array $normalizers = array(), array $encoders = array()) { foreach ($normalizers as $normalizer) { @@ -200,10 +205,35 @@ public function supportsDenormalization($data, $type, $format = null, array $con * * @return NormalizerInterface|null */ - private function getNormalizer($data, $format, array $context) + private function getNormalizer($data, ?string $format, array $context) { - foreach ($this->normalizers as $normalizer) { - if ($normalizer instanceof NormalizerInterface && $normalizer->supportsNormalization($data, $format, $context)) { + if ($this->cachedNormalizers !== $this->normalizers) { + $this->cachedNormalizers = $this->normalizers; + $this->denormalizerCache = $this->normalizerCache = array(); + } + $type = \is_object($data) ? \get_class($data) : 'native-'.\gettype($data); + + if (!isset($this->normalizerCache[$format][$type])) { + $this->normalizerCache[$format][$type] = array(); + + foreach ($this->normalizers as $k => $normalizer) { + if (!$normalizer instanceof NormalizerInterface) { + continue; + } + + if (!$normalizer instanceof CacheableSupportsMethodInterface) { + $this->normalizerCache[$format][$type][$k] = false; + } elseif ($normalizer->supportsNormalization($data, $format)) { + $this->normalizerCache[$format][$type][$k] = true; + + return $normalizer; + } + } + } + + foreach ($this->normalizerCache[$format][$type] as $k => $cached) { + $normalizer = $this->normalizers[$k]; + if ($cached || $normalizer->supportsNormalization($data, $format, $context)) { return $normalizer; } } @@ -219,10 +249,33 @@ private function getNormalizer($data, $format, array $context) * * @return DenormalizerInterface|null */ - private function getDenormalizer($data, $class, $format, array $context) + private function getDenormalizer($data, string $class, ?string $format, array $context) { - foreach ($this->normalizers as $normalizer) { - if ($normalizer instanceof DenormalizerInterface && $normalizer->supportsDenormalization($data, $class, $format, $context)) { + if ($this->cachedNormalizers !== $this->normalizers) { + $this->cachedNormalizers = $this->normalizers; + $this->denormalizerCache = $this->normalizerCache = array(); + } + if (!isset($this->denormalizerCache[$format][$class])) { + $this->denormalizerCache[$format][$class] = array(); + + foreach ($this->normalizers as $k => $normalizer) { + if (!$normalizer instanceof DenormalizerInterface) { + continue; + } + + if (!$normalizer instanceof CacheableSupportsMethodInterface) { + $this->denormalizerCache[$format][$class][$k] = false; + } elseif ($normalizer->supportsDenormalization(null, $class, $format)) { + $this->denormalizerCache[$format][$class][$k] = true; + + return $normalizer; + } + } + } + + foreach ($this->denormalizerCache[$format][$class] as $k => $cached) { + $normalizer = $this->normalizers[$k]; + if ($cached || $normalizer->supportsDenormalization($data, $class, $format, $context)) { return $normalizer; } }