From d62457270ee38c12a80d612ed9915885dc82e987 Mon Sep 17 00:00:00 2001 From: Maximilian Beckers Date: Fri, 17 May 2024 07:31:11 +0200 Subject: [PATCH 01/38] [Serializer] rename the first parameter of ``NormalizerInterface::normalize`` from object to data because of type mixed --- Normalizer/NormalizerInterface.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Normalizer/NormalizerInterface.php b/Normalizer/NormalizerInterface.php index 562f87c2..bbc8a94e 100644 --- a/Normalizer/NormalizerInterface.php +++ b/Normalizer/NormalizerInterface.php @@ -22,9 +22,9 @@ interface NormalizerInterface { /** - * Normalizes an object into a set of arrays/scalars. + * Normalizes data into a set of arrays/scalars. * - * @param mixed $object Object to normalize + * @param mixed $data Data to normalize * @param string|null $format Format the normalization result will be encoded as * @param array $context Context options for the normalizer * @@ -36,7 +36,7 @@ interface NormalizerInterface * @throws LogicException Occurs when the normalizer is not called in an expected context * @throws ExceptionInterface Occurs for all the other cases of errors */ - public function normalize(mixed $object, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null; + public function normalize(mixed $data, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null; /** * Checks whether the given class is supported for normalization by this normalizer. From 0128e9f9c91ac12bad70bfa82eb0dbdb9021b57f Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Mon, 3 Jun 2024 15:27:28 +0200 Subject: [PATCH 02/38] use constructor property promotion --- Extractor/ObjectPropertyListExtractor.php | 8 ++++---- Mapping/Loader/FileLoader.php | 9 +++------ Normalizer/AbstractNormalizer.php | 11 +++++------ 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/Extractor/ObjectPropertyListExtractor.php b/Extractor/ObjectPropertyListExtractor.php index 8422b078..42a54680 100644 --- a/Extractor/ObjectPropertyListExtractor.php +++ b/Extractor/ObjectPropertyListExtractor.php @@ -18,12 +18,12 @@ */ final class ObjectPropertyListExtractor implements ObjectPropertyListExtractorInterface { - private PropertyListExtractorInterface $propertyListExtractor; private \Closure $objectClassResolver; - public function __construct(PropertyListExtractorInterface $propertyListExtractor, ?callable $objectClassResolver = null) - { - $this->propertyListExtractor = $propertyListExtractor; + public function __construct( + private PropertyListExtractorInterface $propertyListExtractor, + ?callable $objectClassResolver = null, + ) { $this->objectClassResolver = ($objectClassResolver ?? 'get_class')(...); } diff --git a/Mapping/Loader/FileLoader.php b/Mapping/Loader/FileLoader.php index 7fda4ebd..ccd443b0 100644 --- a/Mapping/Loader/FileLoader.php +++ b/Mapping/Loader/FileLoader.php @@ -20,15 +20,14 @@ */ abstract class FileLoader implements LoaderInterface { - protected string $file; - /** * @param string $file The mapping file to load * * @throws MappingException if the mapping file does not exist or is not readable */ - public function __construct(string $file) - { + public function __construct( + protected string $file, + ) { if (!is_file($file)) { throw new MappingException(sprintf('The mapping file "%s" does not exist.', $file)); } @@ -36,7 +35,5 @@ public function __construct(string $file) if (!is_readable($file)) { throw new MappingException(sprintf('The mapping file "%s" is not readable.', $file)); } - - $this->file = $file; } } diff --git a/Normalizer/AbstractNormalizer.php b/Normalizer/AbstractNormalizer.php index 00458139..70714ac2 100644 --- a/Normalizer/AbstractNormalizer.php +++ b/Normalizer/AbstractNormalizer.php @@ -139,16 +139,15 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn self::CIRCULAR_REFERENCE_LIMIT => 1, self::IGNORED_ATTRIBUTES => [], ]; - protected ?ClassMetadataFactoryInterface $classMetadataFactory; - protected ?NameConverterInterface $nameConverter; /** * Sets the {@link ClassMetadataFactoryInterface} to use. */ - public function __construct(?ClassMetadataFactoryInterface $classMetadataFactory = null, ?NameConverterInterface $nameConverter = null, array $defaultContext = []) - { - $this->classMetadataFactory = $classMetadataFactory; - $this->nameConverter = $nameConverter; + public function __construct( + protected ?ClassMetadataFactoryInterface $classMetadataFactory = null, + protected ?NameConverterInterface $nameConverter = null, + array $defaultContext = [], + ) { $this->defaultContext = array_merge($this->defaultContext, $defaultContext); $this->validateCallbackContext($this->defaultContext, 'default'); From 4b41b4b5e0e33aa69921416b77658f6f7a65b489 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Thu, 20 Jun 2024 17:52:34 +0200 Subject: [PATCH 03/38] Prefix all sprintf() calls --- Attribute/Context.php | 4 +- Attribute/DiscriminatorMap.php | 4 +- Attribute/Groups.php | 4 +- Attribute/MaxDepth.php | 2 +- Attribute/SerializedName.php | 2 +- Attribute/SerializedPath.php | 2 +- Command/DebugCommand.php | 4 +- Context/Encoder/CsvEncoderContextBuilder.php | 6 +-- .../AbstractNormalizerContextBuilder.php | 2 +- ...AbstractObjectNormalizerContextBuilder.php | 2 +- .../DateTimeNormalizerContextBuilder.php | 2 +- .../UidNormalizerContextBuilder.php | 2 +- .../UnwrappingDenormalizerContextBuilder.php | 2 +- Debug/TraceableEncoder.php | 4 +- Debug/TraceableNormalizer.php | 4 +- Encoder/ChainDecoder.php | 2 +- Encoder/ChainEncoder.php | 2 +- Encoder/CsvEncoder.php | 2 +- Encoder/JsonDecode.php | 2 +- Encoder/XmlEncoder.php | 6 +-- Exception/ExtraAttributesException.php | 2 +- Exception/UnexpectedPropertyException.php | 2 +- .../Factory/ClassMetadataFactoryCompiler.php | 2 +- Mapping/Factory/ClassResolverTrait.php | 2 +- .../Factory/CompiledClassMetadataFactory.php | 2 +- Mapping/Loader/AttributeLoader.php | 16 ++++---- Mapping/Loader/FileLoader.php | 4 +- Mapping/Loader/LoaderChain.php | 2 +- Mapping/Loader/XmlFileLoader.php | 2 +- Mapping/Loader/YamlFileLoader.php | 20 +++++----- NameConverter/MetadataAwareNameConverter.php | 4 +- Normalizer/AbstractNormalizer.php | 22 +++++----- Normalizer/AbstractObjectNormalizer.php | 40 +++++++++---------- Normalizer/ArrayDenormalizer.php | 8 ++-- .../ConstraintViolationListNormalizer.php | 4 +- Normalizer/DataUriNormalizer.php | 8 ++-- Normalizer/DateIntervalNormalizer.php | 2 +- Normalizer/DateTimeNormalizer.php | 8 ++-- Normalizer/JsonSerializableNormalizer.php | 4 +- Normalizer/MimeMessageNormalizer.php | 2 +- Normalizer/ProblemNormalizer.php | 2 +- Normalizer/PropertyNormalizer.php | 2 +- Normalizer/TranslatableNormalizer.php | 2 +- Normalizer/UidNormalizer.php | 4 +- Serializer.php | 16 ++++---- Tests/Annotation/ContextTest.php | 2 +- Tests/Encoder/XmlEncoderTest.php | 4 +- Tests/Mapping/Loader/AttributeLoaderTest.php | 2 +- .../Features/CircularReferenceTestTrait.php | 2 +- .../ConstructorArgumentsTestTrait.php | 4 +- Tests/Normalizer/ObjectNormalizerTest.php | 4 +- 51 files changed, 130 insertions(+), 130 deletions(-) diff --git a/Attribute/Context.php b/Attribute/Context.php index 5ea2d2eb..892af481 100644 --- a/Attribute/Context.php +++ b/Attribute/Context.php @@ -36,14 +36,14 @@ public function __construct( string|array $groups = [], ) { if (!$context && !$normalizationContext && !$denormalizationContext) { - throw new InvalidArgumentException(sprintf('At least one of the "context", "normalizationContext", or "denormalizationContext" options must be provided as a non-empty array to "%s".', static::class)); + throw new InvalidArgumentException(\sprintf('At least one of the "context", "normalizationContext", or "denormalizationContext" options must be provided as a non-empty array to "%s".', static::class)); } $this->groups = (array) $groups; foreach ($this->groups as $group) { if (!\is_string($group)) { - throw new InvalidArgumentException(sprintf('Parameter "groups" given to "%s" must be a string or an array of strings, "%s" given.', static::class, get_debug_type($group))); + throw new InvalidArgumentException(\sprintf('Parameter "groups" given to "%s" must be a string or an array of strings, "%s" given.', static::class, get_debug_type($group))); } } } diff --git a/Attribute/DiscriminatorMap.php b/Attribute/DiscriminatorMap.php index a77fc298..48d0842a 100644 --- a/Attribute/DiscriminatorMap.php +++ b/Attribute/DiscriminatorMap.php @@ -30,11 +30,11 @@ public function __construct( private readonly array $mapping, ) { if (!$typeProperty) { - throw new InvalidArgumentException(sprintf('Parameter "typeProperty" given to "%s" cannot be empty.', static::class)); + throw new InvalidArgumentException(\sprintf('Parameter "typeProperty" given to "%s" cannot be empty.', static::class)); } if (!$mapping) { - throw new InvalidArgumentException(sprintf('Parameter "mapping" given to "%s" cannot be empty.', static::class)); + throw new InvalidArgumentException(\sprintf('Parameter "mapping" given to "%s" cannot be empty.', static::class)); } } diff --git a/Attribute/Groups.php b/Attribute/Groups.php index 39914f97..8747949a 100644 --- a/Attribute/Groups.php +++ b/Attribute/Groups.php @@ -32,12 +32,12 @@ public function __construct(string|array $groups) $this->groups = (array) $groups; if (!$this->groups) { - throw new InvalidArgumentException(sprintf('Parameter given to "%s" cannot be empty.', static::class)); + throw new InvalidArgumentException(\sprintf('Parameter given to "%s" cannot be empty.', static::class)); } foreach ($this->groups as $group) { if (!\is_string($group) || '' === $group) { - throw new InvalidArgumentException(sprintf('Parameter given to "%s" must be a string or an array of non-empty strings.', static::class)); + throw new InvalidArgumentException(\sprintf('Parameter given to "%s" must be a string or an array of non-empty strings.', static::class)); } } } diff --git a/Attribute/MaxDepth.php b/Attribute/MaxDepth.php index 17562b6c..16d6a812 100644 --- a/Attribute/MaxDepth.php +++ b/Attribute/MaxDepth.php @@ -25,7 +25,7 @@ class MaxDepth public function __construct(private readonly int $maxDepth) { if ($maxDepth <= 0) { - throw new InvalidArgumentException(sprintf('Parameter given to "%s" must be a positive integer.', static::class)); + throw new InvalidArgumentException(\sprintf('Parameter given to "%s" must be a positive integer.', static::class)); } } diff --git a/Attribute/SerializedName.php b/Attribute/SerializedName.php index 6acbebd0..f1c6cefe 100644 --- a/Attribute/SerializedName.php +++ b/Attribute/SerializedName.php @@ -25,7 +25,7 @@ class SerializedName public function __construct(private readonly string $serializedName) { if ('' === $serializedName) { - throw new InvalidArgumentException(sprintf('Parameter given to "%s" must be a non-empty string.', self::class)); + throw new InvalidArgumentException(\sprintf('Parameter given to "%s" must be a non-empty string.', self::class)); } } diff --git a/Attribute/SerializedPath.php b/Attribute/SerializedPath.php index 118e3adb..71254bfb 100644 --- a/Attribute/SerializedPath.php +++ b/Attribute/SerializedPath.php @@ -31,7 +31,7 @@ public function __construct(string $serializedPath) try { $this->serializedPath = new PropertyPath($serializedPath); } catch (InvalidPropertyPathException $pathException) { - throw new InvalidArgumentException(sprintf('Parameter given to "%s" must be a valid property path.', self::class)); + throw new InvalidArgumentException(\sprintf('Parameter given to "%s" must be a valid property path.', self::class)); } } diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php index c85ee213..3e70c93a 100644 --- a/Command/DebugCommand.php +++ b/Command/DebugCommand.php @@ -49,7 +49,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if (!class_exists($class)) { $io = new SymfonyStyle($input, $output); - $io->error(sprintf('Class "%s" was not found.', $class)); + $io->error(\sprintf('Class "%s" was not found.', $class)); return Command::FAILURE; } @@ -62,7 +62,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int private function dumpSerializerDataForClass(InputInterface $input, OutputInterface $output, string $class): void { $io = new SymfonyStyle($input, $output); - $title = sprintf('%s', $class); + $title = \sprintf('%s', $class); $rows = []; $dump = new Dumper($output); diff --git a/Context/Encoder/CsvEncoderContextBuilder.php b/Context/Encoder/CsvEncoderContextBuilder.php index f7575903..f449bc31 100644 --- a/Context/Encoder/CsvEncoderContextBuilder.php +++ b/Context/Encoder/CsvEncoderContextBuilder.php @@ -35,7 +35,7 @@ final class CsvEncoderContextBuilder implements ContextBuilderInterface public function withDelimiter(?string $delimiter): static { if (null !== $delimiter && 1 !== \strlen($delimiter)) { - throw new InvalidArgumentException(sprintf('The "%s" delimiter must be a single character.', $delimiter)); + throw new InvalidArgumentException(\sprintf('The "%s" delimiter must be a single character.', $delimiter)); } return $this->with(CsvEncoder::DELIMITER_KEY, $delimiter); @@ -51,7 +51,7 @@ public function withDelimiter(?string $delimiter): static public function withEnclosure(?string $enclosure): static { if (null !== $enclosure && 1 !== \strlen($enclosure)) { - throw new InvalidArgumentException(sprintf('The "%s" enclosure must be a single character.', $enclosure)); + throw new InvalidArgumentException(\sprintf('The "%s" enclosure must be a single character.', $enclosure)); } return $this->with(CsvEncoder::ENCLOSURE_KEY, $enclosure); @@ -67,7 +67,7 @@ public function withEnclosure(?string $enclosure): static public function withEscapeChar(?string $escapeChar): static { if (null !== $escapeChar && \strlen($escapeChar) > 1) { - throw new InvalidArgumentException(sprintf('The "%s" escape character must be empty or a single character.', $escapeChar)); + throw new InvalidArgumentException(\sprintf('The "%s" escape character must be empty or a single character.', $escapeChar)); } return $this->with(CsvEncoder::ESCAPE_CHAR_KEY, $escapeChar); diff --git a/Context/Normalizer/AbstractNormalizerContextBuilder.php b/Context/Normalizer/AbstractNormalizerContextBuilder.php index cb5b0544..a63e1a50 100644 --- a/Context/Normalizer/AbstractNormalizerContextBuilder.php +++ b/Context/Normalizer/AbstractNormalizerContextBuilder.php @@ -87,7 +87,7 @@ public function withAttributes(?array $attributes): static foreach ($it as $attribute) { if (!\is_string($attribute)) { - throw new InvalidArgumentException(sprintf('Each attribute must be a string, "%s" given.', get_debug_type($attribute))); + throw new InvalidArgumentException(\sprintf('Each attribute must be a string, "%s" given.', get_debug_type($attribute))); } } diff --git a/Context/Normalizer/AbstractObjectNormalizerContextBuilder.php b/Context/Normalizer/AbstractObjectNormalizerContextBuilder.php index a27f00c5..45b47bc1 100644 --- a/Context/Normalizer/AbstractObjectNormalizerContextBuilder.php +++ b/Context/Normalizer/AbstractObjectNormalizerContextBuilder.php @@ -47,7 +47,7 @@ public function withDepthKeyPattern(?string $depthKeyPattern): static preg_match_all('/(?[a-z])/', $depthKeyPattern, $matches); if (2 !== \count($matches['specifier']) || 's' !== $matches['specifier'][0] || 's' !== $matches['specifier'][1]) { - throw new InvalidArgumentException(sprintf('The depth key pattern "%s" is not valid. You must set exactly two string placeholders.', $depthKeyPattern)); + throw new InvalidArgumentException(\sprintf('The depth key pattern "%s" is not valid. You must set exactly two string placeholders.', $depthKeyPattern)); } return $this->with(AbstractObjectNormalizer::DEPTH_KEY_PATTERN, $depthKeyPattern); diff --git a/Context/Normalizer/DateTimeNormalizerContextBuilder.php b/Context/Normalizer/DateTimeNormalizerContextBuilder.php index e2d289e6..de83b124 100644 --- a/Context/Normalizer/DateTimeNormalizerContextBuilder.php +++ b/Context/Normalizer/DateTimeNormalizerContextBuilder.php @@ -55,7 +55,7 @@ public function withTimezone(\DateTimeZone|string|null $timezone): static try { $timezone = new \DateTimeZone($timezone); } catch (\Exception $e) { - throw new InvalidArgumentException(sprintf('The "%s" timezone is invalid.', $timezone), previous: $e); + throw new InvalidArgumentException(\sprintf('The "%s" timezone is invalid.', $timezone), previous: $e); } } diff --git a/Context/Normalizer/UidNormalizerContextBuilder.php b/Context/Normalizer/UidNormalizerContextBuilder.php index 1d889e50..b809fe3e 100644 --- a/Context/Normalizer/UidNormalizerContextBuilder.php +++ b/Context/Normalizer/UidNormalizerContextBuilder.php @@ -33,7 +33,7 @@ final class UidNormalizerContextBuilder implements ContextBuilderInterface public function withNormalizationFormat(?string $normalizationFormat): static { if (null !== $normalizationFormat && !\in_array($normalizationFormat, UidNormalizer::NORMALIZATION_FORMATS, true)) { - throw new InvalidArgumentException(sprintf('The "%s" normalization format is not valid.', $normalizationFormat)); + throw new InvalidArgumentException(\sprintf('The "%s" normalization format is not valid.', $normalizationFormat)); } return $this->with(UidNormalizer::NORMALIZATION_FORMAT_KEY, $normalizationFormat); diff --git a/Context/Normalizer/UnwrappingDenormalizerContextBuilder.php b/Context/Normalizer/UnwrappingDenormalizerContextBuilder.php index 5beb4e98..2945cb96 100644 --- a/Context/Normalizer/UnwrappingDenormalizerContextBuilder.php +++ b/Context/Normalizer/UnwrappingDenormalizerContextBuilder.php @@ -45,7 +45,7 @@ public function withUnwrapPath(?string $unwrapPath): static try { new PropertyPath($unwrapPath); } catch (InvalidPropertyPathException $e) { - throw new InvalidArgumentException(sprintf('The "%s" property path is not valid.', $unwrapPath), previous: $e); + throw new InvalidArgumentException(\sprintf('The "%s" property path is not valid.', $unwrapPath), previous: $e); } return $this->with(UnwrappingDenormalizer::UNWRAP_PATH, $unwrapPath); diff --git a/Debug/TraceableEncoder.php b/Debug/TraceableEncoder.php index 0795d14c..2d1ca62a 100644 --- a/Debug/TraceableEncoder.php +++ b/Debug/TraceableEncoder.php @@ -36,7 +36,7 @@ public function __construct( public function encode(mixed $data, string $format, array $context = []): string { if (!$this->encoder instanceof EncoderInterface) { - throw new \BadMethodCallException(sprintf('The "%s()" method cannot be called as nested encoder doesn\'t implements "%s".', __METHOD__, EncoderInterface::class)); + throw new \BadMethodCallException(\sprintf('The "%s()" method cannot be called as nested encoder doesn\'t implements "%s".', __METHOD__, EncoderInterface::class)); } $startTime = microtime(true); @@ -62,7 +62,7 @@ public function supportsEncoding(string $format, array $context = []): bool public function decode(string $data, string $format, array $context = []): mixed { if (!$this->encoder instanceof DecoderInterface) { - throw new \BadMethodCallException(sprintf('The "%s()" method cannot be called as nested encoder doesn\'t implements "%s".', __METHOD__, DecoderInterface::class)); + throw new \BadMethodCallException(\sprintf('The "%s()" method cannot be called as nested encoder doesn\'t implements "%s".', __METHOD__, DecoderInterface::class)); } $startTime = microtime(true); diff --git a/Debug/TraceableNormalizer.php b/Debug/TraceableNormalizer.php index 50842a7b..62e6663d 100644 --- a/Debug/TraceableNormalizer.php +++ b/Debug/TraceableNormalizer.php @@ -42,7 +42,7 @@ public function getSupportedTypes(?string $format): array public function normalize(mixed $object, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { if (!$this->normalizer instanceof NormalizerInterface) { - throw new \BadMethodCallException(sprintf('The "%s()" method cannot be called as nested normalizer doesn\'t implements "%s".', __METHOD__, NormalizerInterface::class)); + throw new \BadMethodCallException(\sprintf('The "%s()" method cannot be called as nested normalizer doesn\'t implements "%s".', __METHOD__, NormalizerInterface::class)); } $startTime = microtime(true); @@ -68,7 +68,7 @@ public function supportsNormalization(mixed $data, ?string $format = null, array public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { if (!$this->normalizer instanceof DenormalizerInterface) { - throw new \BadMethodCallException(sprintf('The "%s()" method cannot be called as nested normalizer doesn\'t implements "%s".', __METHOD__, DenormalizerInterface::class)); + throw new \BadMethodCallException(\sprintf('The "%s()" method cannot be called as nested normalizer doesn\'t implements "%s".', __METHOD__, DenormalizerInterface::class)); } $startTime = microtime(true); diff --git a/Encoder/ChainDecoder.php b/Encoder/ChainDecoder.php index ad182a9a..8df5cd8b 100644 --- a/Encoder/ChainDecoder.php +++ b/Encoder/ChainDecoder.php @@ -78,6 +78,6 @@ private function getDecoder(string $format, array $context): DecoderInterface } } - throw new RuntimeException(sprintf('No decoder found for format "%s".', $format)); + throw new RuntimeException(\sprintf('No decoder found for format "%s".', $format)); } } diff --git a/Encoder/ChainEncoder.php b/Encoder/ChainEncoder.php index 5445761f..3621471b 100644 --- a/Encoder/ChainEncoder.php +++ b/Encoder/ChainEncoder.php @@ -101,6 +101,6 @@ private function getEncoder(string $format, array $context): EncoderInterface } } - throw new RuntimeException(sprintf('No encoder found for format "%s".', $format)); + throw new RuntimeException(\sprintf('No encoder found for format "%s".', $format)); } } diff --git a/Encoder/CsvEncoder.php b/Encoder/CsvEncoder.php index a3112ae3..462bd663 100644 --- a/Encoder/CsvEncoder.php +++ b/Encoder/CsvEncoder.php @@ -241,7 +241,7 @@ private function getCsvOptions(array $context): array $asCollection = $context[self::AS_COLLECTION_KEY] ?? $this->defaultContext[self::AS_COLLECTION_KEY]; if (!\is_array($headers)) { - throw new InvalidArgumentException(sprintf('The "%s" context variable must be an array or null, given "%s".', self::HEADERS_KEY, get_debug_type($headers))); + throw new InvalidArgumentException(\sprintf('The "%s" context variable must be an array or null, given "%s".', self::HEADERS_KEY, get_debug_type($headers))); } return [$delimiter, $enclosure, $escapeChar, $keySeparator, $headers, $escapeFormulas, $outputBom, $asCollection]; diff --git a/Encoder/JsonDecode.php b/Encoder/JsonDecode.php index 0dbc9f6d..b5053817 100644 --- a/Encoder/JsonDecode.php +++ b/Encoder/JsonDecode.php @@ -106,7 +106,7 @@ public function decode(string $data, string $format, array $context = []): mixed } if (!class_exists(JsonParser::class)) { - throw new UnsupportedException(sprintf('Enabling "%s" serializer option requires seld/jsonlint. Try running "composer require seld/jsonlint".', self::DETAILED_ERROR_MESSAGES)); + throw new UnsupportedException(\sprintf('Enabling "%s" serializer option requires seld/jsonlint. Try running "composer require seld/jsonlint".', self::DETAILED_ERROR_MESSAGES)); } throw new NotEncodableValueException((new JsonParser())->lint($data)?->getMessage() ?: $errorMessage); diff --git a/Encoder/XmlEncoder.php b/Encoder/XmlEncoder.php index 5dcb2ba7..a57030b1 100644 --- a/Encoder/XmlEncoder.php +++ b/Encoder/XmlEncoder.php @@ -389,7 +389,7 @@ private function buildXml(\DOMNode $parentNode, mixed $data, string $format, arr if (\is_object($data)) { if (null === $this->serializer) { - throw new BadMethodCallException(sprintf('The serializer needs to be set to allow "%s()" to be used with object data.', __METHOD__)); + throw new BadMethodCallException(\sprintf('The serializer needs to be set to allow "%s()" to be used with object data.', __METHOD__)); } $data = $this->serializer->normalize($data, $format, $context); @@ -408,7 +408,7 @@ private function buildXml(\DOMNode $parentNode, mixed $data, string $format, arr return $this->appendNode($parentNode, $data, $format, $context, 'data'); } - throw new NotEncodableValueException('An unexpected value could not be serialized: '.(!\is_resource($data) ? var_export($data, true) : sprintf('%s resource', get_resource_type($data)))); + throw new NotEncodableValueException('An unexpected value could not be serialized: '.(!\is_resource($data) ? var_export($data, true) : \sprintf('%s resource', get_resource_type($data)))); } /** @@ -457,7 +457,7 @@ private function selectNodeType(\DOMNode $node, mixed $val, string $format, arra $node->appendChild($child); } elseif (\is_object($val)) { if (null === $this->serializer) { - throw new BadMethodCallException(sprintf('The serializer needs to be set to allow "%s()" to be used with object data.', __METHOD__)); + throw new BadMethodCallException(\sprintf('The serializer needs to be set to allow "%s()" to be used with object data.', __METHOD__)); } return $this->selectNodeType($node, $this->serializer->normalize($val, $format, $context), $format, $context); diff --git a/Exception/ExtraAttributesException.php b/Exception/ExtraAttributesException.php index 24b031ae..639808b1 100644 --- a/Exception/ExtraAttributesException.php +++ b/Exception/ExtraAttributesException.php @@ -22,7 +22,7 @@ public function __construct( private readonly array $extraAttributes, ?\Throwable $previous = null, ) { - $msg = sprintf('Extra attributes are not allowed ("%s" %s unknown).', implode('", "', $extraAttributes), \count($extraAttributes) > 1 ? 'are' : 'is'); + $msg = \sprintf('Extra attributes are not allowed ("%s" %s unknown).', implode('", "', $extraAttributes), \count($extraAttributes) > 1 ? 'are' : 'is'); parent::__construct($msg, 0, $previous); } diff --git a/Exception/UnexpectedPropertyException.php b/Exception/UnexpectedPropertyException.php index 4f9ead9a..03e42fca 100644 --- a/Exception/UnexpectedPropertyException.php +++ b/Exception/UnexpectedPropertyException.php @@ -22,7 +22,7 @@ public function __construct( public readonly string $property, ?\Throwable $previous = null, ) { - $msg = sprintf('Property is not allowed ("%s" is unknown).', $this->property); + $msg = \sprintf('Property is not allowed ("%s" is unknown).', $this->property); parent::__construct($msg, 0, $previous); } diff --git a/Mapping/Factory/ClassMetadataFactoryCompiler.php b/Mapping/Factory/ClassMetadataFactoryCompiler.php index f01fe9ce..1e9202b7 100644 --- a/Mapping/Factory/ClassMetadataFactoryCompiler.php +++ b/Mapping/Factory/ClassMetadataFactoryCompiler.php @@ -57,7 +57,7 @@ private function generateDeclaredClassMetadata(array $classMetadatas): string $classMetadata->getClassDiscriminatorMapping()->getTypesMapping(), ] : null; - $compiled .= sprintf("\n'%s' => %s,", $classMetadata->getName(), VarExporter::export([ + $compiled .= \sprintf("\n'%s' => %s,", $classMetadata->getName(), VarExporter::export([ $attributesMetadata, $classDiscriminatorMapping, ])); diff --git a/Mapping/Factory/ClassResolverTrait.php b/Mapping/Factory/ClassResolverTrait.php index 7af722d5..d7037f89 100644 --- a/Mapping/Factory/ClassResolverTrait.php +++ b/Mapping/Factory/ClassResolverTrait.php @@ -31,7 +31,7 @@ private function getClass(object|string $value): string { if (\is_string($value)) { if (!class_exists($value) && !interface_exists($value, false)) { - throw new InvalidArgumentException(sprintf('The class or interface "%s" does not exist.', $value)); + throw new InvalidArgumentException(\sprintf('The class or interface "%s" does not exist.', $value)); } return ltrim($value, '\\'); diff --git a/Mapping/Factory/CompiledClassMetadataFactory.php b/Mapping/Factory/CompiledClassMetadataFactory.php index f4d41c1e..ec25d744 100644 --- a/Mapping/Factory/CompiledClassMetadataFactory.php +++ b/Mapping/Factory/CompiledClassMetadataFactory.php @@ -35,7 +35,7 @@ public function __construct( $compiledClassMetadata = require $compiledClassMetadataFile; if (!\is_array($compiledClassMetadata)) { - throw new \RuntimeException(sprintf('Compiled metadata must be of the type array, %s given.', \gettype($compiledClassMetadata))); + throw new \RuntimeException(\sprintf('Compiled metadata must be of the type array, %s given.', \gettype($compiledClassMetadata))); } $this->compiledClassMetadata = $compiledClassMetadata; diff --git a/Mapping/Loader/AttributeLoader.php b/Mapping/Loader/AttributeLoader.php index ddb46716..272e236b 100644 --- a/Mapping/Loader/AttributeLoader.php +++ b/Mapping/Loader/AttributeLoader.php @@ -138,7 +138,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool foreach ($this->loadAttributes($method) as $annotation) { if ($annotation instanceof Groups) { if (!$accessorOrMutator) { - throw new MappingException(sprintf('Groups on "%s::%s()" cannot be added. Groups can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); + throw new MappingException(\sprintf('Groups on "%s::%s()" cannot be added. Groups can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); } foreach ($annotation->getGroups() as $group) { @@ -146,19 +146,19 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool } } elseif ($annotation instanceof MaxDepth) { if (!$accessorOrMutator) { - throw new MappingException(sprintf('MaxDepth on "%s::%s()" cannot be added. MaxDepth can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); + throw new MappingException(\sprintf('MaxDepth on "%s::%s()" cannot be added. MaxDepth can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); } $attributeMetadata->setMaxDepth($annotation->getMaxDepth()); } elseif ($annotation instanceof SerializedName) { if (!$accessorOrMutator) { - throw new MappingException(sprintf('SerializedName on "%s::%s()" cannot be added. SerializedName can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); + throw new MappingException(\sprintf('SerializedName on "%s::%s()" cannot be added. SerializedName can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); } $attributeMetadata->setSerializedName($annotation->getSerializedName()); } elseif ($annotation instanceof SerializedPath) { if (!$accessorOrMutator) { - throw new MappingException(sprintf('SerializedPath on "%s::%s()" cannot be added. SerializedPath can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); + throw new MappingException(\sprintf('SerializedPath on "%s::%s()" cannot be added. SerializedPath can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); } $attributeMetadata->setSerializedPath($annotation->getSerializedPath()); @@ -168,7 +168,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool } } elseif ($annotation instanceof Context) { if (!$accessorOrMutator) { - throw new MappingException(sprintf('Context on "%s::%s()" cannot be added. Context can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); + throw new MappingException(\sprintf('Context on "%s::%s()" cannot be added. Context can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); } $this->setAttributeContextsForGroups($annotation, $attributeMetadata); @@ -193,12 +193,12 @@ private function loadAttributes(\ReflectionMethod|\ReflectionClass|\ReflectionPr } $on = match (true) { $reflector instanceof \ReflectionClass => ' on class '.$reflector->name, - $reflector instanceof \ReflectionMethod => sprintf(' on "%s::%s()"', $reflector->getDeclaringClass()->name, $reflector->name), - $reflector instanceof \ReflectionProperty => sprintf(' on "%s::$%s"', $reflector->getDeclaringClass()->name, $reflector->name), + $reflector instanceof \ReflectionMethod => \sprintf(' on "%s::%s()"', $reflector->getDeclaringClass()->name, $reflector->name), + $reflector instanceof \ReflectionProperty => \sprintf(' on "%s::$%s"', $reflector->getDeclaringClass()->name, $reflector->name), default => '', }; - throw new MappingException(sprintf('Could not instantiate attribute "%s"%s.', $attribute->getName(), $on), 0, $e); + throw new MappingException(\sprintf('Could not instantiate attribute "%s"%s.', $attribute->getName(), $on), 0, $e); } } } diff --git a/Mapping/Loader/FileLoader.php b/Mapping/Loader/FileLoader.php index ccd443b0..ac68d21e 100644 --- a/Mapping/Loader/FileLoader.php +++ b/Mapping/Loader/FileLoader.php @@ -29,11 +29,11 @@ public function __construct( protected string $file, ) { if (!is_file($file)) { - throw new MappingException(sprintf('The mapping file "%s" does not exist.', $file)); + throw new MappingException(\sprintf('The mapping file "%s" does not exist.', $file)); } if (!is_readable($file)) { - throw new MappingException(sprintf('The mapping file "%s" is not readable.', $file)); + throw new MappingException(\sprintf('The mapping file "%s" is not readable.', $file)); } } } diff --git a/Mapping/Loader/LoaderChain.php b/Mapping/Loader/LoaderChain.php index e93a0a7b..41abf8ce 100644 --- a/Mapping/Loader/LoaderChain.php +++ b/Mapping/Loader/LoaderChain.php @@ -38,7 +38,7 @@ public function __construct(private readonly array $loaders) { foreach ($loaders as $loader) { if (!$loader instanceof LoaderInterface) { - throw new MappingException(sprintf('Class "%s" is expected to implement LoaderInterface.', get_debug_type($loader))); + throw new MappingException(\sprintf('Class "%s" is expected to implement LoaderInterface.', get_debug_type($loader))); } } } diff --git a/Mapping/Loader/XmlFileLoader.php b/Mapping/Loader/XmlFileLoader.php index b36fb871..44ba89df 100644 --- a/Mapping/Loader/XmlFileLoader.php +++ b/Mapping/Loader/XmlFileLoader.php @@ -70,7 +70,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool try { $attributeMetadata->setSerializedPath(new PropertyPath((string) $attribute['serialized-path'])); } catch (InvalidPropertyPathException) { - throw new MappingException(sprintf('The "serialized-path" value must be a valid property path for the attribute "%s" of the class "%s".', $attributeName, $classMetadata->getName())); + throw new MappingException(\sprintf('The "serialized-path" value must be a valid property path for the attribute "%s" of the class "%s".', $attributeName, $classMetadata->getName())); } } diff --git a/Mapping/Loader/YamlFileLoader.php b/Mapping/Loader/YamlFileLoader.php index b0398355..ca71cbcb 100644 --- a/Mapping/Loader/YamlFileLoader.php +++ b/Mapping/Loader/YamlFileLoader.php @@ -59,12 +59,12 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool if (isset($data['groups'])) { if (!\is_array($data['groups'])) { - throw new MappingException(sprintf('The "groups" key must be an array of strings in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); + throw new MappingException(\sprintf('The "groups" key must be an array of strings in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); } foreach ($data['groups'] as $group) { if (!\is_string($group)) { - throw new MappingException(sprintf('Group names must be strings in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); + throw new MappingException(\sprintf('Group names must be strings in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); } $attributeMetadata->addGroup($group); @@ -73,7 +73,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool if (isset($data['max_depth'])) { if (!\is_int($data['max_depth'])) { - throw new MappingException(sprintf('The "max_depth" value must be an integer in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); + throw new MappingException(\sprintf('The "max_depth" value must be an integer in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); } $attributeMetadata->setMaxDepth($data['max_depth']); @@ -81,7 +81,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool if (isset($data['serialized_name'])) { if (!\is_string($data['serialized_name']) || '' === $data['serialized_name']) { - throw new MappingException(sprintf('The "serialized_name" value must be a non-empty string in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); + throw new MappingException(\sprintf('The "serialized_name" value must be a non-empty string in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); } $attributeMetadata->setSerializedName($data['serialized_name']); @@ -91,13 +91,13 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool try { $attributeMetadata->setSerializedPath(new PropertyPath((string) $data['serialized_path'])); } catch (InvalidPropertyPathException) { - throw new MappingException(sprintf('The "serialized_path" value must be a valid property path in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); + throw new MappingException(\sprintf('The "serialized_path" value must be a valid property path in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); } } if (isset($data['ignore'])) { if (!\is_bool($data['ignore'])) { - throw new MappingException(sprintf('The "ignore" value must be a boolean in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); + throw new MappingException(\sprintf('The "ignore" value must be a boolean in "%s" for the attribute "%s" of the class "%s".', $this->file, $attribute, $classMetadata->getName())); } $attributeMetadata->setIgnore($data['ignore']); @@ -124,11 +124,11 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool if (isset($yaml['discriminator_map'])) { if (!isset($yaml['discriminator_map']['type_property'])) { - throw new MappingException(sprintf('The "type_property" key must be set for the discriminator map of the class "%s" in "%s".', $classMetadata->getName(), $this->file)); + throw new MappingException(\sprintf('The "type_property" key must be set for the discriminator map of the class "%s" in "%s".', $classMetadata->getName(), $this->file)); } if (!isset($yaml['discriminator_map']['mapping'])) { - throw new MappingException(sprintf('The "mapping" key must be set for the discriminator map of the class "%s" in "%s".', $classMetadata->getName(), $this->file)); + throw new MappingException(\sprintf('The "mapping" key must be set for the discriminator map of the class "%s" in "%s".', $classMetadata->getName(), $this->file)); } $classMetadata->setClassDiscriminatorMapping(new ClassDiscriminatorMapping( @@ -153,7 +153,7 @@ public function getMappedClasses(): array private function getClassesFromYaml(): array { if (!stream_is_local($this->file)) { - throw new MappingException(sprintf('This is not a local file "%s".', $this->file)); + throw new MappingException(\sprintf('This is not a local file "%s".', $this->file)); } $this->yamlParser ??= new Parser(); @@ -165,7 +165,7 @@ private function getClassesFromYaml(): array } if (!\is_array($classes)) { - throw new MappingException(sprintf('The file "%s" must contain a YAML array.', $this->file)); + throw new MappingException(\sprintf('The file "%s" must contain a YAML array.', $this->file)); } return $classes; diff --git a/NameConverter/MetadataAwareNameConverter.php b/NameConverter/MetadataAwareNameConverter.php index bc693bd9..341e6353 100644 --- a/NameConverter/MetadataAwareNameConverter.php +++ b/NameConverter/MetadataAwareNameConverter.php @@ -88,7 +88,7 @@ private function getCacheValueForNormalization(string $propertyName, string $cla } if (null !== $attributesMetadata[$propertyName]->getSerializedName() && null !== $attributesMetadata[$propertyName]->getSerializedPath()) { - throw new LogicException(sprintf('Found SerializedName and SerializedPath attributes on property "%s" of class "%s".', $propertyName, $class)); + throw new LogicException(\sprintf('Found SerializedName and SerializedPath attributes on property "%s" of class "%s".', $propertyName, $class)); } return $attributesMetadata[$propertyName]->getSerializedName() ?? null; @@ -132,7 +132,7 @@ private function getCacheValueForAttributesMetadata(string $class, array $contex } if (null !== $metadata->getSerializedName() && null !== $metadata->getSerializedPath()) { - throw new LogicException(sprintf('Found SerializedName and SerializedPath attributes on property "%s" of class "%s".', $name, $class)); + throw new LogicException(\sprintf('Found SerializedName and SerializedPath attributes on property "%s" of class "%s".', $name, $class)); } $metadataGroups = $metadata->getGroups(); diff --git a/Normalizer/AbstractNormalizer.php b/Normalizer/AbstractNormalizer.php index 70714ac2..d87bc059 100644 --- a/Normalizer/AbstractNormalizer.php +++ b/Normalizer/AbstractNormalizer.php @@ -153,7 +153,7 @@ public function __construct( $this->validateCallbackContext($this->defaultContext, 'default'); if (isset($this->defaultContext[self::CIRCULAR_REFERENCE_HANDLER]) && !\is_callable($this->defaultContext[self::CIRCULAR_REFERENCE_HANDLER])) { - throw new InvalidArgumentException(sprintf('Invalid callback found in the "%s" default context option.', self::CIRCULAR_REFERENCE_HANDLER)); + throw new InvalidArgumentException(\sprintf('Invalid callback found in the "%s" default context option.', self::CIRCULAR_REFERENCE_HANDLER)); } } @@ -199,7 +199,7 @@ protected function handleCircularReference(object $object, ?string $format = nul return $circularReferenceHandler($object, $format, $context); } - throw new CircularReferenceException(sprintf('A circular reference has been detected when serializing the object of class "%s" (configured limit: %d).', get_debug_type($object), $context[self::CIRCULAR_REFERENCE_LIMIT] ?? $this->defaultContext[self::CIRCULAR_REFERENCE_LIMIT])); + throw new CircularReferenceException(\sprintf('A circular reference has been detected when serializing the object of class "%s" (configured limit: %d).', get_debug_type($object), $context[self::CIRCULAR_REFERENCE_LIMIT] ?? $this->defaultContext[self::CIRCULAR_REFERENCE_LIMIT])); } /** @@ -216,7 +216,7 @@ protected function getAllowedAttributes(string|object $classOrObject, array $con $allowExtraAttributes = $context[self::ALLOW_EXTRA_ATTRIBUTES] ?? $this->defaultContext[self::ALLOW_EXTRA_ATTRIBUTES]; if (!$this->classMetadataFactory) { if (!$allowExtraAttributes) { - throw new LogicException(sprintf('A class metadata factory must be provided in the constructor when setting "%s" to false.', self::ALLOW_EXTRA_ATTRIBUTES)); + throw new LogicException(\sprintf('A class metadata factory must be provided in the constructor when setting "%s" to false.', self::ALLOW_EXTRA_ATTRIBUTES)); } return false; @@ -346,7 +346,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex if ($constructorParameter->isVariadic()) { if ($allowed && !$ignored && (isset($data[$key]) || \array_key_exists($key, $data))) { if (!\is_array($data[$key])) { - throw new RuntimeException(sprintf('Cannot create an instance of "%s" from serialized data because the variadic parameter "%s" can only accept an array.', $class, $constructorParameter->name)); + throw new RuntimeException(\sprintf('Cannot create an instance of "%s" from serialized data because the variadic parameter "%s" can only accept an array.', $class, $constructorParameter->name)); } $variadicParameters = []; @@ -399,7 +399,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex } $exception = NotNormalizableValueException::createForUnexpectedDataType( - sprintf('Failed to create object because the class misses the "%s" property.', $constructorParameter->name), + \sprintf('Failed to create object because the class misses the "%s" property.', $constructorParameter->name), null, [$constructorParameterType], $attributeContext['deserialization_path'] ?? null, @@ -410,7 +410,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex } if ($missingConstructorArguments) { - throw new MissingConstructorArgumentsException(sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires the following parameters to be present : "$%s".', $class, implode('", "$', $missingConstructorArguments)), 0, null, $missingConstructorArguments, $class); + throw new MissingConstructorArgumentsException(\sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires the following parameters to be present : "$%s".', $class, implode('", "$', $missingConstructorArguments)), 0, null, $missingConstructorArguments, $class); } if (!$constructor->isConstructor()) { @@ -445,7 +445,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex unset($context['has_constructor']); if (!$reflectionClass->isInstantiable()) { - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Failed to create object because the class "%s" is not instantiable.', $class), $data, ['unknown'], $context['deserialization_path'] ?? null); + throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('Failed to create object because the class "%s" is not instantiable.', $class), $data, ['unknown'], $context['deserialization_path'] ?? null); } return new $class(); @@ -462,13 +462,13 @@ protected function denormalizeParameter(\ReflectionClass $class, \ReflectionPara new \ReflectionClass($parameterClass); // throws a \ReflectionException if the class doesn't exist if (!$this->serializer instanceof DenormalizerInterface) { - throw new LogicException(sprintf('Cannot create an instance of "%s" from serialized data because the serializer inject in "%s" is not a denormalizer.', $parameterClass, static::class)); + throw new LogicException(\sprintf('Cannot create an instance of "%s" from serialized data because the serializer inject in "%s" is not a denormalizer.', $parameterClass, static::class)); } $parameterData = $this->serializer->denormalize($parameterData, $parameterClass, $format, $this->createChildContext($context, $parameterName, $format)); } } catch (\ReflectionException $e) { - throw new RuntimeException(sprintf('Could not determine the class of the parameter "%s".', $parameterName), 0, $e); + throw new RuntimeException(\sprintf('Could not determine the class of the parameter "%s".', $parameterName), 0, $e); } catch (MissingConstructorArgumentsException $e) { if (!$parameter->getType()->allowsNull()) { throw $e; @@ -510,12 +510,12 @@ final protected function validateCallbackContext(array $context, string $context } if (!\is_array($context[self::CALLBACKS])) { - throw new InvalidArgumentException(sprintf('The "%s"%s context option must be an array of callables.', self::CALLBACKS, '' !== $contextType ? " $contextType" : '')); + throw new InvalidArgumentException(\sprintf('The "%s"%s context option must be an array of callables.', self::CALLBACKS, '' !== $contextType ? " $contextType" : '')); } foreach ($context[self::CALLBACKS] as $attribute => $callback) { if (!\is_callable($callback)) { - throw new InvalidArgumentException(sprintf('Invalid callback found for attribute "%s" in the "%s"%s context option.', $attribute, self::CALLBACKS, '' !== $contextType ? " $contextType" : '')); + throw new InvalidArgumentException(\sprintf('Invalid callback found for attribute "%s" in the "%s"%s context option.', $attribute, self::CALLBACKS, '' !== $contextType ? " $contextType" : '')); } } } diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 27c38317..4a0a84b9 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -134,7 +134,7 @@ public function __construct( parent::__construct($classMetadataFactory, $nameConverter, $defaultContext); if (isset($this->defaultContext[self::MAX_DEPTH_HANDLER]) && !\is_callable($this->defaultContext[self::MAX_DEPTH_HANDLER])) { - throw new InvalidArgumentException(sprintf('The "%s" given in the default context is not callable.', self::MAX_DEPTH_HANDLER)); + throw new InvalidArgumentException(\sprintf('The "%s" given in the default context is not callable.', self::MAX_DEPTH_HANDLER)); } $this->defaultContext[self::EXCLUDE_FROM_CACHE_KEY] = array_merge($this->defaultContext[self::EXCLUDE_FROM_CACHE_KEY] ?? [], [self::CIRCULAR_REFERENCE_LIMIT_COUNTERS]); @@ -174,7 +174,7 @@ public function normalize(mixed $object, ?string $format = null, array $context if (isset($context[self::MAX_DEPTH_HANDLER])) { $maxDepthHandler = $context[self::MAX_DEPTH_HANDLER]; if (!\is_callable($maxDepthHandler)) { - throw new InvalidArgumentException(sprintf('The "%s" given in the context is not callable.', self::MAX_DEPTH_HANDLER)); + throw new InvalidArgumentException(\sprintf('The "%s" given in the context is not callable.', self::MAX_DEPTH_HANDLER)); } } else { $maxDepthHandler = null; @@ -215,7 +215,7 @@ public function normalize(mixed $object, ?string $format = null, array $context } if (!$this->serializer instanceof NormalizerInterface) { - throw new LogicException(sprintf('Cannot normalize attribute "%s" because the injected serializer is not a normalizer.', $attribute)); + throw new LogicException(\sprintf('Cannot normalize attribute "%s" because the injected serializer is not a normalizer.', $attribute)); } $childContext = $this->createChildContext($attributeContext, $attribute, $format); @@ -341,7 +341,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a $notConverted = $attribute; $attribute = $this->nameConverter->denormalize($attribute, $resolvedClass, $format, $context); if (isset($nestedData[$notConverted]) && !isset($originalNestedData[$attribute])) { - throw new LogicException(sprintf('Duplicate values for key "%s" found. One value is set via the SerializedPath attribute: "%s", the other one is set via the SerializedName attribute: "%s".', $notConverted, implode('->', $nestedAttributes[$notConverted]->getElements()), $attribute)); + throw new LogicException(\sprintf('Duplicate values for key "%s" found. One value is set via the SerializedPath attribute: "%s", the other one is set via the SerializedName attribute: "%s".', $notConverted, implode('->', $nestedAttributes[$notConverted]->getElements()), $attribute)); } } @@ -394,7 +394,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a $this->setAttributeValue($object, $attribute, $value, $format, $attributeContext); } catch (PropertyAccessInvalidArgumentException $e) { $exception = NotNormalizableValueException::createForUnexpectedDataType( - sprintf('Failed to denormalize attribute "%s" value for class "%s": '.$e->getMessage(), $attribute, $resolvedClass), + \sprintf('Failed to denormalize attribute "%s" value for class "%s": '.$e->getMessage(), $attribute, $resolvedClass), $data, $e instanceof InvalidTypeException ? [$e->expectedType] : ['unknown'], $attributeContext['deserialization_path'] ?? null, @@ -485,14 +485,14 @@ private function validateAndDenormalizeLegacy(array $types, string $currentClass } elseif ('true' === $data || '1' === $data) { $data = true; } else { - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be bool ("%s" given).', $attribute, $currentClass, $data), $data, [LegacyType::BUILTIN_TYPE_BOOL], $context['deserialization_path'] ?? null); + throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('The type of the "%s" attribute for class "%s" must be bool ("%s" given).', $attribute, $currentClass, $data), $data, [LegacyType::BUILTIN_TYPE_BOOL], $context['deserialization_path'] ?? null); } break; case LegacyType::BUILTIN_TYPE_INT: if (ctype_digit(isset($data[0]) && '-' === $data[0] ? substr($data, 1) : $data)) { $data = (int) $data; } else { - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be int ("%s" given).', $attribute, $currentClass, $data), $data, [LegacyType::BUILTIN_TYPE_INT], $context['deserialization_path'] ?? null); + throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('The type of the "%s" attribute for class "%s" must be int ("%s" given).', $attribute, $currentClass, $data), $data, [LegacyType::BUILTIN_TYPE_INT], $context['deserialization_path'] ?? null); } break; case LegacyType::BUILTIN_TYPE_FLOAT: @@ -504,7 +504,7 @@ private function validateAndDenormalizeLegacy(array $types, string $currentClass 'NaN' => \NAN, 'INF' => \INF, '-INF' => -\INF, - default => throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be float ("%s" given).', $attribute, $currentClass, $data), $data, [LegacyType::BUILTIN_TYPE_FLOAT], $context['deserialization_path'] ?? null), + default => throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('The type of the "%s" attribute for class "%s" must be float ("%s" given).', $attribute, $currentClass, $data), $data, [LegacyType::BUILTIN_TYPE_FLOAT], $context['deserialization_path'] ?? null), }; } } @@ -547,7 +547,7 @@ private function validateAndDenormalizeLegacy(array $types, string $currentClass if (LegacyType::BUILTIN_TYPE_OBJECT === $builtinType && null !== $class) { if (!$this->serializer instanceof DenormalizerInterface) { - throw new LogicException(sprintf('Cannot denormalize attribute "%s" for class "%s" because injected serializer is not a denormalizer.', $attribute, $class)); + throw new LogicException(\sprintf('Cannot denormalize attribute "%s" for class "%s" because injected serializer is not a denormalizer.', $attribute, $class)); } $childContext = $this->createChildContext($context, $attribute, $format); @@ -612,7 +612,7 @@ private function validateAndDenormalizeLegacy(array $types, string $currentClass return $data; } - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be one of "%s" ("%s" given).', $attribute, $currentClass, implode('", "', array_keys($expectedTypes)), get_debug_type($data)), $data, array_keys($expectedTypes), $context['deserialization_path'] ?? $attribute); + throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('The type of the "%s" attribute for class "%s" must be one of "%s" ("%s" given).', $attribute, $currentClass, implode('", "', array_keys($expectedTypes)), get_debug_type($data)), $data, array_keys($expectedTypes), $context['deserialization_path'] ?? $attribute); } /** @@ -678,14 +678,14 @@ private function validateAndDenormalize(Type $type, string $currentClass, string } elseif ('true' === $data || '1' === $data) { $data = true; } else { - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be bool ("%s" given).', $attribute, $currentClass, $data), $data, [Type::bool()], $context['deserialization_path'] ?? null); + throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('The type of the "%s" attribute for class "%s" must be bool ("%s" given).', $attribute, $currentClass, $data), $data, [Type::bool()], $context['deserialization_path'] ?? null); } break; case TypeIdentifier::INT: if (ctype_digit(isset($data[0]) && '-' === $data[0] ? substr($data, 1) : $data)) { $data = (int) $data; } else { - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be int ("%s" given).', $attribute, $currentClass, $data), $data, [Type::int()], $context['deserialization_path'] ?? null); + throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('The type of the "%s" attribute for class "%s" must be int ("%s" given).', $attribute, $currentClass, $data), $data, [Type::int()], $context['deserialization_path'] ?? null); } break; case TypeIdentifier::FLOAT: @@ -697,7 +697,7 @@ private function validateAndDenormalize(Type $type, string $currentClass, string 'NaN' => \NAN, 'INF' => \INF, '-INF' => -\INF, - default => throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be float ("%s" given).', $attribute, $currentClass, $data), $data, [Type::float()], $context['deserialization_path'] ?? null), + default => throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('The type of the "%s" attribute for class "%s" must be float ("%s" given).', $attribute, $currentClass, $data), $data, [Type::float()], $context['deserialization_path'] ?? null), }; } } @@ -754,7 +754,7 @@ private function validateAndDenormalize(Type $type, string $currentClass, string if (TypeIdentifier::OBJECT === $typeIdentifier && null !== $class) { if (!$this->serializer instanceof DenormalizerInterface) { - throw new LogicException(sprintf('Cannot denormalize attribute "%s" for class "%s" because injected serializer is not a denormalizer.', $attribute, $class)); + throw new LogicException(\sprintf('Cannot denormalize attribute "%s" for class "%s" because injected serializer is not a denormalizer.', $attribute, $class)); } $childContext = $this->createChildContext($context, $attribute, $format); @@ -828,7 +828,7 @@ private function validateAndDenormalize(Type $type, string $currentClass, string return $data; } - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the "%s" attribute for class "%s" must be one of "%s" ("%s" given).', $attribute, $currentClass, implode('", "', array_keys($expectedTypes)), get_debug_type($data)), $data, array_keys($expectedTypes), $context['deserialization_path'] ?? $attribute); + throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('The type of the "%s" attribute for class "%s" must be one of "%s" ("%s" given).', $attribute, $currentClass, implode('", "', array_keys($expectedTypes)), get_debug_type($data)), $data, array_keys($expectedTypes), $context['deserialization_path'] ?? $attribute); } /** @@ -915,7 +915,7 @@ private function updateData(array $data, string $attribute, mixed $attributeValu if (null !== $classMetadata && null !== $serializedPath = ($attributesMetadata[$attribute] ?? null)?->getSerializedPath()) { $propertyAccessor = PropertyAccess::createPropertyAccessor(); if ($propertyAccessor->isReadable($data, $serializedPath) && null !== $propertyAccessor->getValue($data, $serializedPath)) { - throw new LogicException(sprintf('The element you are trying to set is already populated: "%s".', (string) $serializedPath)); + throw new LogicException(\sprintf('The element you are trying to set is already populated: "%s".', (string) $serializedPath)); } $propertyAccessor->setValue($data, $serializedPath, $attributeValue); @@ -944,7 +944,7 @@ private function isMaxDepthReached(array $attributesMetadata, string $class, str return false; } - $key = sprintf(self::DEPTH_KEY_PATTERN, $class, $attribute); + $key = \sprintf(self::DEPTH_KEY_PATTERN, $class, $attribute); if (!isset($context[$key])) { $context[$key] = 1; @@ -1033,7 +1033,7 @@ private function getNestedAttributes(string $class): array } $pathIdentifier = implode(',', $serializedPath->getElements()); if (isset($serializedPaths[$pathIdentifier])) { - throw new LogicException(sprintf('Duplicate serialized path: "%s" used for properties "%s" and "%s".', $pathIdentifier, $serializedPaths[$pathIdentifier], $name)); + throw new LogicException(\sprintf('Duplicate serialized path: "%s" used for properties "%s" and "%s".', $pathIdentifier, $serializedPaths[$pathIdentifier], $name)); } $serializedPaths[$pathIdentifier] = $name; $properties[$name] = $serializedPath; @@ -1066,11 +1066,11 @@ private function getMappedClass(array $data, string $class, array $context): str } if (null === $type = $data[$mapping->getTypeProperty()] ?? null) { - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Type property "%s" not found for the abstract object "%s".', $mapping->getTypeProperty(), $class), null, ['string'], isset($context['deserialization_path']) ? $context['deserialization_path'].'.'.$mapping->getTypeProperty() : $mapping->getTypeProperty(), false); + throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('Type property "%s" not found for the abstract object "%s".', $mapping->getTypeProperty(), $class), null, ['string'], isset($context['deserialization_path']) ? $context['deserialization_path'].'.'.$mapping->getTypeProperty() : $mapping->getTypeProperty(), false); } if (null === $mappedClass = $mapping->getClassForType($type)) { - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type "%s" is not a valid value.', $type), $type, ['string'], isset($context['deserialization_path']) ? $context['deserialization_path'].'.'.$mapping->getTypeProperty() : $mapping->getTypeProperty(), true); + throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('The type "%s" is not a valid value.', $type), $type, ['string'], isset($context['deserialization_path']) ? $context['deserialization_path'].'.'.$mapping->getTypeProperty() : $mapping->getTypeProperty(), true); } return $mappedClass; diff --git a/Normalizer/ArrayDenormalizer.php b/Normalizer/ArrayDenormalizer.php index 1bd6c54b..af5ffb6a 100644 --- a/Normalizer/ArrayDenormalizer.php +++ b/Normalizer/ArrayDenormalizer.php @@ -48,7 +48,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a throw new BadMethodCallException('Please set a denormalizer before calling denormalize()!'); } if (!\is_array($data)) { - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Data expected to be "%s", "%s" given.', $type, get_debug_type($data)), $data, ['array'], $context['deserialization_path'] ?? null); + throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('Data expected to be "%s", "%s" given.', $type, get_debug_type($data)), $data, ['array'], $context['deserialization_path'] ?? null); } if (!str_ends_with($type, '[]')) { throw new InvalidArgumentException('Unsupported class: '.$type); @@ -67,7 +67,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a foreach ($data as $key => $value) { $subContext = $context; - $subContext['deserialization_path'] = ($context['deserialization_path'] ?? false) ? sprintf('%s[%s]', $context['deserialization_path'], $key) : "[$key]"; + $subContext['deserialization_path'] = ($context['deserialization_path'] ?? false) ? \sprintf('%s[%s]', $context['deserialization_path'], $key) : "[$key]"; $this->validateKeyType($typeIdentifiers, $key, $subContext['deserialization_path']); @@ -80,7 +80,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool { if (null === $this->denormalizer) { - throw new BadMethodCallException(sprintf('The nested denormalizer needs to be set to allow "%s()" to be used.', __METHOD__)); + throw new BadMethodCallException(\sprintf('The nested denormalizer needs to be set to allow "%s()" to be used.', __METHOD__)); } return str_ends_with($type, '[]') @@ -102,6 +102,6 @@ private function validateKeyType(array $typeIdentifiers, mixed $key, string $pat } } - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The type of the key "%s" must be "%s" ("%s" given).', $key, implode('", "', $typeIdentifiers), get_debug_type($key)), $key, $typeIdentifiers, $path, true); + throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('The type of the key "%s" must be "%s" ("%s" given).', $key, implode('", "', $typeIdentifiers), get_debug_type($key)), $key, $typeIdentifiers, $path, true); } } diff --git a/Normalizer/ConstraintViolationListNormalizer.php b/Normalizer/ConstraintViolationListNormalizer.php index eeb6ab46..eda3b758 100644 --- a/Normalizer/ConstraintViolationListNormalizer.php +++ b/Normalizer/ConstraintViolationListNormalizer.php @@ -69,7 +69,7 @@ public function normalize(mixed $object, ?string $format = null, array $context 'parameters' => $violation->getParameters(), ]; if (null !== $code = $violation->getCode()) { - $violationEntry['type'] = sprintf('urn:uuid:%s', $code); + $violationEntry['type'] = \sprintf('urn:uuid:%s', $code); } $constraint = $violation->getConstraint(); @@ -85,7 +85,7 @@ public function normalize(mixed $object, ?string $format = null, array $context $violations[] = $violationEntry; - $prefix = $propertyPath ? sprintf('%s: ', $propertyPath) : ''; + $prefix = $propertyPath ? \sprintf('%s: ', $propertyPath) : ''; $messages[] = $prefix.$violation->getMessage(); } diff --git a/Normalizer/DataUriNormalizer.php b/Normalizer/DataUriNormalizer.php index bd1dc822..f8577f84 100644 --- a/Normalizer/DataUriNormalizer.php +++ b/Normalizer/DataUriNormalizer.php @@ -68,10 +68,10 @@ public function normalize(mixed $object, ?string $format = null, array $context } if ('text' === explode('/', $mimeType, 2)[0]) { - return sprintf('data:%s,%s', $mimeType, rawurlencode($data)); + return \sprintf('data:%s,%s', $mimeType, rawurlencode($data)); } - return sprintf('data:%s;base64,%s', $mimeType, base64_encode($data)); + return \sprintf('data:%s;base64,%s', $mimeType, base64_encode($data)); } public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool @@ -97,7 +97,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a switch ($type) { case File::class: if (!class_exists(File::class)) { - throw new InvalidArgumentException(sprintf('Cannot denormalize to a "%s" without the HttpFoundation component installed. Try running "composer require symfony/http-foundation".', File::class)); + throw new InvalidArgumentException(\sprintf('Cannot denormalize to a "%s" without the HttpFoundation component installed. Try running "composer require symfony/http-foundation".', File::class)); } return new File($data, false); @@ -110,7 +110,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a throw NotNormalizableValueException::createForUnexpectedDataType($exception->getMessage(), $data, ['string'], $context['deserialization_path'] ?? null, false, $exception->getCode(), $exception); } - throw new InvalidArgumentException(sprintf('The class parameter "%s" is not supported. It must be one of "SplFileInfo", "SplFileObject" or "Symfony\Component\HttpFoundation\File\File".', $type)); + throw new InvalidArgumentException(\sprintf('The class parameter "%s" is not supported. It must be one of "SplFileInfo", "SplFileObject" or "Symfony\Component\HttpFoundation\File\File".', $type)); } public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool diff --git a/Normalizer/DateIntervalNormalizer.php b/Normalizer/DateIntervalNormalizer.php index 995d4f6a..05d1a852 100644 --- a/Normalizer/DateIntervalNormalizer.php +++ b/Normalizer/DateIntervalNormalizer.php @@ -85,7 +85,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a } $valuePattern = '/^'.$signPattern.preg_replace('/%([yYmMdDhHiIsSwW])(\w)/', '(?:(?P<$1>\d+)$2)?', preg_replace('/(T.*)$/', '($1)?', $dateIntervalFormat)).'$/'; if (!preg_match($valuePattern, $data)) { - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Value "%s" contains intervals not accepted by format "%s".', $data, $dateIntervalFormat), $data, ['string'], $context['deserialization_path'] ?? null, false); + throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('Value "%s" contains intervals not accepted by format "%s".', $data, $dateIntervalFormat), $data, ['string'], $context['deserialization_path'] ?? null, false); } try { diff --git a/Normalizer/DateTimeNormalizer.php b/Normalizer/DateTimeNormalizer.php index 71ce2649..fc32f6f5 100644 --- a/Normalizer/DateTimeNormalizer.php +++ b/Normalizer/DateTimeNormalizer.php @@ -94,10 +94,10 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a if (\is_int($data) || \is_float($data)) { switch ($context[self::FORMAT_KEY] ?? $this->defaultContext[self::FORMAT_KEY] ?? null) { case 'U': - $data = sprintf('%d', $data); + $data = \sprintf('%d', $data); break; case 'U.u': - $data = sprintf('%.6F', $data); + $data = \sprintf('%.6F', $data); break; } } @@ -121,7 +121,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a $dateTimeErrors = $type::getLastErrors(); - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Parsing datetime string "%s" using format "%s" resulted in %d errors: ', $data, $dateTimeFormat, $dateTimeErrors['error_count'])."\n".implode("\n", $this->formatDateTimeErrors($dateTimeErrors['errors'])), $data, ['string'], $context['deserialization_path'] ?? null, true); + throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('Parsing datetime string "%s" using format "%s" resulted in %d errors: ', $data, $dateTimeFormat, $dateTimeErrors['error_count'])."\n".implode("\n", $this->formatDateTimeErrors($dateTimeErrors['errors'])), $data, ['string'], $context['deserialization_path'] ?? null, true); } $defaultDateTimeFormat = $this->defaultContext[self::FORMAT_KEY] ?? null; @@ -155,7 +155,7 @@ private function formatDateTimeErrors(array $errors): array $formattedErrors = []; foreach ($errors as $pos => $message) { - $formattedErrors[] = sprintf('at position %d: %s', $pos, $message); + $formattedErrors[] = \sprintf('at position %d: %s', $pos, $message); } return $formattedErrors; diff --git a/Normalizer/JsonSerializableNormalizer.php b/Normalizer/JsonSerializableNormalizer.php index 1287627f..31c22417 100644 --- a/Normalizer/JsonSerializableNormalizer.php +++ b/Normalizer/JsonSerializableNormalizer.php @@ -28,7 +28,7 @@ public function normalize(mixed $object, ?string $format = null, array $context } if (!$object instanceof \JsonSerializable) { - throw new InvalidArgumentException(sprintf('The object must implement "%s".', \JsonSerializable::class)); + throw new InvalidArgumentException(\sprintf('The object must implement "%s".', \JsonSerializable::class)); } if (!$this->serializer instanceof NormalizerInterface) { @@ -57,6 +57,6 @@ public function supportsDenormalization(mixed $data, string $type, ?string $form public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { - throw new LogicException(sprintf('Cannot denormalize with "%s".', \JsonSerializable::class)); + throw new LogicException(\sprintf('Cannot denormalize with "%s".', \JsonSerializable::class)); } } diff --git a/Normalizer/MimeMessageNormalizer.php b/Normalizer/MimeMessageNormalizer.php index 7006ab32..633edf36 100644 --- a/Normalizer/MimeMessageNormalizer.php +++ b/Normalizer/MimeMessageNormalizer.php @@ -56,7 +56,7 @@ public function getSupportedTypes(?string $format): array public function setSerializer(SerializerInterface $serializer): void { if (!$serializer instanceof NormalizerInterface || !$serializer instanceof DenormalizerInterface) { - throw new LogicException(sprintf('The passed serializer should implement both NormalizerInterface and DenormalizerInterface, "%s" given.', get_debug_type($serializer))); + throw new LogicException(\sprintf('The passed serializer should implement both NormalizerInterface and DenormalizerInterface, "%s" given.', get_debug_type($serializer))); } $this->serializer = $serializer; $this->normalizer->setSerializer($serializer); diff --git a/Normalizer/ProblemNormalizer.php b/Normalizer/ProblemNormalizer.php index 04c68064..08aca679 100644 --- a/Normalizer/ProblemNormalizer.php +++ b/Normalizer/ProblemNormalizer.php @@ -54,7 +54,7 @@ public function getSupportedTypes(?string $format): array public function normalize(mixed $object, ?string $format = null, array $context = []): array { if (!$object instanceof FlattenException) { - throw new InvalidArgumentException(sprintf('The object must implement "%s".', FlattenException::class)); + throw new InvalidArgumentException(\sprintf('The object must implement "%s".', FlattenException::class)); } $data = []; diff --git a/Normalizer/PropertyNormalizer.php b/Normalizer/PropertyNormalizer.php index e1d893be..1619f35b 100644 --- a/Normalizer/PropertyNormalizer.php +++ b/Normalizer/PropertyNormalizer.php @@ -162,7 +162,7 @@ protected function getAttributeValue(object $object, string $attribute, ?string || ($reflectionProperty->isProtected() && !\array_key_exists("\0*\0{$reflectionProperty->name}", $propertyValues)) || ($reflectionProperty->isPrivate() && !\array_key_exists("\0{$reflectionProperty->class}\0{$reflectionProperty->name}", $propertyValues)) ) { - throw new UninitializedPropertyException(sprintf('The property "%s::$%s" is not initialized.', $object::class, $reflectionProperty->name)); + throw new UninitializedPropertyException(\sprintf('The property "%s::$%s" is not initialized.', $object::class, $reflectionProperty->name)); } } diff --git a/Normalizer/TranslatableNormalizer.php b/Normalizer/TranslatableNormalizer.php index 398e00b7..463616e7 100644 --- a/Normalizer/TranslatableNormalizer.php +++ b/Normalizer/TranslatableNormalizer.php @@ -37,7 +37,7 @@ public function __construct( public function normalize(mixed $object, ?string $format = null, array $context = []): string { if (!$object instanceof TranslatableInterface) { - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The object must implement the "%s".', TranslatableInterface::class), $object, [TranslatableInterface::class]); + throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('The object must implement the "%s".', TranslatableInterface::class), $object, [TranslatableInterface::class]); } return $object->trans($this->translator, $context[self::NORMALIZATION_LOCALE_KEY] ?? $this->defaultContext[self::NORMALIZATION_LOCALE_KEY]); diff --git a/Normalizer/UidNormalizer.php b/Normalizer/UidNormalizer.php index a6cc190a..b107c9d3 100644 --- a/Normalizer/UidNormalizer.php +++ b/Normalizer/UidNormalizer.php @@ -56,7 +56,7 @@ public function normalize(mixed $object, ?string $format = null, array $context self::NORMALIZATION_FORMAT_BASE58 => $object->toBase58(), self::NORMALIZATION_FORMAT_BASE32 => $object->toBase32(), self::NORMALIZATION_FORMAT_RFC4122 => $object->toRfc4122(), - default => throw new LogicException(sprintf('The "%s" format is not valid.', $context[self::NORMALIZATION_FORMAT_KEY] ?? $this->defaultContext[self::NORMALIZATION_FORMAT_KEY])), + default => throw new LogicException(\sprintf('The "%s" format is not valid.', $context[self::NORMALIZATION_FORMAT_KEY] ?? $this->defaultContext[self::NORMALIZATION_FORMAT_KEY])), }; } @@ -70,7 +70,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a try { return $type::fromString($data); } catch (\InvalidArgumentException|\TypeError) { - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('The data is not a valid "%s" string representation.', $type), $data, ['string'], $context['deserialization_path'] ?? null, true); + throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('The data is not a valid "%s" string representation.', $type), $data, ['string'], $context['deserialization_path'] ?? null, true); } } diff --git a/Serializer.php b/Serializer.php index 9fba20f1..b907a518 100644 --- a/Serializer.php +++ b/Serializer.php @@ -94,7 +94,7 @@ public function __construct( } if (!($normalizer instanceof NormalizerInterface || $normalizer instanceof DenormalizerInterface)) { - throw new InvalidArgumentException(sprintf('The class "%s" neither implements "%s" nor "%s".', get_debug_type($normalizer), NormalizerInterface::class, DenormalizerInterface::class)); + throw new InvalidArgumentException(\sprintf('The class "%s" neither implements "%s" nor "%s".', get_debug_type($normalizer), NormalizerInterface::class, DenormalizerInterface::class)); } } @@ -112,7 +112,7 @@ public function __construct( } if (!($encoder instanceof EncoderInterface || $encoder instanceof DecoderInterface)) { - throw new InvalidArgumentException(sprintf('The class "%s" neither implements "%s" nor "%s".', get_debug_type($encoder), EncoderInterface::class, DecoderInterface::class)); + throw new InvalidArgumentException(\sprintf('The class "%s" neither implements "%s" nor "%s".', get_debug_type($encoder), EncoderInterface::class, DecoderInterface::class)); } } $this->encoder = new ChainEncoder($realEncoders); @@ -122,7 +122,7 @@ public function __construct( final public function serialize(mixed $data, string $format, array $context = []): string { if (!$this->supportsEncoding($format, $context)) { - throw new UnsupportedFormatException(sprintf('Serialization for the format "%s" is not supported.', $format)); + throw new UnsupportedFormatException(\sprintf('Serialization for the format "%s" is not supported.', $format)); } if ($this->encoder->needsNormalization($format, $context)) { @@ -135,7 +135,7 @@ final public function serialize(mixed $data, string $format, array $context = [] final public function deserialize(mixed $data, string $type, string $format, array $context = []): mixed { if (!$this->supportsDecoding($format, $context)) { - throw new UnsupportedFormatException(sprintf('Deserialization for the format "%s" is not supported.', $format)); + throw new UnsupportedFormatException(\sprintf('Deserialization for the format "%s" is not supported.', $format)); } $data = $this->decode($data, $format, $context); @@ -176,10 +176,10 @@ public function normalize(mixed $data, ?string $format = null, array $context = throw new LogicException('You must register at least one normalizer to be able to normalize objects.'); } - throw new NotNormalizableValueException(sprintf('Could not normalize object of type "%s", no supporting normalizer found.', get_debug_type($data))); + throw new NotNormalizableValueException(\sprintf('Could not normalize object of type "%s", no supporting normalizer found.', get_debug_type($data))); } - throw new NotNormalizableValueException('An unexpected value could not be normalized: '.(!\is_resource($data) ? var_export($data, true) : sprintf('"%s" resource', get_resource_type($data)))); + throw new NotNormalizableValueException('An unexpected value could not be normalized: '.(!\is_resource($data) ? var_export($data, true) : \sprintf('"%s" resource', get_resource_type($data)))); } /** @@ -197,7 +197,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a // Check for a denormalizer first, e.g. the data is wrapped if (!$normalizer && isset(self::SCALAR_TYPES[$type])) { if (!('is_'.$type)($data)) { - throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Data expected to be of type "%s" ("%s" given).', $type, get_debug_type($data)), $data, [$type], $context['deserialization_path'] ?? null, true); + throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('Data expected to be of type "%s" ("%s" given).', $type, get_debug_type($data)), $data, [$type], $context['deserialization_path'] ?? null, true); } return $data; @@ -208,7 +208,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a } if (!$normalizer) { - throw new NotNormalizableValueException(sprintf('Could not denormalize object of type "%s", no supporting normalizer found.', $type)); + throw new NotNormalizableValueException(\sprintf('Could not denormalize object of type "%s", no supporting normalizer found.', $type)); } if (isset($context[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS])) { diff --git a/Tests/Annotation/ContextTest.php b/Tests/Annotation/ContextTest.php index 9584d6f1..84ff41b8 100644 --- a/Tests/Annotation/ContextTest.php +++ b/Tests/Annotation/ContextTest.php @@ -40,7 +40,7 @@ public function testThrowsOnEmptyContext() public function testInvalidGroupOption() { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage(sprintf('Parameter "groups" given to "%s" must be a string or an array of strings, "stdClass" given', Context::class)); + $this->expectExceptionMessage(\sprintf('Parameter "groups" given to "%s" must be a string or an array of strings, "stdClass" given', Context::class)); new Context(context: ['foo' => 'bar'], groups: ['fine', new \stdClass()]); } diff --git a/Tests/Encoder/XmlEncoderTest.php b/Tests/Encoder/XmlEncoderTest.php index 5be6be23..91163762 100644 --- a/Tests/Encoder/XmlEncoderTest.php +++ b/Tests/Encoder/XmlEncoderTest.php @@ -1050,14 +1050,14 @@ private function createMockDateTimeNormalizer(): MockObject&NormalizerInterface private function createXmlWithDateTime(): string { - return sprintf(' + return \sprintf(' %s ', $this->exampleDateTimeString); } private function createXmlWithDateTimeField(): string { - return sprintf(' + return \sprintf(' ', $this->exampleDateTimeString); } diff --git a/Tests/Mapping/Loader/AttributeLoaderTest.php b/Tests/Mapping/Loader/AttributeLoaderTest.php index a7659550..5b6ef3de 100644 --- a/Tests/Mapping/Loader/AttributeLoaderTest.php +++ b/Tests/Mapping/Loader/AttributeLoaderTest.php @@ -162,7 +162,7 @@ public function testLoadContextsPropertiesPromoted() public function testThrowsOnContextOnInvalidMethod() { $this->expectException(MappingException::class); - $this->expectExceptionMessage(sprintf('Context on "%s::badMethod()" cannot be added', BadMethodContextDummy::class)); + $this->expectExceptionMessage(\sprintf('Context on "%s::badMethod()" cannot be added', BadMethodContextDummy::class)); $loader = $this->getLoaderForContextMapping(); diff --git a/Tests/Normalizer/Features/CircularReferenceTestTrait.php b/Tests/Normalizer/Features/CircularReferenceTestTrait.php index d02e245f..85720bcf 100644 --- a/Tests/Normalizer/Features/CircularReferenceTestTrait.php +++ b/Tests/Normalizer/Features/CircularReferenceTestTrait.php @@ -42,7 +42,7 @@ public function testUnableToNormalizeCircularReference(array $defaultContext, ar $obj = $this->getSelfReferencingModel(); $this->expectException(CircularReferenceException::class); - $this->expectExceptionMessage(sprintf('A circular reference has been detected when serializing the object of class "%s" (configured limit: %d).', $obj::class, $expectedLimit)); + $this->expectExceptionMessage(\sprintf('A circular reference has been detected when serializing the object of class "%s" (configured limit: %d).', $obj::class, $expectedLimit)); $normalizer->normalize($obj, null, $context); } diff --git a/Tests/Normalizer/Features/ConstructorArgumentsTestTrait.php b/Tests/Normalizer/Features/ConstructorArgumentsTestTrait.php index 72652f34..0a5f6f24 100644 --- a/Tests/Normalizer/Features/ConstructorArgumentsTestTrait.php +++ b/Tests/Normalizer/Features/ConstructorArgumentsTestTrait.php @@ -64,10 +64,10 @@ public function testConstructorWithMissingData() $normalizer = $this->getDenormalizerForConstructArguments(); try { $normalizer->denormalize($data, ConstructorArgumentsObject::class); - self::fail(sprintf('Failed asserting that exception of type "%s" is thrown.', MissingConstructorArgumentsException::class)); + self::fail(\sprintf('Failed asserting that exception of type "%s" is thrown.', MissingConstructorArgumentsException::class)); } catch (MissingConstructorArgumentsException $e) { self::assertSame(ConstructorArgumentsObject::class, $e->getClass()); - self::assertSame(sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires the following parameters to be present : "$foo", "$baz".', ConstructorArgumentsObject::class), $e->getMessage()); + self::assertSame(\sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires the following parameters to be present : "$foo", "$baz".', ConstructorArgumentsObject::class), $e->getMessage()); self::assertSame(['foo', 'baz'], $e->getMissingConstructorArguments()); } } diff --git a/Tests/Normalizer/ObjectNormalizerTest.php b/Tests/Normalizer/ObjectNormalizerTest.php index 19bbcde2..8c564f00 100644 --- a/Tests/Normalizer/ObjectNormalizerTest.php +++ b/Tests/Normalizer/ObjectNormalizerTest.php @@ -786,12 +786,12 @@ public function testAdvancedNameConverter() $nameConverter = new class() implements AdvancedNameConverterInterface { public function normalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string { - return sprintf('%s-%s-%s-%s', $propertyName, $class, $format, $context['foo']); + return \sprintf('%s-%s-%s-%s', $propertyName, $class, $format, $context['foo']); } public function denormalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string { - return sprintf('%s-%s-%s-%s', $propertyName, $class, $format, $context['foo']); + return \sprintf('%s-%s-%s-%s', $propertyName, $class, $format, $context['foo']); } }; From 289be0b7bf80214234d4f2d804d6c037e7b4ca50 Mon Sep 17 00:00:00 2001 From: hbgamra Date: Fri, 21 Jun 2024 14:38:03 +0200 Subject: [PATCH 04/38] Update ClassMetadata.php Optimize the getReflectionClass() function --- Mapping/ClassMetadata.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Mapping/ClassMetadata.php b/Mapping/ClassMetadata.php index 5cec14c9..179e6050 100644 --- a/Mapping/ClassMetadata.php +++ b/Mapping/ClassMetadata.php @@ -78,11 +78,7 @@ public function merge(ClassMetadataInterface $classMetadata): void public function getReflectionClass(): \ReflectionClass { - if (!$this->reflClass) { - $this->reflClass = new \ReflectionClass($this->getName()); - } - - return $this->reflClass; + return $this->reflClass ??= new \ReflectionClass($this->getName()); } public function getClassDiscriminatorMapping(): ?ClassDiscriminatorMapping From 898e452d5d5d8a0c3175151e244b30c5b4fd6f4a Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Fri, 21 Jun 2024 16:02:45 +0200 Subject: [PATCH 05/38] [Serializer] Use `SUPPORTED_TYPES` in Normalizers when available --- Normalizer/DataUriNormalizer.php | 6 +----- Normalizer/DateTimeNormalizer.php | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/Normalizer/DataUriNormalizer.php b/Normalizer/DataUriNormalizer.php index f8577f84..5ee076be 100644 --- a/Normalizer/DataUriNormalizer.php +++ b/Normalizer/DataUriNormalizer.php @@ -44,11 +44,7 @@ public function __construct(?MimeTypeGuesserInterface $mimeTypeGuesser = null) public function getSupportedTypes(?string $format): array { - return [ - \SplFileInfo::class => true, - \SplFileObject::class => true, - File::class => true, - ]; + return self::SUPPORTED_TYPES; } public function normalize(mixed $object, ?string $format = null, array $context = []): string diff --git a/Normalizer/DateTimeNormalizer.php b/Normalizer/DateTimeNormalizer.php index fc32f6f5..55b2e130 100644 --- a/Normalizer/DateTimeNormalizer.php +++ b/Normalizer/DateTimeNormalizer.php @@ -50,11 +50,7 @@ public function setDefaultContext(array $defaultContext): void public function getSupportedTypes(?string $format): array { - return [ - \DateTimeInterface::class => true, - \DateTimeImmutable::class => true, - \DateTime::class => true, - ]; + return self::SUPPORTED_TYPES; } /** From f78926de1d52c57a27e72f4c3ebd1fe0ef7efc70 Mon Sep 17 00:00:00 2001 From: Dariusz Ruminski Date: Sun, 16 Jun 2024 17:17:26 +0200 Subject: [PATCH 06/38] chore: CS fixes --- Normalizer/BackedEnumNormalizer.php | 2 +- Normalizer/GetSetMethodNormalizer.php | 2 +- Normalizer/ObjectNormalizer.php | 2 +- Tests/Encoder/XmlEncoderTest.php | 12 +++--- .../ConstraintViolationListNormalizerTest.php | 30 +++++++-------- Tests/Normalizer/PropertyNormalizerTest.php | 37 ++++++++++++++----- Tests/Normalizer/UidNormalizerTest.php | 4 +- Tests/SerializerTest.php | 6 +-- 8 files changed, 56 insertions(+), 39 deletions(-) diff --git a/Normalizer/BackedEnumNormalizer.php b/Normalizer/BackedEnumNormalizer.php index 504047bc..3d8e7e7c 100644 --- a/Normalizer/BackedEnumNormalizer.php +++ b/Normalizer/BackedEnumNormalizer.php @@ -29,7 +29,7 @@ final class BackedEnumNormalizer implements NormalizerInterface, DenormalizerInt public function getSupportedTypes(?string $format): array { return [ - \BackedEnum::class => true, + \BackedEnum::class => true, ]; } diff --git a/Normalizer/GetSetMethodNormalizer.php b/Normalizer/GetSetMethodNormalizer.php index 3a398ef3..61fdc3b4 100644 --- a/Normalizer/GetSetMethodNormalizer.php +++ b/Normalizer/GetSetMethodNormalizer.php @@ -164,7 +164,7 @@ protected function isAllowedAttribute($classOrObject, string $attribute, ?string return false; } - $class = \is_object($classOrObject) ? \get_class($classOrObject) : $classOrObject; + $class = \is_object($classOrObject) ? $classOrObject::class : $classOrObject; if (!isset(self::$reflectionCache[$class])) { self::$reflectionCache[$class] = new \ReflectionClass($class); diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index 1b51b729..10fe403e 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -173,7 +173,7 @@ protected function isAllowedAttribute($classOrObject, string $attribute, ?string return false; } - $class = \is_object($classOrObject) ? \get_class($classOrObject) : $classOrObject; + $class = \is_object($classOrObject) ? $classOrObject::class : $classOrObject; if ($context['_read_attributes'] ?? true) { if (!isset(self::$isReadableCache[$class.$attribute])) { diff --git a/Tests/Encoder/XmlEncoderTest.php b/Tests/Encoder/XmlEncoderTest.php index 91163762..5fc8fe98 100644 --- a/Tests/Encoder/XmlEncoderTest.php +++ b/Tests/Encoder/XmlEncoderTest.php @@ -671,8 +671,8 @@ public function testDecodeIgnoreComments() XML; $expected = ['person' => [ - ['firstname' => 'Benjamin', 'lastname' => 'Alexandre'], - ['firstname' => 'Damien', 'lastname' => 'Clay'], + ['firstname' => 'Benjamin', 'lastname' => 'Alexandre'], + ['firstname' => 'Damien', 'lastname' => 'Clay'], ]]; $this->assertEquals($expected, $this->encoder->decode($source, 'xml')); @@ -695,8 +695,8 @@ public function testDecodeIgnoreDocumentType() XML; $expected = ['person' => [ - ['firstname' => 'Benjamin', 'lastname' => 'Alexandre'], - ['firstname' => 'Damien', 'lastname' => 'Clay'], + ['firstname' => 'Benjamin', 'lastname' => 'Alexandre'], + ['firstname' => 'Damien', 'lastname' => 'Clay'], ]]; $this->assertEquals($expected, $this->encoder->decode( $source, @@ -730,8 +730,8 @@ public function testDecodePreserveComments() $this->encoder->setSerializer($serializer); $expected = ['person' => [ - ['firstname' => 'Benjamin', 'lastname' => 'Alexandre', '#comment' => ' This comment should be decoded. '], - ['firstname' => 'Damien', 'lastname' => 'Clay'], + ['firstname' => 'Benjamin', 'lastname' => 'Alexandre', '#comment' => ' This comment should be decoded. '], + ['firstname' => 'Damien', 'lastname' => 'Clay'], ]]; $this->assertEquals($expected, $this->encoder->decode($source, 'xml')); diff --git a/Tests/Normalizer/ConstraintViolationListNormalizerTest.php b/Tests/Normalizer/ConstraintViolationListNormalizerTest.php index bb69392f..1d0afb3c 100644 --- a/Tests/Normalizer/ConstraintViolationListNormalizerTest.php +++ b/Tests/Normalizer/ConstraintViolationListNormalizerTest.php @@ -50,23 +50,23 @@ public function testNormalize() 'detail' => 'd: a 4: 1', 'violations' => [ - [ - 'propertyPath' => 'd', - 'title' => 'a', - 'template' => 'b', - 'type' => 'urn:uuid:f', - 'parameters' => [ - 'value' => 'foo', - ], - ], - [ - 'propertyPath' => '4', - 'title' => '1', - 'template' => '2', - 'type' => 'urn:uuid:6', - 'parameters' => [], + [ + 'propertyPath' => 'd', + 'title' => 'a', + 'template' => 'b', + 'type' => 'urn:uuid:f', + 'parameters' => [ + 'value' => 'foo', ], ], + [ + 'propertyPath' => '4', + 'title' => '1', + 'template' => '2', + 'type' => 'urn:uuid:6', + 'parameters' => [], + ], + ], ]; $this->assertEquals($expected, $this->normalizer->normalize($list)); diff --git a/Tests/Normalizer/PropertyNormalizerTest.php b/Tests/Normalizer/PropertyNormalizerTest.php index b93a7bb9..9773e65b 100644 --- a/Tests/Normalizer/PropertyNormalizerTest.php +++ b/Tests/Normalizer/PropertyNormalizerTest.php @@ -340,12 +340,16 @@ public function testGroupsDenormalizeWithNameConverter() $this->assertEquals( $obj, - $this->normalizer->denormalize([ - 'bar' => null, - 'foo_bar' => '@dunglas', - 'symfony' => '@coopTilleuls', - 'coop_tilleuls' => 'les-tilleuls.coop', - ], GroupDummy::class, null, [PropertyNormalizer::GROUPS => ['name_converter']]) + $this->normalizer->denormalize( + [ + 'bar' => null, + 'foo_bar' => '@dunglas', + 'symfony' => '@coopTilleuls', + 'coop_tilleuls' => 'les-tilleuls.coop', + ], + GroupDummy::class, null, + [PropertyNormalizer::GROUPS => ['name_converter']] + ) ); } @@ -405,13 +409,19 @@ public function testDenormalizeNonExistingAttribute() { $this->assertEquals( new PropertyDummy(), - $this->normalizer->denormalize(['non_existing' => true], PropertyDummy::class) + $this->normalizer->denormalize( + ['non_existing' => true], + PropertyDummy::class + ) ); } public function testDenormalizeShouldIgnoreStaticProperty() { - $obj = $this->normalizer->denormalize(['outOfScope' => true], PropertyDummy::class); + $obj = $this->normalizer->denormalize( + ['outOfScope' => true], + PropertyDummy::class + ); $this->assertEquals(new PropertyDummy(), $obj); $this->assertEquals('out_of_scope', PropertyDummy::$outOfScope); @@ -450,7 +460,8 @@ public function testInheritedPropertiesSupport() public function testMultiDimensionObject() { $normalizer = $this->getDenormalizerForTypeEnforcement(); - $root = $normalizer->denormalize([ + $root = $normalizer->denormalize( + [ 'children' => [[ ['foo' => 'one', 'bar' => 'two'], ['foo' => 'three', 'bar' => 'four'], @@ -535,7 +546,13 @@ public function testDenormalizeWithDiscriminator() $denormalized = new PropertyDiscriminatedDummyTwo(); $denormalized->url = 'url'; - $this->assertEquals($denormalized, $normalizer->denormalize(['type' => 'two', 'url' => 'url'], PropertyDummyInterface::class)); + $this->assertEquals( + $denormalized, + $normalizer->denormalize( + ['type' => 'two', 'url' => 'url'], + PropertyDummyInterface::class + ) + ); } } diff --git a/Tests/Normalizer/UidNormalizerTest.php b/Tests/Normalizer/UidNormalizerTest.php index 1471074a..734b15b4 100644 --- a/Tests/Normalizer/UidNormalizerTest.php +++ b/Tests/Normalizer/UidNormalizerTest.php @@ -47,8 +47,8 @@ public static function normalizeProvider() { $uidFormats = [null, 'canonical', 'base58', 'base32', 'rfc4122']; $data = [ - [ - UuidV1::fromString('9b7541de-6f87-11ea-ab3c-9da9a81562fc'), + [ + UuidV1::fromString('9b7541de-6f87-11ea-ab3c-9da9a81562fc'), '9b7541de-6f87-11ea-ab3c-9da9a81562fc', '9b7541de-6f87-11ea-ab3c-9da9a81562fc', 'LCQS8f2p5SDSiAt9V7ZYnF', diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index 1bf5905a..7d4cbc30 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -1214,7 +1214,7 @@ public function testCollectDenormalizationErrors2(?ClassMetadataFactory $classMe 'useMessageForUser' => false, 'message' => 'The type of the "string" attribute for class "Symfony\\Component\\Serializer\\Tests\\Fixtures\\Php74Full" must be one of "string" ("null" given).', ], - ]; + ]; $this->assertSame($expected, $exceptionsAsArray); } @@ -1464,8 +1464,8 @@ public function testCollectDenormalizationErrorsWithWrongPropertyWithoutConstruc try { $serializer->deserialize('{"get": "POST"}', DummyObjectWithEnumProperty::class, 'json', [ - DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true, - ]); + DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS => true, + ]); } catch (\Throwable $e) { $this->assertInstanceOf(PartialDenormalizationException::class, $e); } From 0ae173cc0ebdd73f15fad4005151e1eeac101df4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Mon, 1 Jul 2024 02:16:34 +0200 Subject: [PATCH 07/38] Remove useless uniqid in tempnam calls --- Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php b/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php index 1826d3dc..40dcb501 100644 --- a/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php +++ b/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php @@ -27,7 +27,7 @@ final class ClassMetadataFactoryCompilerTest extends TestCase protected function setUp(): void { - $this->dumpPath = sys_get_temp_dir().\DIRECTORY_SEPARATOR.'php_serializer_metadata.'.uniqid('CompiledClassMetadataFactory').'.php'; + $this->dumpPath = tempnam(sys_get_temp_dir(), 'sf_serializer_metadata_'); } protected function tearDown(): void From faaab8b87521de97b2471db04569a99d3ff1f466 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Thu, 4 Jul 2024 11:48:29 +0200 Subject: [PATCH 08/38] [Serializer] Remove ArrayDenormalizer::setDenormalizer() --- Normalizer/ArrayDenormalizer.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Normalizer/ArrayDenormalizer.php b/Normalizer/ArrayDenormalizer.php index af5ffb6a..23df0a1e 100644 --- a/Normalizer/ArrayDenormalizer.php +++ b/Normalizer/ArrayDenormalizer.php @@ -29,11 +29,6 @@ class ArrayDenormalizer implements DenormalizerInterface, DenormalizerAwareInter { use DenormalizerAwareTrait; - public function setDenormalizer(DenormalizerInterface $denormalizer): void - { - $this->denormalizer = $denormalizer; - } - public function getSupportedTypes(?string $format): array { return ['object' => null, '*' => false]; From e439ec653b32cd8b75faaf639d1b3d1952ef2c95 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 6 Jul 2024 09:57:16 +0200 Subject: [PATCH 09/38] Update .gitattributes --- .gitattributes | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitattributes b/.gitattributes index 84c7add0..14c3c359 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,3 @@ /Tests export-ignore /phpunit.xml.dist export-ignore -/.gitattributes export-ignore -/.gitignore export-ignore +/.git* export-ignore From f08629456bc65ceb7e25939f35b41433c6a24288 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Wed, 17 Jul 2024 10:14:50 +0200 Subject: [PATCH 10/38] do not use uniqid() for generating dev tool tokens --- Debug/TraceableSerializer.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Debug/TraceableSerializer.php b/Debug/TraceableSerializer.php index 9cf7bad8..f04f3c91 100644 --- a/Debug/TraceableSerializer.php +++ b/Debug/TraceableSerializer.php @@ -37,7 +37,7 @@ public function __construct( public function serialize(mixed $data, string $format, array $context = []): string { - $context[self::DEBUG_TRACE_ID] = $traceId = uniqid('', true); + $context[self::DEBUG_TRACE_ID] = $traceId = bin2hex(random_bytes(4)); $startTime = microtime(true); $result = $this->serializer->serialize($data, $format, $context); @@ -52,7 +52,7 @@ public function serialize(mixed $data, string $format, array $context = []): str public function deserialize(mixed $data, string $type, string $format, array $context = []): mixed { - $context[self::DEBUG_TRACE_ID] = $traceId = uniqid('', true); + $context[self::DEBUG_TRACE_ID] = $traceId = bin2hex(random_bytes(4)); $startTime = microtime(true); $result = $this->serializer->deserialize($data, $type, $format, $context); @@ -67,7 +67,7 @@ public function deserialize(mixed $data, string $type, string $format, array $co public function normalize(mixed $object, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null { - $context[self::DEBUG_TRACE_ID] = $traceId = uniqid('', true); + $context[self::DEBUG_TRACE_ID] = $traceId = bin2hex(random_bytes(4)); $startTime = microtime(true); $result = $this->serializer->normalize($object, $format, $context); @@ -82,7 +82,7 @@ public function normalize(mixed $object, ?string $format = null, array $context public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { - $context[self::DEBUG_TRACE_ID] = $traceId = uniqid('', true); + $context[self::DEBUG_TRACE_ID] = $traceId = bin2hex(random_bytes(4)); $startTime = microtime(true); $result = $this->serializer->denormalize($data, $type, $format, $context); @@ -97,7 +97,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a public function encode(mixed $data, string $format, array $context = []): string { - $context[self::DEBUG_TRACE_ID] = $traceId = uniqid('', true); + $context[self::DEBUG_TRACE_ID] = $traceId = bin2hex(random_bytes(4)); $startTime = microtime(true); $result = $this->serializer->encode($data, $format, $context); @@ -112,7 +112,7 @@ public function encode(mixed $data, string $format, array $context = []): string public function decode(string $data, string $format, array $context = []): mixed { - $context[self::DEBUG_TRACE_ID] = $traceId = uniqid('', true); + $context[self::DEBUG_TRACE_ID] = $traceId = bin2hex(random_bytes(4)); $startTime = microtime(true); $result = $this->serializer->decode($data, $format, $context); From 51611e545131dee861f3c570ce1dbec2cae3c96b Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Wed, 31 Jul 2024 16:13:26 +0200 Subject: [PATCH 11/38] Remove unused code and unnecessary `else` branches --- Encoder/XmlEncoder.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Encoder/XmlEncoder.php b/Encoder/XmlEncoder.php index a57030b1..e1a81638 100644 --- a/Encoder/XmlEncoder.php +++ b/Encoder/XmlEncoder.php @@ -152,9 +152,8 @@ public function decode(string $data, string $format, array $context = []): mixed } $data = array_merge($this->parseXmlAttributes($rootNode, $context), ['#' => $rootNode->nodeValue]); - $data = $this->addXmlNamespaces($data, $rootNode, $dom); - return $data; + return $this->addXmlNamespaces($data, $rootNode, $dom); } public function supportsEncoding(string $format): bool From 1e26038da323d5146a309f1d5d8c3c8dd770f448 Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Thu, 2 May 2024 14:41:09 +0200 Subject: [PATCH 12/38] [Serializer] Rework `XmlEncoderTest` --- Tests/Encoder/XmlEncoderTest.php | 399 +++++++++++++------------------ 1 file changed, 171 insertions(+), 228 deletions(-) diff --git a/Tests/Encoder/XmlEncoderTest.php b/Tests/Encoder/XmlEncoderTest.php index 5fc8fe98..ae96f2c1 100644 --- a/Tests/Encoder/XmlEncoderTest.php +++ b/Tests/Encoder/XmlEncoderTest.php @@ -39,46 +39,39 @@ protected function setUp(): void $this->encoder->setSerializer($serializer); } - public function testEncodeScalar() + /** + * @dataProvider validEncodeProvider + */ + public function testEncode(string $expected, mixed $data, array $context = []) { - $obj = new ScalarDummy(); - $obj->xmlFoo = 'foo'; - - $expected = ''."\n". - 'foo'."\n"; - - $this->assertEquals($expected, $this->encoder->encode($obj, 'xml')); + $this->assertSame($expected, $this->encoder->encode($data, 'xml', $context)); } - public function testEncodeArrayObject() - { - $obj = new \ArrayObject(['foo' => 'bar']); - - $expected = ''."\n". - 'bar'."\n"; - - $this->assertEquals($expected, $this->encoder->encode($obj, 'xml')); - } - - public function testEncodeEmptyArrayObject() + /** + * @return iterable + */ + public static function validEncodeProvider(): iterable { - $obj = new \ArrayObject(); + $obj = new ScalarDummy(); + $obj->xmlFoo = 'foo'; - $expected = ''."\n". - ''."\n"; + yield 'encode scalar' => [ + ''."\n" + .'foo'."\n", + $obj, + ]; - $this->assertEquals($expected, $this->encoder->encode($obj, 'xml')); - } + yield 'encode array object' => [ + ''."\n" + .'bar'."\n", + new \ArrayObject(['foo' => 'bar']), + ]; - public function testDocTypeIsNotAllowed() - { - $this->expectException(UnexpectedValueException::class); - $this->expectExceptionMessage('Document types are not allowed.'); - $this->encoder->decode('', 'foo'); - } + yield 'encode empty array object' => [ + ''."\n".''."\n", + new \ArrayObject(), + ]; - public function testAttributes() - { $obj = new ScalarDummy(); $obj->xmlFoo = [ 'foo-bar' => [ @@ -100,7 +93,9 @@ public function testAttributes() '@sring' => 'a', ], ]; - $expected = ''."\n". + + yield 'attributes' => [ + ''."\n". ''. ''. 'Test'. @@ -110,12 +105,10 @@ public function testAttributes() '3'. 'b'. ''. - ''."\n"; - $this->assertEquals($expected, $this->encoder->encode($obj, 'xml')); - } + ''."\n", + $obj, + ]; - public function testElementNameValid() - { $obj = new ScalarDummy(); $obj->xmlFoo = [ 'foo-bar' => 'a', @@ -123,70 +116,54 @@ public function testElementNameValid() 'föo_bär' => 'a', ]; - $expected = ''."\n". + yield 'element name valid' => [ + ''."\n". ''. 'a'. 'a'. 'a'. - ''."\n"; - - $this->assertEquals($expected, $this->encoder->encode($obj, 'xml')); - } + ''."\n", + $obj, + ]; - public function testEncodeSimpleXML() - { $xml = simplexml_load_string('Peter'); $array = ['person' => $xml]; - $expected = ''."\n". - 'Peter'."\n"; - - $this->assertEquals($expected, $this->encoder->encode($array, 'xml')); - } + yield 'encode SimpleXML' => [ + ''."\n". + 'Peter'."\n", + $array, + ]; - public function testEncodeXmlAttributes() - { $xml = simplexml_load_string('Peter'); $array = ['person' => $xml]; - $expected = ''."\n". - 'Peter'."\n"; - - $context = [ - 'xml_version' => '1.1', - 'xml_encoding' => 'utf-8', - 'xml_standalone' => true, + yield 'encode XML attributes' => [ + ''."\n". + 'Peter'."\n", + $array, + [ + 'xml_version' => '1.1', + 'xml_encoding' => 'utf-8', + 'xml_standalone' => true, + ], ]; - $this->assertSame($expected, $this->encoder->encode($array, 'xml', $context)); - } - - public function testEncodeRemovingEmptyTags() - { - $array = ['person' => ['firstname' => 'Peter', 'lastname' => null]]; - - $expected = ''."\n". - 'Peter'."\n"; - - $context = ['remove_empty_tags' => true]; - - $this->assertSame($expected, $this->encoder->encode($array, 'xml', $context)); - } - - public function testEncodeNotRemovingEmptyTags() - { - $array = ['person' => ['firstname' => 'Peter', 'lastname' => null]]; - - $expected = ''."\n". - 'Peter'."\n"; + yield 'encode remvoing empty tags' => [ + ''."\n". + 'Peter'."\n", + ['person' => ['firstname' => 'Peter', 'lastname' => null]], + ['remove_empty_tags' => true], + ]; - $this->assertSame($expected, $this->encoder->encode($array, 'xml')); - } + yield 'encode not removing empty tags' => [ + ''."\n". + 'Peter'."\n", + ['person' => ['firstname' => 'Peter', 'lastname' => null]], + ]; - public function testContext() - { - $array = ['person' => ['name' => 'George Abitbol', 'age' => null]]; - $expected = <<<'XML' + yield 'encode with context' => [ + <<<'XML' @@ -195,128 +172,139 @@ public function testContext() -XML; +XML, + ['person' => ['name' => 'George Abitbol', 'age' => null]], + [ + 'xml_format_output' => true, + 'save_options' => \LIBXML_NOEMPTYTAG, + ], + ]; - $context = [ - 'xml_format_output' => true, - 'save_options' => \LIBXML_NOEMPTYTAG, + yield 'encode scalar root attributes' => [ + ''."\n". + 'Paul'."\n", + [ + '#' => 'Paul', + '@eye-color' => 'brown', + ], ]; - $this->assertSame($expected, $this->encoder->encode($array, 'xml', $context)); - } + yield 'encode root attributes' => [ + ''."\n". + 'Paul'."\n", + [ + 'firstname' => 'Paul', + '@eye-color' => 'brown', + ], + ]; - public function testEncodeScalarRootAttributes() - { - $array = [ - '#' => 'Paul', - '@eye-color' => 'brown', + yield 'encode with CDATA wrapping with default pattern #1' => [ + ''."\n". + ']]>'."\n", + ['firstname' => 'Paul & Martha '], ]; - $expected = ''."\n". - 'Paul'."\n"; + yield 'encode with CDATA wrapping with default pattern #2' => [ + ''."\n". + 'O\'Donnel'."\n", + ['lastname' => 'O\'Donnel'], + ]; - $this->assertEquals($expected, $this->encoder->encode($array, 'xml')); - } + yield 'encode with CDATA wrapping with default pattern #3' => [ + ''."\n". + ''."\n", + ['firstname' => 'Paul & Martha'], + ]; - public function testEncodeRootAttributes() - { - $array = [ - 'firstname' => 'Paul', - '@eye-color' => 'brown', + yield 'encode with CDATA wrapping with custom pattern #1' => [ + ''."\n". + ']]>'."\n", + ['firstname' => 'Paul & Martha '], + ['cdata_wrapping_pattern' => '/[<>&"\']/'] ]; - $expected = ''."\n". - 'Paul'."\n"; + yield 'encode with CDATA wrapping with custom pattern #2' => [ + ''."\n". + ''."\n", + ['lastname' => 'O\'Donnel'], + ['cdata_wrapping_pattern' => '/[<>&"\']/'] + ]; - $this->assertEquals($expected, $this->encoder->encode($array, 'xml')); - } + yield 'encode with CDATA wrapping with custom pattern #3' => [ + ''."\n". + 'Paul and Martha'."\n", + ['firstname' => 'Paul and Martha'], + ['cdata_wrapping_pattern' => '/[<>&"\']/'] + ]; - /** - * @dataProvider encodeCdataWrappingWithDefaultPattern - */ - public function testEncodeCdataWrappingWithDefaultPattern($input, $expected) - { - $this->assertEquals($expected, $this->encoder->encode($input, 'xml')); - } + yield 'enable CDATA wrapping' => [ + ''."\n". + ']]>'."\n", + ['firstname' => 'Paul & Martha '], + ['cdata_wrapping' => true], + ]; - public static function encodeCdataWrappingWithDefaultPattern() - { - return [ - [ - ['firstname' => 'Paul and Martha'], - ''."\n".'Paul and Martha'."\n", - ], - [ - ['lastname' => 'O\'Donnel'], - ''."\n".'O\'Donnel'."\n", - ], - [ - ['firstname' => 'Paul & Martha '], - ''."\n".']]>'."\n", - ], + yield 'disable CDATA wrapping' => [ + ''."\n". + 'Paul & Martha <or Me>'."\n", + ['firstname' => 'Paul & Martha '], + ['cdata_wrapping' => false], ]; - } - /** - * @dataProvider encodeCdataWrappingWithCustomPattern - */ - public function testEncodeCdataWrappingWithCustomPattern($input, $expected) - { - $this->assertEquals($expected, $this->encoder->encode($input, 'xml', ['cdata_wrapping_pattern' => '/[<>&"\']/'])); - } + yield 'encode scalar with attribute' => [ + ''."\n". + 'Peter'."\n", + ['person' => ['@eye-color' => 'brown', '#' => 'Peter']], + ]; - public static function encodeCdataWrappingWithCustomPattern() - { - return [ - [ - ['firstname' => 'Paul and Martha'], - ''."\n".'Paul and Martha'."\n", - ], - [ - ['lastname' => 'O\'Donnel'], - ''."\n".''."\n", - ], - [ - ['firstname' => 'Paul & Martha '], - ''."\n".']]>'."\n", - ], + yield 'encode' => [ + self::getXmlSource(), + self::getObject(), + ]; + + yield 'encode with namespace' => [ + self::getNamespacedXmlSource(), + self::getNamespacedArray(), ]; } - public function testEnableCdataWrapping() + public function testEncodeSerializerXmlRootNodeNameOption() { + $options = ['xml_root_node_name' => 'test']; + $this->encoder = new XmlEncoder(); + $serializer = new Serializer([], ['xml' => new XmlEncoder()]); + $this->encoder->setSerializer($serializer); + $array = [ - 'firstname' => 'Paul & Martha ', + 'person' => ['@eye-color' => 'brown', '#' => 'Peter'], ]; $expected = ''."\n". - ']]>'."\n"; + 'Peter'."\n"; - $this->assertEquals($expected, $this->encoder->encode($array, 'xml', ['cdata_wrapping' => true])); + $this->assertSame($expected, $serializer->serialize($array, 'xml', $options)); } - public function testDisableCdataWrapping() + public function testEncodeTraversableWhenNormalizable() { - $array = [ - 'firstname' => 'Paul & Martha ', - ]; + $this->encoder = new XmlEncoder(); + $serializer = new Serializer([new CustomNormalizer()], ['xml' => new XmlEncoder()]); + $this->encoder->setSerializer($serializer); - $expected = ''."\n". - 'Paul & Martha <or Me>'."\n"; + $expected = <<<'XML' + +normalizedFoonormalizedBar - $this->assertEquals($expected, $this->encoder->encode($array, 'xml', ['cdata_wrapping' => false])); +XML; + + $this->assertSame($expected, $serializer->serialize(new NormalizableTraversableDummy(), 'xml')); } - public function testEncodeScalarWithAttribute() + public function testDocTypeIsNotAllowed() { - $array = [ - 'person' => ['@eye-color' => 'brown', '#' => 'Peter'], - ]; - - $expected = ''."\n". - 'Peter'."\n"; - - $this->assertEquals($expected, $this->encoder->encode($array, 'xml')); + $this->expectException(UnexpectedValueException::class); + $this->expectExceptionMessage('Document types are not allowed.'); + $this->encoder->decode('', 'foo'); } public function testDecodeScalar() @@ -324,7 +312,7 @@ public function testDecodeScalar() $source = ''."\n". 'foo'."\n"; - $this->assertEquals('foo', $this->encoder->decode($source, 'xml')); + $this->assertSame('foo', $this->encoder->decode($source, 'xml')); } public function testDecodeBigDigitAttributes() @@ -425,54 +413,6 @@ public function testDoesNotTypeCastStringsStartingWith0() $this->assertSame('018', $data['@a']); } - public function testEncode() - { - $source = $this->getXmlSource(); - $obj = $this->getObject(); - - $this->assertEquals($source, $this->encoder->encode($obj, 'xml')); - } - - public function testEncodeWithNamespace() - { - $source = $this->getNamespacedXmlSource(); - $array = $this->getNamespacedArray(); - - $this->assertEquals($source, $this->encoder->encode($array, 'xml')); - } - - public function testEncodeSerializerXmlRootNodeNameOption() - { - $options = ['xml_root_node_name' => 'test']; - $this->encoder = new XmlEncoder(); - $serializer = new Serializer([], ['xml' => new XmlEncoder()]); - $this->encoder->setSerializer($serializer); - - $array = [ - 'person' => ['@eye-color' => 'brown', '#' => 'Peter'], - ]; - - $expected = ''."\n". - 'Peter'."\n"; - - $this->assertEquals($expected, $serializer->serialize($array, 'xml', $options)); - } - - public function testEncodeTraversableWhenNormalizable() - { - $this->encoder = new XmlEncoder(); - $serializer = new Serializer([new CustomNormalizer()], ['xml' => new XmlEncoder()]); - $this->encoder->setSerializer($serializer); - - $expected = <<<'XML' - -normalizedFoonormalizedBar - -XML; - - $this->assertEquals($expected, $serializer->serialize(new NormalizableTraversableDummy(), 'xml')); - } - public function testEncodeException() { $this->expectException(NotEncodableValueException::class); @@ -816,7 +756,7 @@ public function testDecodeEmptyXml() $this->encoder->decode(' ', 'xml'); } - protected function getXmlSource() + protected static function getXmlSource(): string { return ''."\n". ''. @@ -829,7 +769,7 @@ protected function getXmlSource() ''."\n"; } - protected function getNamespacedXmlSource() + protected static function getNamespacedXmlSource(): string { return ''."\n". ''. @@ -842,7 +782,7 @@ protected function getNamespacedXmlSource() ''."\n"; } - protected function getNamespacedArray() + protected static function getNamespacedArray(): array { return [ '@xmlns' => 'http://www.w3.org/2005/Atom', @@ -876,7 +816,10 @@ protected function getNamespacedArray() ]; } - protected function getObject() + /** + * @return Dummy + */ + protected static function getObject(): object { $obj = new Dummy(); $obj->foo = 'foo'; From a6a73cb7ca3c2160bb97494ca881f4278652caf7 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Mon, 5 Aug 2024 09:12:25 +0200 Subject: [PATCH 13/38] Fix multiple CS errors --- Tests/Encoder/XmlEncoderTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/Encoder/XmlEncoderTest.php b/Tests/Encoder/XmlEncoderTest.php index ae96f2c1..31d2ddfc 100644 --- a/Tests/Encoder/XmlEncoderTest.php +++ b/Tests/Encoder/XmlEncoderTest.php @@ -220,21 +220,21 @@ public static function validEncodeProvider(): iterable ''."\n". ']]>'."\n", ['firstname' => 'Paul & Martha '], - ['cdata_wrapping_pattern' => '/[<>&"\']/'] + ['cdata_wrapping_pattern' => '/[<>&"\']/'], ]; yield 'encode with CDATA wrapping with custom pattern #2' => [ ''."\n". ''."\n", ['lastname' => 'O\'Donnel'], - ['cdata_wrapping_pattern' => '/[<>&"\']/'] + ['cdata_wrapping_pattern' => '/[<>&"\']/'], ]; yield 'encode with CDATA wrapping with custom pattern #3' => [ ''."\n". 'Paul and Martha'."\n", ['firstname' => 'Paul and Martha'], - ['cdata_wrapping_pattern' => '/[<>&"\']/'] + ['cdata_wrapping_pattern' => '/[<>&"\']/'], ]; yield 'enable CDATA wrapping' => [ From c2b207d4ad885a52e7f9c845d27e363d2826f0a6 Mon Sep 17 00:00:00 2001 From: Roy de Vos Burchart Date: Thu, 1 Aug 2024 17:21:17 +0200 Subject: [PATCH 14/38] Code style change in `@PER-CS2.0` affecting `@Symfony` (parentheses for anonymous classes) --- Tests/Context/ContextBuilderTraitTest.php | 4 ++-- .../AbstractNormalizerContextBuilderTest.php | 2 +- ...ractObjectNormalizerContextBuilderTest.php | 2 +- .../Factory/CacheMetadataFactoryTest.php | 2 +- Tests/Normalizer/AbstractNormalizerTest.php | 2 +- .../AbstractObjectNormalizerTest.php | 20 +++++++++---------- .../Features/CallbacksTestTrait.php | 4 ++-- Tests/Normalizer/MapDenormalizationTest.php | 2 +- Tests/Normalizer/ObjectNormalizerTest.php | 4 ++-- Tests/SerializerTest.php | 6 +++--- 10 files changed, 24 insertions(+), 24 deletions(-) diff --git a/Tests/Context/ContextBuilderTraitTest.php b/Tests/Context/ContextBuilderTraitTest.php index 17ad231a..edd3937e 100644 --- a/Tests/Context/ContextBuilderTraitTest.php +++ b/Tests/Context/ContextBuilderTraitTest.php @@ -22,7 +22,7 @@ class ContextBuilderTraitTest extends TestCase { public function testWithContext() { - $contextBuilder = new class() implements ContextBuilderInterface { + $contextBuilder = new class implements ContextBuilderInterface { use ContextBuilderTrait; }; @@ -37,7 +37,7 @@ public function testWithContext() public function testWith() { - $contextBuilder = new class() { + $contextBuilder = new class { use ContextBuilderTrait; public function withFoo(string $value): static diff --git a/Tests/Context/Normalizer/AbstractNormalizerContextBuilderTest.php b/Tests/Context/Normalizer/AbstractNormalizerContextBuilderTest.php index 4b8f0cc3..4e92c54d 100644 --- a/Tests/Context/Normalizer/AbstractNormalizerContextBuilderTest.php +++ b/Tests/Context/Normalizer/AbstractNormalizerContextBuilderTest.php @@ -25,7 +25,7 @@ class AbstractNormalizerContextBuilderTest extends TestCase protected function setUp(): void { - $this->contextBuilder = new class() extends AbstractNormalizerContextBuilder {}; + $this->contextBuilder = new class extends AbstractNormalizerContextBuilder {}; } /** diff --git a/Tests/Context/Normalizer/AbstractObjectNormalizerContextBuilderTest.php b/Tests/Context/Normalizer/AbstractObjectNormalizerContextBuilderTest.php index 410f2972..c1376011 100644 --- a/Tests/Context/Normalizer/AbstractObjectNormalizerContextBuilderTest.php +++ b/Tests/Context/Normalizer/AbstractObjectNormalizerContextBuilderTest.php @@ -25,7 +25,7 @@ class AbstractObjectNormalizerContextBuilderTest extends TestCase protected function setUp(): void { - $this->contextBuilder = new class() extends AbstractObjectNormalizerContextBuilder {}; + $this->contextBuilder = new class extends AbstractObjectNormalizerContextBuilder {}; } /** diff --git a/Tests/Mapping/Factory/CacheMetadataFactoryTest.php b/Tests/Mapping/Factory/CacheMetadataFactoryTest.php index 6db0b95a..e18dd707 100644 --- a/Tests/Mapping/Factory/CacheMetadataFactoryTest.php +++ b/Tests/Mapping/Factory/CacheMetadataFactoryTest.php @@ -68,7 +68,7 @@ public function testInvalidClassThrowsException() public function testAnonymousClass() { - $anonymousObject = new class() { + $anonymousObject = new class { }; $metadata = new ClassMetadata($anonymousObject::class); diff --git a/Tests/Normalizer/AbstractNormalizerTest.php b/Tests/Normalizer/AbstractNormalizerTest.php index 3108fe3c..74ca3d6d 100644 --- a/Tests/Normalizer/AbstractNormalizerTest.php +++ b/Tests/Normalizer/AbstractNormalizerTest.php @@ -271,7 +271,7 @@ public function testVariadicConstructorDenormalization() public static function getNormalizerWithCustomNameConverter() { $extractor = new PhpDocExtractor(); - $nameConverter = new class() implements NameConverterInterface { + $nameConverter = new class implements NameConverterInterface { public function normalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string { return ucfirst($propertyName); diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index e1b1031d..685641ff 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -526,7 +526,7 @@ public function testDenormalizeWithDiscriminatorMapUsesCorrectClassname() { $factory = new ClassMetadataFactory(new AttributeLoader()); - $loaderMock = new class() implements ClassMetadataFactoryInterface { + $loaderMock = new class implements ClassMetadataFactoryInterface { public function getMetadataFor($value): ClassMetadataInterface { if (AbstractDummy::class === $value) { @@ -561,7 +561,7 @@ public function testDenormalizeWithDiscriminatorMapAndObjectToPopulateUsesCorrec { $factory = new ClassMetadataFactory(new AttributeLoader()); - $loaderMock = new class() implements ClassMetadataFactoryInterface { + $loaderMock = new class implements ClassMetadataFactoryInterface { public function getMetadataFor($value): ClassMetadataInterface { if (AbstractDummy::class === $value) { @@ -614,7 +614,7 @@ public function hasMetadataFor($value): bool public function testDenormalizeWithNestedDiscriminatorMap() { - $classDiscriminatorResolver = new class() implements ClassDiscriminatorResolverInterface { + $classDiscriminatorResolver = new class implements ClassDiscriminatorResolverInterface { public function getMappingForClass(string $class): ?ClassDiscriminatorMapping { return match ($class) { @@ -867,7 +867,7 @@ public function testDenormalizeWithNumberAsSerializedNameAndNoArrayReindex() '99' => 'baz', ]; - $obj = new class() { + $obj = new class { #[SerializedName('1')] public $foo; @@ -891,7 +891,7 @@ public function testDenormalizeWithCorrectOrderOfAttributeAndProperty() ], ]; - $obj = new class() { + $obj = new class { #[SerializedPath('[data][id]')] public $id; }; @@ -902,7 +902,7 @@ public function testDenormalizeWithCorrectOrderOfAttributeAndProperty() public function testNormalizeBasedOnAllowedAttributes() { - $normalizer = new class() extends AbstractObjectNormalizer { + $normalizer = new class extends AbstractObjectNormalizer { public function getSupportedTypes(?string $format): array { return ['*' => false]; @@ -982,7 +982,7 @@ public function testProvidingContextCacheKeyGeneratesSameChildContextCacheKey() $foobar->bar = 'bar'; $foobar->baz = 'baz'; - $normalizer = new class() extends AbstractObjectNormalizerDummy { + $normalizer = new class extends AbstractObjectNormalizerDummy { public $childContextCacheKey; protected function extractAttributes(object $object, ?string $format = null, array $context = []): array @@ -1022,7 +1022,7 @@ public function testChildContextKeepsOriginalContextCacheKey() $foobar->bar = 'bar'; $foobar->baz = 'baz'; - $normalizer = new class() extends AbstractObjectNormalizerDummy { + $normalizer = new class extends AbstractObjectNormalizerDummy { public $childContextCacheKey; protected function extractAttributes(object $object, ?string $format = null, array $context = []): array @@ -1057,7 +1057,7 @@ public function testChildContextCacheKeyStaysFalseWhenOriginalCacheKeyIsFalse() $foobar->bar = 'bar'; $foobar->baz = 'baz'; - $normalizer = new class() extends AbstractObjectNormalizerDummy { + $normalizer = new class extends AbstractObjectNormalizerDummy { public $childContextCacheKey; protected function extractAttributes(object $object, ?string $format = null, array $context = []): array @@ -1087,7 +1087,7 @@ protected function createChildContext(array $parentContext, string $attribute, ? public function testDenormalizeXmlScalar() { - $normalizer = new class() extends AbstractObjectNormalizer { + $normalizer = new class extends AbstractObjectNormalizer { public function __construct() { parent::__construct(null, new MetadataAwareNameConverter(new ClassMetadataFactory(new AttributeLoader()))); diff --git a/Tests/Normalizer/Features/CallbacksTestTrait.php b/Tests/Normalizer/Features/CallbacksTestTrait.php index 09e2f886..3a9191ae 100644 --- a/Tests/Normalizer/Features/CallbacksTestTrait.php +++ b/Tests/Normalizer/Features/CallbacksTestTrait.php @@ -59,7 +59,7 @@ public function testNormalizeCallbacksWithNoConstructorArgument($callbacks, $val { $normalizer = $this->getNormalizerForCallbacksWithPropertyTypeExtractor(); - $obj = new class() extends CallbacksObject { + $obj = new class extends CallbacksObject { public function __construct() { } @@ -101,7 +101,7 @@ public function testDenormalizeCallbacksWithNoConstructorArgument($callbacks, $v { $normalizer = $this->getNormalizerForCallbacksWithPropertyTypeExtractor(); - $objWithNoConstructorArgument = new class() extends CallbacksObject { + $objWithNoConstructorArgument = new class extends CallbacksObject { public function __construct() { } diff --git a/Tests/Normalizer/MapDenormalizationTest.php b/Tests/Normalizer/MapDenormalizationTest.php index ea451595..75ecbccb 100644 --- a/Tests/Normalizer/MapDenormalizationTest.php +++ b/Tests/Normalizer/MapDenormalizationTest.php @@ -187,7 +187,7 @@ public function testNullableAbstractObject() private function getSerializer() { - $loaderMock = new class() implements ClassMetadataFactoryInterface { + $loaderMock = new class implements ClassMetadataFactoryInterface { public function getMetadataFor($value): ClassMetadataInterface { if (AbstractDummyValue::class === $value) { diff --git a/Tests/Normalizer/ObjectNormalizerTest.php b/Tests/Normalizer/ObjectNormalizerTest.php index 8a979e23..323eba00 100644 --- a/Tests/Normalizer/ObjectNormalizerTest.php +++ b/Tests/Normalizer/ObjectNormalizerTest.php @@ -749,7 +749,7 @@ public function testDoesntHaveIssuesWithUnionConstTypes() $normalizer = new ObjectNormalizer(null, null, null, $extractor); $serializer = new Serializer([new ArrayDenormalizer(), new DateTimeNormalizer(), $normalizer]); - $this->assertSame('bar', $serializer->denormalize(['foo' => 'bar'], (new class() { + $this->assertSame('bar', $serializer->denormalize(['foo' => 'bar'], (new class { /** @var self::*|null */ public $foo; })::class)->foo); @@ -784,7 +784,7 @@ public function testDenormalizeFalsePseudoType() public function testAdvancedNameConverter() { - $nameConverter = new class() implements AdvancedNameConverterInterface { + $nameConverter = new class implements AdvancedNameConverterInterface { public function normalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string { return \sprintf('%s-%s-%s-%s', $propertyName, $class, $format, $context['foo']); diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index 7d4cbc30..a600f072 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -426,7 +426,7 @@ public function testDeserializeAndSerializeAbstractObjectsWithTheClassMetadataDi $example = new AbstractDummyFirstChild('foo-value', 'bar-value'); $example->setQuux(new DummyFirstChildQuux('quux')); - $loaderMock = new class() implements ClassMetadataFactoryInterface { + $loaderMock = new class implements ClassMetadataFactoryInterface { public function getMetadataFor($value): ClassMetadataInterface { if (AbstractDummy::class === $value) { @@ -607,10 +607,10 @@ public static function provideObjectOrCollectionTests() $data['c2'] = new \ArrayObject(['nested' => new \ArrayObject(['k' => 'v'])]); $data['d1'] = new \ArrayObject(['nested' => []]); $data['d2'] = new \ArrayObject(['nested' => ['k' => 'v']]); - $data['e1'] = new class() { + $data['e1'] = new class { public $map = []; }; - $data['e2'] = new class() { + $data['e2'] = new class { public $map = ['k' => 'v']; }; $data['f1'] = new class(new \ArrayObject()) { From a06245d66340b8ba54c836787e19a6b2f1d681b2 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Thu, 25 Jul 2024 11:31:45 +0200 Subject: [PATCH 15/38] [Serializer][Translation] Deprecate passing a non-empty CSV escape char --- CHANGELOG.md | 6 ++++ Context/Encoder/CsvEncoderContextBuilder.php | 4 +++ Encoder/CsvEncoder.php | 7 +++++ .../Encoder/CsvEncoderContextBuilderTest.php | 24 +++++++++++--- Tests/Encoder/CsvEncoderTest.php | 31 +++++++++++++++---- 5 files changed, 61 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3118834d..8473d9e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +7.2 +--- + + * Deprecate the `csv_escape_char` context option of `CsvEncoder` and the `CsvEncoder::ESCAPE_CHAR_KEY` constant + * Deprecate `CsvEncoderContextBuilder::withEscapeChar()` method + 7.1 --- diff --git a/Context/Encoder/CsvEncoderContextBuilder.php b/Context/Encoder/CsvEncoderContextBuilder.php index f449bc31..9f0d6da6 100644 --- a/Context/Encoder/CsvEncoderContextBuilder.php +++ b/Context/Encoder/CsvEncoderContextBuilder.php @@ -62,10 +62,14 @@ public function withEnclosure(?string $enclosure): static * * Must be empty or a single character. * + * @deprecated since Symfony 7.2, to be removed in 8.0 + * * @throws InvalidArgumentException */ public function withEscapeChar(?string $escapeChar): static { + trigger_deprecation('symfony/serializer', '7.2', 'The "%s" method is deprecated. It will be removed in 8.0.', __METHOD__); + if (null !== $escapeChar && \strlen($escapeChar) > 1) { throw new InvalidArgumentException(\sprintf('The "%s" escape character must be empty or a single character.', $escapeChar)); } diff --git a/Encoder/CsvEncoder.php b/Encoder/CsvEncoder.php index 462bd663..3902b561 100644 --- a/Encoder/CsvEncoder.php +++ b/Encoder/CsvEncoder.php @@ -25,6 +25,9 @@ class CsvEncoder implements EncoderInterface, DecoderInterface public const FORMAT = 'csv'; public const DELIMITER_KEY = 'csv_delimiter'; public const ENCLOSURE_KEY = 'csv_enclosure'; + /** + * @deprecated since Symfony 7.2, to be removed in 8.0 + */ public const ESCAPE_CHAR_KEY = 'csv_escape_char'; public const KEY_SEPARATOR_KEY = 'csv_key_separator'; public const HEADERS_KEY = 'csv_headers'; @@ -53,6 +56,10 @@ class CsvEncoder implements EncoderInterface, DecoderInterface public function __construct(array $defaultContext = []) { + if (\array_key_exists(self::ESCAPE_CHAR_KEY, $defaultContext)) { + trigger_deprecation('symfony/serializer', '7.2', 'Setting the "csv_escape_char" option is deprecated. The option will be removed in 8.0.'); + } + $this->defaultContext = array_merge($this->defaultContext, $defaultContext); } diff --git a/Tests/Context/Encoder/CsvEncoderContextBuilderTest.php b/Tests/Context/Encoder/CsvEncoderContextBuilderTest.php index c71d41b6..bcaaf2a8 100644 --- a/Tests/Context/Encoder/CsvEncoderContextBuilderTest.php +++ b/Tests/Context/Encoder/CsvEncoderContextBuilderTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Serializer\Tests\Context\Encoder; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Serializer\Context\Encoder\CsvEncoderContextBuilder; use Symfony\Component\Serializer\Encoder\CsvEncoder; use Symfony\Component\Serializer\Exception\InvalidArgumentException; @@ -21,6 +22,8 @@ */ class CsvEncoderContextBuilderTest extends TestCase { + use ExpectDeprecationTrait; + private CsvEncoderContextBuilder $contextBuilder; protected function setUp(): void @@ -38,7 +41,6 @@ public function testWithers(array $values) $context = $this->contextBuilder ->withDelimiter($values[CsvEncoder::DELIMITER_KEY]) ->withEnclosure($values[CsvEncoder::ENCLOSURE_KEY]) - ->withEscapeChar($values[CsvEncoder::ESCAPE_CHAR_KEY]) ->withKeySeparator($values[CsvEncoder::KEY_SEPARATOR_KEY]) ->withHeaders($values[CsvEncoder::HEADERS_KEY]) ->withEscapedFormulas($values[CsvEncoder::ESCAPE_FORMULAS_KEY]) @@ -59,7 +61,6 @@ public static function withersDataProvider(): iterable yield 'With values' => [[ CsvEncoder::DELIMITER_KEY => ';', CsvEncoder::ENCLOSURE_KEY => '"', - CsvEncoder::ESCAPE_CHAR_KEY => '\\', CsvEncoder::KEY_SEPARATOR_KEY => '_', CsvEncoder::HEADERS_KEY => ['h1', 'h2'], CsvEncoder::ESCAPE_FORMULAS_KEY => true, @@ -72,7 +73,6 @@ public static function withersDataProvider(): iterable yield 'With null values' => [[ CsvEncoder::DELIMITER_KEY => null, CsvEncoder::ENCLOSURE_KEY => null, - CsvEncoder::ESCAPE_CHAR_KEY => null, CsvEncoder::KEY_SEPARATOR_KEY => null, CsvEncoder::HEADERS_KEY => null, CsvEncoder::ESCAPE_FORMULAS_KEY => null, @@ -88,7 +88,6 @@ public function testWithersWithoutValue() $context = $this->contextBuilder ->withDelimiter(null) ->withEnclosure(null) - ->withEscapeChar(null) ->withKeySeparator(null) ->withHeaders(null) ->withEscapedFormulas(null) @@ -101,7 +100,6 @@ public function testWithersWithoutValue() $this->assertSame([ CsvEncoder::DELIMITER_KEY => null, CsvEncoder::ENCLOSURE_KEY => null, - CsvEncoder::ESCAPE_CHAR_KEY => null, CsvEncoder::KEY_SEPARATOR_KEY => null, CsvEncoder::HEADERS_KEY => null, CsvEncoder::ESCAPE_FORMULAS_KEY => null, @@ -124,9 +122,25 @@ public function testCannotSetMultipleBytesAsEnclosure() $this->contextBuilder->withEnclosure('ọ'); } + /** + * @group legacy + */ public function testCannotSetMultipleBytesAsEscapeChar() { + $this->expectDeprecation('Since symfony/serializer 7.2: The "Symfony\Component\Serializer\Context\Encoder\CsvEncoderContextBuilder::withEscapeChar" method is deprecated. It will be removed in 8.0.'); + $this->expectException(InvalidArgumentException::class); $this->contextBuilder->withEscapeChar('ọ'); } + + /** + * @group legacy + */ + public function testWithEscapeCharIsDeprecated() + { + $this->expectDeprecation('Since symfony/serializer 7.2: The "Symfony\Component\Serializer\Context\Encoder\CsvEncoderContextBuilder::withEscapeChar" method is deprecated. It will be removed in 8.0.'); + $context = $this->contextBuilder->withEscapeChar('\\'); + + $this->assertSame(['csv_escape_char' => '\\'], $context->toArray()); + } } diff --git a/Tests/Encoder/CsvEncoderTest.php b/Tests/Encoder/CsvEncoderTest.php index f487356c..e250d1c6 100644 --- a/Tests/Encoder/CsvEncoderTest.php +++ b/Tests/Encoder/CsvEncoderTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Serializer\Tests\Encoder; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\Serializer\Encoder\CsvEncoder; use Symfony\Component\Serializer\Exception\UnexpectedValueException; @@ -20,6 +21,8 @@ */ class CsvEncoderTest extends TestCase { + use ExpectDeprecationTrait; + private CsvEncoder $encoder; protected function setUp(): void @@ -149,7 +152,6 @@ public function testEncodeCustomSettings() $this->encoder = new CsvEncoder([ CsvEncoder::DELIMITER_KEY => ';', CsvEncoder::ENCLOSURE_KEY => "'", - CsvEncoder::ESCAPE_CHAR_KEY => \PHP_VERSION_ID < 70400 ? '|' : '', CsvEncoder::KEY_SEPARATOR_KEY => '-', ]); @@ -175,7 +177,6 @@ public function testEncodeCustomSettingsPassedInContext() , $this->encoder->encode($value, 'csv', [ CsvEncoder::DELIMITER_KEY => ';', CsvEncoder::ENCLOSURE_KEY => "'", - CsvEncoder::ESCAPE_CHAR_KEY => \PHP_VERSION_ID < 70400 ? '|' : '', CsvEncoder::KEY_SEPARATOR_KEY => '-', ])); } @@ -185,7 +186,6 @@ public function testEncodeCustomSettingsPassedInConstructor() $encoder = new CsvEncoder([ CsvEncoder::DELIMITER_KEY => ';', CsvEncoder::ENCLOSURE_KEY => "'", - CsvEncoder::ESCAPE_CHAR_KEY => \PHP_VERSION_ID < 70400 ? '|' : '', CsvEncoder::KEY_SEPARATOR_KEY => '-', ]); $value = ['a' => 'he\'llo', 'c' => ['d' => 'foo']]; @@ -574,7 +574,6 @@ public function testDecodeCustomSettings() $this->encoder = new CsvEncoder([ CsvEncoder::DELIMITER_KEY => ';', CsvEncoder::ENCLOSURE_KEY => "'", - CsvEncoder::ESCAPE_CHAR_KEY => \PHP_VERSION_ID < 70400 ? '|' : '', CsvEncoder::KEY_SEPARATOR_KEY => '-', ]); @@ -596,7 +595,6 @@ public function testDecodeCustomSettingsPassedInContext() , 'csv', [ CsvEncoder::DELIMITER_KEY => ';', CsvEncoder::ENCLOSURE_KEY => "'", - CsvEncoder::ESCAPE_CHAR_KEY => \PHP_VERSION_ID < 70400 ? '|' : '', CsvEncoder::KEY_SEPARATOR_KEY => '-', ])); } @@ -606,7 +604,6 @@ public function testDecodeCustomSettingsPassedInConstructor() $encoder = new CsvEncoder([ CsvEncoder::DELIMITER_KEY => ';', CsvEncoder::ENCLOSURE_KEY => "'", - CsvEncoder::ESCAPE_CHAR_KEY => \PHP_VERSION_ID < 70400 ? '|' : '', CsvEncoder::KEY_SEPARATOR_KEY => '-', CsvEncoder::AS_COLLECTION_KEY => true, // Can be removed in 5.0 ]); @@ -710,4 +707,26 @@ public function testEndOfLinePassedInConstructor() $encoder = new CsvEncoder([CsvEncoder::END_OF_LINE => "\r\n"]); $this->assertSame("foo,bar\r\nhello,test\r\n", $encoder->encode($value, 'csv')); } + + /** + * @group legacy + */ + public function testPassingNonEmptyEscapeCharIsDeprecated() + { + $this->expectDeprecation('Since symfony/serializer 7.2: Setting the "csv_escape_char" option is deprecated. The option will be removed in 8.0.'); + $encoder = new CsvEncoder(['csv_escape_char' => '@']); + + $this->assertSame( + [[ + 'A, B@"' => 'D', + 'C' => 'E', + ]], + $encoder->decode(<<<'CSV' + "A, B@"", "C" + "D", "E" + CSV, + 'csv' + ) + ); + } } From d0c1cd1603f2509a138a541c9b0005bb722466a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Thu, 22 Aug 2024 10:19:16 +0200 Subject: [PATCH 16/38] [Serializer] more precise type for CamelCaseToSnakeCaseNameConverter::$attributes --- NameConverter/CamelCaseToSnakeCaseNameConverter.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NameConverter/CamelCaseToSnakeCaseNameConverter.php b/NameConverter/CamelCaseToSnakeCaseNameConverter.php index 47d69d3a..033ec94b 100644 --- a/NameConverter/CamelCaseToSnakeCaseNameConverter.php +++ b/NameConverter/CamelCaseToSnakeCaseNameConverter.php @@ -27,8 +27,8 @@ class CamelCaseToSnakeCaseNameConverter implements NameConverterInterface public const REQUIRE_SNAKE_CASE_PROPERTIES = 'require_snake_case_properties'; /** - * @param array|null $attributes The list of attributes to rename or null for all attributes - * @param bool $lowerCamelCase Use lowerCamelCase style + * @param string[]|null $attributes The list of attributes to rename or null for all attributes + * @param bool $lowerCamelCase Use lowerCamelCase style */ public function __construct( private ?array $attributes = null, From e31367ee8f489d0180023525624883ed87e30d00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Thu, 22 Aug 2024 11:18:18 +0200 Subject: [PATCH 17/38] [Serializer] Add SnakeCaseToCamelCaseNameConverter --- CHANGELOG.md | 1 + .../SnakeCaseToCamelCaseNameConverter.php | 78 +++++++++++++++++++ .../SnakeCaseToCamelCaseNameConverterTest.php | 64 +++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 NameConverter/SnakeCaseToCamelCaseNameConverter.php create mode 100644 Tests/NameConverter/SnakeCaseToCamelCaseNameConverterTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 8473d9e9..bb31b565 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Deprecate the `csv_escape_char` context option of `CsvEncoder` and the `CsvEncoder::ESCAPE_CHAR_KEY` constant * Deprecate `CsvEncoderContextBuilder::withEscapeChar()` method + * Add `SnakeCaseToCamelCaseNameConverter` 7.1 --- diff --git a/NameConverter/SnakeCaseToCamelCaseNameConverter.php b/NameConverter/SnakeCaseToCamelCaseNameConverter.php new file mode 100644 index 00000000..cb93d3e9 --- /dev/null +++ b/NameConverter/SnakeCaseToCamelCaseNameConverter.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\NameConverter; + +use Symfony\Component\Serializer\Exception\UnexpectedPropertyException; + +/** + * Underscore to camelCase name converter. + * + * @author Kévin Dunglas + */ +final readonly class SnakeCaseToCamelCaseNameConverter implements NameConverterInterface +{ + /** + * Require all properties to be written in camelCase. + */ + public const REQUIRE_CAMEL_CASE_PROPERTIES = 'require_camel_case_properties'; + + /** + * @param string[]|null $attributes The list of attributes to rename or null for all attributes + * @param bool $lowerCamelCase Use lowerCamelCase style + */ + public function __construct( + private ?array $attributes = null, + private bool $lowerCamelCase = true, + ) { + } + + /** + * @param class-string|null $class + * @param array $context + */ + public function normalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string + { + if (null !== $this->attributes && !\in_array($propertyName, $this->attributes, true)) { + return $propertyName; + } + + $camelCasedName = preg_replace_callback( + '/(^|_|\.)+(.)/', + fn ($match) => ('.' === $match[1] ? '_' : '').strtoupper($match[2]), + $propertyName + ); + + if ($this->lowerCamelCase) { + $camelCasedName = lcfirst($camelCasedName); + } + + return $camelCasedName; + } + + /** + * @param class-string|null $class + * @param array $context + */ + public function denormalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string + { + if (($context[self::REQUIRE_CAMEL_CASE_PROPERTIES] ?? false) && $propertyName !== $this->normalize($propertyName, $class, $format, $context)) { + throw new UnexpectedPropertyException($propertyName); + } + + $snakeCased = strtolower(preg_replace('/[A-Z]/', '_\\0', lcfirst($propertyName))); + if (null === $this->attributes || \in_array($snakeCased, $this->attributes, true)) { + return $snakeCased; + } + + return $propertyName; + } +} diff --git a/Tests/NameConverter/SnakeCaseToCamelCaseNameConverterTest.php b/Tests/NameConverter/SnakeCaseToCamelCaseNameConverterTest.php new file mode 100644 index 00000000..2d2799e2 --- /dev/null +++ b/Tests/NameConverter/SnakeCaseToCamelCaseNameConverterTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\NameConverter; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Exception\UnexpectedPropertyException; +use Symfony\Component\Serializer\NameConverter\NameConverterInterface; +use Symfony\Component\Serializer\NameConverter\SnakeCaseToCamelCaseNameConverter; + +/** + * @author Kévin Dunglas + * @author Aurélien Pillevesse + */ +class SnakeCaseToCamelCaseNameConverterTest extends TestCase +{ + public function testInterface() + { + $attributeMetadata = new SnakeCaseToCamelCaseNameConverter(); + $this->assertInstanceOf(NameConverterInterface::class, $attributeMetadata); + } + + /** + * @dataProvider Symfony\Component\Serializer\Tests\NameConverter\CamelCaseToSnakeCaseNameConverterTest::attributeProvider + */ + public function testNormalize($underscored, $camelCased, $useLowerCamelCase) + { + $nameConverter = new SnakeCaseToCamelCaseNameConverter(null, $useLowerCamelCase); + $this->assertEquals($camelCased, $nameConverter->normalize($underscored)); + } + + /** + * @dataProvider Symfony\Component\Serializer\Tests\NameConverter\CamelCaseToSnakeCaseNameConverterTest::attributeProvider + */ + public function testDenormalize($underscored, $camelCased, $useLowerCamelCase) + { + $nameConverter = new SnakeCaseToCamelCaseNameConverter(null, $useLowerCamelCase); + $this->assertEquals($underscored, $nameConverter->denormalize($camelCased)); + } + + public function testDenormalizeWithContext() + { + $nameConverter = new SnakeCaseToCamelCaseNameConverter(null, true); + $denormalizedValue = $nameConverter->denormalize('lastName', null, null, [SnakeCaseToCamelCaseNameConverter::REQUIRE_CAMEL_CASE_PROPERTIES => true]); + + $this->assertSame('last_name', $denormalizedValue); + } + + public function testErrorDenormalizeWithContext() + { + $nameConverter = new SnakeCaseToCamelCaseNameConverter(null, true); + + $this->expectException(UnexpectedPropertyException::class); + $nameConverter->denormalize('last_name', null, null, [SnakeCaseToCamelCaseNameConverter::REQUIRE_CAMEL_CASE_PROPERTIES => true]); + } +} From bce9daacdce95cf5e774b17b10f9d8153c6e947d Mon Sep 17 00:00:00 2001 From: Attila Szeremi Date: Mon, 22 Jul 2024 10:09:14 +0200 Subject: [PATCH 18/38] [Serializer] Support subclasses of `DateTime` and `DateTimeImmutable` --- CHANGELOG.md | 1 + Normalizer/DateTimeNormalizer.php | 2 +- Tests/Normalizer/DateTimeNormalizerTest.php | 13 +++++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3118834d..232c8eda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ CHANGELOG * Add `CamelCaseToSnakeCaseNameConverter::REQUIRE_SNAKE_CASE_PROPERTIES` context option * Deprecate `AbstractNormalizerContextBuilder::withDefaultContructorArguments(?array $defaultContructorArguments)`, use `withDefaultConstructorArguments(?array $defaultConstructorArguments)` instead (note the missing `s` character in Contructor word in deprecated method) * Add `XmlEncoder::CDATA_WRAPPING_PATTERN` context option + * Support subclasses of `\DateTime` and `\DateTimeImmutable` for denormalization 7.0 --- diff --git a/Normalizer/DateTimeNormalizer.php b/Normalizer/DateTimeNormalizer.php index 55b2e130..dfc498c1 100644 --- a/Normalizer/DateTimeNormalizer.php +++ b/Normalizer/DateTimeNormalizer.php @@ -138,7 +138,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool { - return isset(self::SUPPORTED_TYPES[$type]); + return is_a($type, \DateTimeInterface::class, true); } /** diff --git a/Tests/Normalizer/DateTimeNormalizerTest.php b/Tests/Normalizer/DateTimeNormalizerTest.php index 5dbf36fb..a744325d 100644 --- a/Tests/Normalizer/DateTimeNormalizerTest.php +++ b/Tests/Normalizer/DateTimeNormalizerTest.php @@ -232,6 +232,8 @@ public function testSupportsDenormalization() $this->assertTrue($this->normalizer->supportsDenormalization('2016-01-01T00:00:00+00:00', \DateTimeInterface::class)); $this->assertTrue($this->normalizer->supportsDenormalization('2016-01-01T00:00:00+00:00', \DateTime::class)); $this->assertTrue($this->normalizer->supportsDenormalization('2016-01-01T00:00:00+00:00', \DateTimeImmutable::class)); + $this->assertTrue($this->normalizer->supportsDenormalization('2016-01-01T00:00:00+00:00', DateTimeImmutableChild::class)); + $this->assertTrue($this->normalizer->supportsDenormalization('2016-01-01T00:00:00+00:00', DateTimeChild::class)); $this->assertFalse($this->normalizer->supportsDenormalization('foo', 'Bar')); } @@ -241,6 +243,10 @@ public function testDenormalize() $this->assertEquals(new \DateTimeImmutable('2016/01/01', new \DateTimeZone('UTC')), $this->normalizer->denormalize('2016-01-01T00:00:00+00:00', \DateTimeImmutable::class)); $this->assertEquals(new \DateTime('2016/01/01', new \DateTimeZone('UTC')), $this->normalizer->denormalize('2016-01-01T00:00:00+00:00', \DateTime::class)); $this->assertEquals(new \DateTime('2016/01/01', new \DateTimeZone('UTC')), $this->normalizer->denormalize(' 2016-01-01T00:00:00+00:00 ', \DateTime::class)); + $this->assertEquals(new DateTimeImmutableChild('2016/01/01', new \DateTimeZone('UTC')), $this->normalizer->denormalize('2016-01-01T00:00:00+00:00', DateTimeImmutableChild::class)); + $this->assertEquals(new DateTimeImmutableChild('2016/01/01', new \DateTimeZone('UTC')), $this->normalizer->denormalize('2016-01-01T00:00:00+00:00', DateTimeImmutableChild::class)); + $this->assertEquals(new DateTimeChild('2016/01/01', new \DateTimeZone('UTC')), $this->normalizer->denormalize('2016-01-01T00:00:00+00:00', DateTimeChild::class)); + $this->assertEquals(new DateTimeChild('2016/01/01', new \DateTimeZone('UTC')), $this->normalizer->denormalize(' 2016-01-01T00:00:00+00:00 ', DateTimeChild::class)); $this->assertEquals(new \DateTimeImmutable('2023-05-06T17:35:34.000000+0000', new \DateTimeZone('UTC')), $this->normalizer->denormalize(1683394534, \DateTimeImmutable::class, null, [DateTimeNormalizer::FORMAT_KEY => 'U'])); $this->assertEquals(new \DateTimeImmutable('2023-05-06T17:35:34.123400+0000', new \DateTimeZone('UTC')), $this->normalizer->denormalize(1683394534.1234, \DateTimeImmutable::class, null, [DateTimeNormalizer::FORMAT_KEY => 'U.u'])); } @@ -387,3 +393,10 @@ public function testDenormalizeFormatMismatchThrowsException() $this->normalizer->denormalize('2016-01-01T00:00:00+00:00', \DateTimeInterface::class, null, [DateTimeNormalizer::FORMAT_KEY => 'Y-m-d|']); } } + +class DateTimeChild extends \DateTime +{ +} +class DateTimeImmutableChild extends \DateTimeImmutable +{ +} From a79b424babd6f328453610866bf12ffc353e26dc Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 30 Aug 2024 17:56:36 +0200 Subject: [PATCH 19/38] CS fixes --- CHANGELOG.md | 2 +- Tests/Normalizer/DateTimeNormalizerTest.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40d486a2..cb0466ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ CHANGELOG * Deprecate the `csv_escape_char` context option of `CsvEncoder` and the `CsvEncoder::ESCAPE_CHAR_KEY` constant * Deprecate `CsvEncoderContextBuilder::withEscapeChar()` method * Add `SnakeCaseToCamelCaseNameConverter` + * Support subclasses of `\DateTime` and `\DateTimeImmutable` for denormalization 7.1 --- @@ -18,7 +19,6 @@ CHANGELOG * Add `CamelCaseToSnakeCaseNameConverter::REQUIRE_SNAKE_CASE_PROPERTIES` context option * Deprecate `AbstractNormalizerContextBuilder::withDefaultContructorArguments(?array $defaultContructorArguments)`, use `withDefaultConstructorArguments(?array $defaultConstructorArguments)` instead (note the missing `s` character in Contructor word in deprecated method) * Add `XmlEncoder::CDATA_WRAPPING_PATTERN` context option - * Support subclasses of `\DateTime` and `\DateTimeImmutable` for denormalization 7.0 --- diff --git a/Tests/Normalizer/DateTimeNormalizerTest.php b/Tests/Normalizer/DateTimeNormalizerTest.php index a744325d..81219652 100644 --- a/Tests/Normalizer/DateTimeNormalizerTest.php +++ b/Tests/Normalizer/DateTimeNormalizerTest.php @@ -397,6 +397,7 @@ public function testDenormalizeFormatMismatchThrowsException() class DateTimeChild extends \DateTime { } + class DateTimeImmutableChild extends \DateTimeImmutable { } From cb6cff4433f9d2a675eee3e15448c5dd2cacce79 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Thu, 12 Sep 2024 20:19:16 +0200 Subject: [PATCH 20/38] [Serializer][Uid] Add the `Uuid::FORMAT_RFC_9562` and `UidNormalizer::NORMALIZATION_FORMAT_RFC9562` constants --- CHANGELOG.md | 1 + Normalizer/UidNormalizer.php | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb0466ad..b62f84ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ CHANGELOG * Deprecate `CsvEncoderContextBuilder::withEscapeChar()` method * Add `SnakeCaseToCamelCaseNameConverter` * Support subclasses of `\DateTime` and `\DateTimeImmutable` for denormalization + * Add the `UidNormalizer::NORMALIZATION_FORMAT_RFC9562` constant 7.1 --- diff --git a/Normalizer/UidNormalizer.php b/Normalizer/UidNormalizer.php index b107c9d3..2e370bdd 100644 --- a/Normalizer/UidNormalizer.php +++ b/Normalizer/UidNormalizer.php @@ -23,6 +23,7 @@ final class UidNormalizer implements NormalizerInterface, DenormalizerInterface public const NORMALIZATION_FORMAT_BASE58 = 'base58'; public const NORMALIZATION_FORMAT_BASE32 = 'base32'; public const NORMALIZATION_FORMAT_RFC4122 = 'rfc4122'; + public const NORMALIZATION_FORMAT_RFC9562 = self::NORMALIZATION_FORMAT_RFC4122; public const NORMALIZATION_FORMATS = [ self::NORMALIZATION_FORMAT_CANONICAL, self::NORMALIZATION_FORMAT_BASE58, From 498bda022c46e0382e0a7d2aa0dbf9f250d07c11 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Wed, 18 Sep 2024 13:33:46 +0200 Subject: [PATCH 21/38] Miscellaneous tests improvements --- Tests/Normalizer/PropertyNormalizerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Normalizer/PropertyNormalizerTest.php b/Tests/Normalizer/PropertyNormalizerTest.php index 9773e65b..e0e2f9da 100644 --- a/Tests/Normalizer/PropertyNormalizerTest.php +++ b/Tests/Normalizer/PropertyNormalizerTest.php @@ -478,7 +478,7 @@ public function testMultiDimensionObject() RootDummy::class, 'any' ); - $this->assertEquals($root::class, RootDummy::class); + $this->assertSame(RootDummy::class, $root::class); // children (two dimension array) $this->assertCount(1, $root->children); From d93eac1ffd8e3b85070971cd2d95f2e9f7794575 Mon Sep 17 00:00:00 2001 From: HypeMC Date: Mon, 20 May 2024 06:59:56 +0200 Subject: [PATCH 22/38] [Serializer] Introduce named serializers --- CHANGELOG.md | 3 + DataCollector/SerializerDataCollector.php | 90 +-- Debug/TraceableEncoder.php | 5 +- Debug/TraceableNormalizer.php | 5 +- Debug/TraceableSerializer.php | 13 +- DependencyInjection/SerializerPass.php | 152 ++++- .../SerializerDataCollectorTest.php | 174 ++++-- Tests/Debug/TraceableEncoderTest.php | 26 +- Tests/Debug/TraceableNormalizerTest.php | 26 +- Tests/Debug/TraceableSerializerTest.php | 20 +- .../SerializerPassTest.php | 520 +++++++++++++++++- composer.json | 2 +- 12 files changed, 905 insertions(+), 131 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b62f84ba..79a14b50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ CHANGELOG * Add `SnakeCaseToCamelCaseNameConverter` * Support subclasses of `\DateTime` and `\DateTimeImmutable` for denormalization * Add the `UidNormalizer::NORMALIZATION_FORMAT_RFC9562` constant + * Add support for configuring multiple serializer instances with different + default contexts, name converters, sets of normalizers and encoders + * Add support for collection profiles of multiple serializer instances 7.1 --- diff --git a/DataCollector/SerializerDataCollector.php b/DataCollector/SerializerDataCollector.php index 2880dea3..e87c51ca 100644 --- a/DataCollector/SerializerDataCollector.php +++ b/DataCollector/SerializerDataCollector.php @@ -25,11 +25,22 @@ */ class SerializerDataCollector extends DataCollector implements LateDataCollectorInterface { + private const DATA_TEMPLATE = [ + 'serialize' => [], + 'deserialize' => [], + 'normalize' => [], + 'denormalize' => [], + 'encode' => [], + 'decode' => [], + ]; + + private array $dataGroupedByName; private array $collected = []; public function reset(): void { $this->data = []; + unset($this->dataGroupedByName); $this->collected = []; } @@ -43,14 +54,14 @@ public function getName(): string return 'serializer'; } - public function getData(): Data|array + public function getData(?string $name = null): Data|array { - return $this->data; + return null === $name ? $this->data : $this->getDataGroupedByName()[$name]; } - public function getHandledCount(): int + public function getHandledCount(?string $name = null): int { - return array_sum(array_map('count', $this->data)); + return array_sum(array_map('count', $this->getData($name))); } public function getTotalTime(): float @@ -64,110 +75,108 @@ public function getTotalTime(): float return $totalTime; } - public function collectSerialize(string $traceId, mixed $data, string $format, array $context, float $time, array $caller): void + public function getSerializerNames(): array + { + return array_keys($this->getDataGroupedByName()); + } + + public function collectSerialize(string $traceId, mixed $data, string $format, array $context, float $time, array $caller, string $name): void { unset($context[TraceableSerializer::DEBUG_TRACE_ID]); $this->collected[$traceId] = array_merge( $this->collected[$traceId] ?? [], - compact('data', 'format', 'context', 'time', 'caller'), + compact('data', 'format', 'context', 'time', 'caller', 'name'), ['method' => 'serialize'], ); } - public function collectDeserialize(string $traceId, mixed $data, string $type, string $format, array $context, float $time, array $caller): void + public function collectDeserialize(string $traceId, mixed $data, string $type, string $format, array $context, float $time, array $caller, string $name): void { unset($context[TraceableSerializer::DEBUG_TRACE_ID]); $this->collected[$traceId] = array_merge( $this->collected[$traceId] ?? [], - compact('data', 'format', 'type', 'context', 'time', 'caller'), + compact('data', 'format', 'type', 'context', 'time', 'caller', 'name'), ['method' => 'deserialize'], ); } - public function collectNormalize(string $traceId, mixed $data, ?string $format, array $context, float $time, array $caller): void + public function collectNormalize(string $traceId, mixed $data, ?string $format, array $context, float $time, array $caller, string $name): void { unset($context[TraceableSerializer::DEBUG_TRACE_ID]); $this->collected[$traceId] = array_merge( $this->collected[$traceId] ?? [], - compact('data', 'format', 'context', 'time', 'caller'), + compact('data', 'format', 'context', 'time', 'caller', 'name'), ['method' => 'normalize'], ); } - public function collectDenormalize(string $traceId, mixed $data, string $type, ?string $format, array $context, float $time, array $caller): void + public function collectDenormalize(string $traceId, mixed $data, string $type, ?string $format, array $context, float $time, array $caller, string $name): void { unset($context[TraceableSerializer::DEBUG_TRACE_ID]); $this->collected[$traceId] = array_merge( $this->collected[$traceId] ?? [], - compact('data', 'format', 'type', 'context', 'time', 'caller'), + compact('data', 'format', 'type', 'context', 'time', 'caller', 'name'), ['method' => 'denormalize'], ); } - public function collectEncode(string $traceId, mixed $data, ?string $format, array $context, float $time, array $caller): void + public function collectEncode(string $traceId, mixed $data, ?string $format, array $context, float $time, array $caller, string $name): void { unset($context[TraceableSerializer::DEBUG_TRACE_ID]); $this->collected[$traceId] = array_merge( $this->collected[$traceId] ?? [], - compact('data', 'format', 'context', 'time', 'caller'), + compact('data', 'format', 'context', 'time', 'caller', 'name'), ['method' => 'encode'], ); } - public function collectDecode(string $traceId, mixed $data, ?string $format, array $context, float $time, array $caller): void + public function collectDecode(string $traceId, mixed $data, ?string $format, array $context, float $time, array $caller, string $name): void { unset($context[TraceableSerializer::DEBUG_TRACE_ID]); $this->collected[$traceId] = array_merge( $this->collected[$traceId] ?? [], - compact('data', 'format', 'context', 'time', 'caller'), + compact('data', 'format', 'context', 'time', 'caller', 'name'), ['method' => 'decode'], ); } - public function collectNormalization(string $traceId, string $normalizer, float $time): void + public function collectNormalization(string $traceId, string $normalizer, float $time, string $name): void { $method = 'normalize'; - $this->collected[$traceId]['normalization'][] = compact('normalizer', 'method', 'time'); + $this->collected[$traceId]['normalization'][] = compact('normalizer', 'method', 'time', 'name'); } - public function collectDenormalization(string $traceId, string $normalizer, float $time): void + public function collectDenormalization(string $traceId, string $normalizer, float $time, string $name): void { $method = 'denormalize'; - $this->collected[$traceId]['normalization'][] = compact('normalizer', 'method', 'time'); + $this->collected[$traceId]['normalization'][] = compact('normalizer', 'method', 'time', 'name'); } - public function collectEncoding(string $traceId, string $encoder, float $time): void + public function collectEncoding(string $traceId, string $encoder, float $time, string $name): void { $method = 'encode'; - $this->collected[$traceId]['encoding'][] = compact('encoder', 'method', 'time'); + $this->collected[$traceId]['encoding'][] = compact('encoder', 'method', 'time', 'name'); } - public function collectDecoding(string $traceId, string $encoder, float $time): void + public function collectDecoding(string $traceId, string $encoder, float $time, string $name): void { $method = 'decode'; - $this->collected[$traceId]['encoding'][] = compact('encoder', 'method', 'time'); + $this->collected[$traceId]['encoding'][] = compact('encoder', 'method', 'time', 'name'); } public function lateCollect(): void { - $this->data = [ - 'serialize' => [], - 'deserialize' => [], - 'normalize' => [], - 'denormalize' => [], - 'encode' => [], - 'decode' => [], - ]; + $this->data = self::DATA_TEMPLATE; foreach ($this->collected as $collected) { if (!isset($collected['data'])) { @@ -184,6 +193,7 @@ public function lateCollect(): void 'normalization' => [], 'encoding' => [], 'caller' => $collected['caller'] ?? null, + 'name' => $collected['name'], ]; if (isset($collected['normalization'])) { @@ -220,6 +230,22 @@ public function lateCollect(): void } } + private function getDataGroupedByName(): array + { + if (!isset($this->dataGroupedByName)) { + $this->dataGroupedByName = []; + + foreach ($this->data as $method => $items) { + foreach ($items as $item) { + $this->dataGroupedByName[$item['name']] ??= self::DATA_TEMPLATE; + $this->dataGroupedByName[$item['name']][$method][] = $item; + } + } + } + + return $this->dataGroupedByName; + } + private function getMethodLocation(string $class, string $method): array { $reflection = new \ReflectionClass($class); diff --git a/Debug/TraceableEncoder.php b/Debug/TraceableEncoder.php index 42bf4868..39e75e34 100644 --- a/Debug/TraceableEncoder.php +++ b/Debug/TraceableEncoder.php @@ -30,6 +30,7 @@ class TraceableEncoder implements EncoderInterface, DecoderInterface, Serializer public function __construct( private EncoderInterface|DecoderInterface $encoder, private SerializerDataCollector $dataCollector, + private readonly string $serializerName = 'default', ) { } @@ -44,7 +45,7 @@ public function encode(mixed $data, string $format, array $context = []): string $time = microtime(true) - $startTime; if ($traceId = ($context[TraceableSerializer::DEBUG_TRACE_ID] ?? null)) { - $this->dataCollector->collectEncoding($traceId, $this->encoder::class, $time); + $this->dataCollector->collectEncoding($traceId, $this->encoder::class, $time, $this->serializerName); } return $encoded; @@ -70,7 +71,7 @@ public function decode(string $data, string $format, array $context = []): mixed $time = microtime(true) - $startTime; if ($traceId = ($context[TraceableSerializer::DEBUG_TRACE_ID] ?? null)) { - $this->dataCollector->collectDecoding($traceId, $this->encoder::class, $time); + $this->dataCollector->collectDecoding($traceId, $this->encoder::class, $time, $this->serializerName); } return $encoded; diff --git a/Debug/TraceableNormalizer.php b/Debug/TraceableNormalizer.php index c8063675..1b143e29 100644 --- a/Debug/TraceableNormalizer.php +++ b/Debug/TraceableNormalizer.php @@ -31,6 +31,7 @@ class TraceableNormalizer implements NormalizerInterface, DenormalizerInterface, public function __construct( private NormalizerInterface|DenormalizerInterface $normalizer, private SerializerDataCollector $dataCollector, + private readonly string $serializerName = 'default', ) { } @@ -50,7 +51,7 @@ public function normalize(mixed $object, ?string $format = null, array $context $time = microtime(true) - $startTime; if ($traceId = ($context[TraceableSerializer::DEBUG_TRACE_ID] ?? null)) { - $this->dataCollector->collectNormalization($traceId, $this->normalizer::class, $time); + $this->dataCollector->collectNormalization($traceId, $this->normalizer::class, $time, $this->serializerName); } return $normalized; @@ -76,7 +77,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a $time = microtime(true) - $startTime; if ($traceId = ($context[TraceableSerializer::DEBUG_TRACE_ID] ?? null)) { - $this->dataCollector->collectDenormalization($traceId, $this->normalizer::class, $time); + $this->dataCollector->collectDenormalization($traceId, $this->normalizer::class, $time, $this->serializerName); } return $denormalized; diff --git a/Debug/TraceableSerializer.php b/Debug/TraceableSerializer.php index ab766bf4..a05bf4bf 100644 --- a/Debug/TraceableSerializer.php +++ b/Debug/TraceableSerializer.php @@ -32,6 +32,7 @@ class TraceableSerializer implements SerializerInterface, NormalizerInterface, D public function __construct( private SerializerInterface&NormalizerInterface&DenormalizerInterface&EncoderInterface&DecoderInterface $serializer, private SerializerDataCollector $dataCollector, + private readonly string $serializerName = 'default', ) { } @@ -45,7 +46,7 @@ public function serialize(mixed $data, string $format, array $context = []): str $caller = $this->getCaller(__FUNCTION__, SerializerInterface::class); - $this->dataCollector->collectSerialize($traceId, $data, $format, $context, $time, $caller); + $this->dataCollector->collectSerialize($traceId, $data, $format, $context, $time, $caller, $this->serializerName); return $result; } @@ -60,7 +61,7 @@ public function deserialize(mixed $data, string $type, string $format, array $co $caller = $this->getCaller(__FUNCTION__, SerializerInterface::class); - $this->dataCollector->collectDeserialize($traceId, $data, $type, $format, $context, $time, $caller); + $this->dataCollector->collectDeserialize($traceId, $data, $type, $format, $context, $time, $caller, $this->serializerName); return $result; } @@ -75,7 +76,7 @@ public function normalize(mixed $object, ?string $format = null, array $context $caller = $this->getCaller(__FUNCTION__, NormalizerInterface::class); - $this->dataCollector->collectNormalize($traceId, $object, $format, $context, $time, $caller); + $this->dataCollector->collectNormalize($traceId, $object, $format, $context, $time, $caller, $this->serializerName); return $result; } @@ -90,7 +91,7 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a $caller = $this->getCaller(__FUNCTION__, DenormalizerInterface::class); - $this->dataCollector->collectDenormalize($traceId, $data, $type, $format, $context, $time, $caller); + $this->dataCollector->collectDenormalize($traceId, $data, $type, $format, $context, $time, $caller, $this->serializerName); return $result; } @@ -105,7 +106,7 @@ public function encode(mixed $data, string $format, array $context = []): string $caller = $this->getCaller(__FUNCTION__, EncoderInterface::class); - $this->dataCollector->collectEncode($traceId, $data, $format, $context, $time, $caller); + $this->dataCollector->collectEncode($traceId, $data, $format, $context, $time, $caller, $this->serializerName); return $result; } @@ -120,7 +121,7 @@ public function decode(string $data, string $format, array $context = []): mixed $caller = $this->getCaller(__FUNCTION__, DecoderInterface::class); - $this->dataCollector->collectDecode($traceId, $data, $format, $context, $time, $caller); + $this->dataCollector->collectDecode($traceId, $data, $format, $context, $time, $caller, $this->serializerName); return $result; } diff --git a/DependencyInjection/SerializerPass.php b/DependencyInjection/SerializerPass.php index 2a429054..bc1c6e10 100644 --- a/DependencyInjection/SerializerPass.php +++ b/DependencyInjection/SerializerPass.php @@ -19,6 +19,7 @@ use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\Serializer\Debug\TraceableEncoder; use Symfony\Component\Serializer\Debug\TraceableNormalizer; +use Symfony\Component\Serializer\SerializerInterface; /** * Adds all services with the tags "serializer.encoder" and "serializer.normalizer" as @@ -31,44 +32,177 @@ class SerializerPass implements CompilerPassInterface { use PriorityTaggedServiceTrait; + private const NAME_CONVERTER_METADATA_AWARE_ID = 'serializer.name_converter.metadata_aware'; + public function process(ContainerBuilder $container): void { if (!$container->hasDefinition('serializer')) { return; } - if (!$normalizers = $this->findAndSortTaggedServices('serializer.normalizer', $container)) { + $namedSerializers = $container->hasParameter('.serializer.named_serializers') + ? $container->getParameter('.serializer.named_serializers') : []; + + $this->createNamedSerializerTags($container, 'serializer.normalizer', 'include_built_in_normalizers', $namedSerializers); + $this->createNamedSerializerTags($container, 'serializer.encoder', 'include_built_in_encoders', $namedSerializers); + + if (!$normalizers = $this->findAndSortTaggedServices('serializer.normalizer.default', $container)) { throw new RuntimeException('You must tag at least one service as "serializer.normalizer" to use the "serializer" service.'); } - if (!$encoders = $this->findAndSortTaggedServices('serializer.encoder', $container)) { + if (!$encoders = $this->findAndSortTaggedServices('serializer.encoder.default', $container)) { throw new RuntimeException('You must tag at least one service as "serializer.encoder" to use the "serializer" service.'); } if ($container->hasParameter('serializer.default_context')) { $defaultContext = $container->getParameter('serializer.default_context'); - foreach (array_merge($normalizers, $encoders) as $service) { - $definition = $container->getDefinition($service); - $definition->setBindings(['array $defaultContext' => new BoundArgument($defaultContext, false)] + $definition->getBindings()); + $this->bindDefaultContext($container, array_merge($normalizers, $encoders), $defaultContext); + $container->getParameterBag()->remove('serializer.default_context'); + } + + $this->configureSerializer($container, 'serializer', $normalizers, $encoders, 'default'); + + if ($namedSerializers) { + $this->configureNamedSerializers($container); + } + } + + private function createNamedSerializerTags(ContainerBuilder $container, string $tagName, string $configName, array $namedSerializers): void + { + $serializerNames = array_keys($namedSerializers); + $withBuiltIn = array_filter($serializerNames, fn (string $name) => $namedSerializers[$name][$configName] ?? false); + + foreach ($container->findTaggedServiceIds($tagName) as $serviceId => $tags) { + $definition = $container->getDefinition($serviceId); + + foreach ($tags as $tag) { + $names = (array) ($tag['serializer'] ?? []); + + if (!$names) { + $names = ['default']; + } elseif (\in_array('*', $names, true)) { + $names = array_unique(['default', ...$serializerNames]); + } + + if ($tag['built_in'] ?? false) { + $names = array_unique(['default', ...$names, ...$withBuiltIn]); + } + + unset($tag['serializer'], $tag['built_in']); + + foreach ($names as $name) { + $definition->addTag($tagName.'.'.$name, $tag); + } } + } + } - $container->getParameterBag()->remove('serializer.default_context'); + private function bindDefaultContext(ContainerBuilder $container, array $services, array $defaultContext): void + { + foreach ($services as $id) { + $definition = $container->getDefinition((string) $id); + $definition->setBindings(['array $defaultContext' => new BoundArgument($defaultContext, false)] + $definition->getBindings()); } + } + private function configureSerializer(ContainerBuilder $container, string $id, array $normalizers, array $encoders, string $serializerName): void + { if ($container->getParameter('kernel.debug') && $container->hasDefinition('serializer.data_collector')) { foreach ($normalizers as $i => $normalizer) { $normalizers[$i] = $container->register('.debug.serializer.normalizer.'.$normalizer, TraceableNormalizer::class) - ->setArguments([$normalizer, new Reference('serializer.data_collector')]); + ->setArguments([$normalizer, new Reference('serializer.data_collector'), $serializerName]); } foreach ($encoders as $i => $encoder) { $encoders[$i] = $container->register('.debug.serializer.encoder.'.$encoder, TraceableEncoder::class) - ->setArguments([$encoder, new Reference('serializer.data_collector')]); + ->setArguments([$encoder, new Reference('serializer.data_collector'), $serializerName]); } } - $serializerDefinition = $container->getDefinition('serializer'); + $serializerDefinition = $container->getDefinition($id); $serializerDefinition->replaceArgument(0, $normalizers); $serializerDefinition->replaceArgument(1, $encoders); } + + private function configureNamedSerializers(ContainerBuilder $container): void + { + $defaultSerializerNameConverter = $container->hasParameter('.serializer.name_converter') + ? $container->getParameter('.serializer.name_converter') : null; + + foreach ($container->getParameter('.serializer.named_serializers') as $serializerName => $config) { + $config += ['default_context' => [], 'name_converter' => null]; + $serializerId = 'serializer.'.$serializerName; + + if (!$normalizers = $this->findAndSortTaggedServices('serializer.normalizer.'.$serializerName, $container)) { + throw new RuntimeException(\sprintf('The named serializer "%1$s" requires at least one registered normalizer. Tag the normalizers as "serializer.normalizer" with the "serializer" attribute set to "%1$s".', $serializerName)); + } + + if (!$encoders = $this->findAndSortTaggedServices('serializer.encoder.'.$serializerName, $container)) { + throw new RuntimeException(\sprintf('The named serializer "%1$s" requires at least one registered encoder. Tag the encoders as "serializer.encoder" with the "serializer" attribute set to "%1$s".', $serializerName)); + } + + $config['name_converter'] = $defaultSerializerNameConverter !== $config['name_converter'] + ? $this->buildChildNameConverterDefinition($container, $config['name_converter']) + : self::NAME_CONVERTER_METADATA_AWARE_ID; + + $normalizers = $this->buildChildDefinitions($container, $serializerName, $normalizers, $config); + $encoders = $this->buildChildDefinitions($container, $serializerName, $encoders, $config); + + $this->bindDefaultContext($container, array_merge($normalizers, $encoders), $config['default_context']); + + $container->registerChild($serializerId, 'serializer'); + $container->registerAliasForArgument($serializerId, SerializerInterface::class, $serializerName.'.serializer'); + + $this->configureSerializer($container, $serializerId, $normalizers, $encoders, $serializerName); + + if ($container->getParameter('kernel.debug') && $container->hasDefinition('debug.serializer')) { + $container->registerChild($debugId = 'debug.'.$serializerId, 'debug.serializer') + ->setDecoratedService($serializerId) + ->replaceArgument(0, new Reference($debugId.'.inner')) + ->replaceArgument(2, $serializerName); + } + } + } + + private function buildChildNameConverterDefinition(ContainerBuilder $container, ?string $nameConverter): ?string + { + $childId = self::NAME_CONVERTER_METADATA_AWARE_ID.'.'.ContainerBuilder::hash($nameConverter); + + if (!$container->hasDefinition($childId)) { + $childDefinition = $container->registerChild($childId, self::NAME_CONVERTER_METADATA_AWARE_ID.'.abstract'); + if (null !== $nameConverter) { + $childDefinition->addArgument(new Reference($nameConverter)); + } + } + + return $childId; + } + + private function buildChildDefinitions(ContainerBuilder $container, string $serializerName, array $services, array $config): array + { + foreach ($services as &$id) { + $childId = $id.'.'.$serializerName; + + $definition = $container->registerChild($childId, (string) $id); + + if (null !== $nameConverterIndex = $this->findNameConverterIndex($container, (string) $id)) { + $definition->replaceArgument($nameConverterIndex, new Reference($config['name_converter'])); + } + + $id = new Reference($childId); + } + + return $services; + } + + private function findNameConverterIndex(ContainerBuilder $container, string $id): int|string|null + { + foreach ($container->getDefinition($id)->getArguments() as $index => $argument) { + if ($argument instanceof Reference && self::NAME_CONVERTER_METADATA_AWARE_ID === (string) $argument) { + return $index; + } + } + + return null; + } } diff --git a/Tests/DataCollector/SerializerDataCollectorTest.php b/Tests/DataCollector/SerializerDataCollectorTest.php index aebde8ef..6a26565a 100644 --- a/Tests/DataCollector/SerializerDataCollectorTest.php +++ b/Tests/DataCollector/SerializerDataCollectorTest.php @@ -25,8 +25,8 @@ public function testCollectSerialize() $dataCollector = new SerializerDataCollector(); $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; - $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller); - $dataCollector->collectDeserialize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 1.0, $caller); + $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller, 'default'); + $dataCollector->collectDeserialize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 1.0, $caller, 'default'); $dataCollector->lateCollect(); $collectedData = $this->castCollectedData($dataCollector->getData()); @@ -41,6 +41,7 @@ public function testCollectSerialize() 'normalization' => [], 'encoding' => [], 'caller' => $caller, + 'name' => 'default', ]], $collectedData['serialize']); $this->assertSame([[ @@ -53,6 +54,7 @@ public function testCollectSerialize() 'normalization' => [], 'encoding' => [], 'caller' => $caller, + 'name' => 'default', ]], $collectedData['deserialize']); } @@ -61,8 +63,8 @@ public function testCollectNormalize() $dataCollector = new SerializerDataCollector(); $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; - $dataCollector->collectNormalize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller); - $dataCollector->collectDenormalize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 1.0, $caller); + $dataCollector->collectNormalize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller, 'default'); + $dataCollector->collectDenormalize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 1.0, $caller, 'default'); $dataCollector->lateCollect(); $collectedData = $this->castCollectedData($dataCollector->getData()); @@ -77,6 +79,7 @@ public function testCollectNormalize() 'normalization' => [], 'encoding' => [], 'caller' => $caller, + 'name' => 'default', ]], $collectedData['normalize']); $this->assertSame([[ @@ -89,6 +92,7 @@ public function testCollectNormalize() 'normalization' => [], 'encoding' => [], 'caller' => $caller, + 'name' => 'default', ]], $collectedData['denormalize']); } @@ -97,8 +101,8 @@ public function testCollectEncode() $dataCollector = new SerializerDataCollector(); $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; - $dataCollector->collectEncode('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller); - $dataCollector->collectDecode('traceIdTwo', 'data', 'format', ['foo' => 'bar'], 1.0, $caller); + $dataCollector->collectEncode('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller, 'default'); + $dataCollector->collectDecode('traceIdTwo', 'data', 'format', ['foo' => 'bar'], 1.0, $caller, 'default'); $dataCollector->lateCollect(); $collectedData = $this->castCollectedData($dataCollector->getData()); @@ -113,6 +117,7 @@ public function testCollectEncode() 'normalization' => [], 'encoding' => [], 'caller' => $caller, + 'name' => 'default', ]], $collectedData['encode']); $this->assertSame([[ @@ -125,6 +130,7 @@ public function testCollectEncode() 'normalization' => [], 'encoding' => [], 'caller' => $caller, + 'name' => 'default', ]], $collectedData['decode']); } @@ -133,18 +139,18 @@ public function testCollectNormalization() $dataCollector = new SerializerDataCollector(); $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; - $dataCollector->collectNormalize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 20.0, $caller); - $dataCollector->collectDenormalize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 20.0, $caller); + $dataCollector->collectNormalize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 20.0, $caller, 'default'); + $dataCollector->collectDenormalize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 20.0, $caller, 'default'); - $dataCollector->collectNormalization('traceIdOne', DateTimeNormalizer::class, 1.0); - $dataCollector->collectNormalization('traceIdOne', DateTimeNormalizer::class, 2.0); - $dataCollector->collectNormalization('traceIdOne', ObjectNormalizer::class, 5.0); - $dataCollector->collectNormalization('traceIdOne', ObjectNormalizer::class, 10.0); + $dataCollector->collectNormalization('traceIdOne', DateTimeNormalizer::class, 1.0, 'default'); + $dataCollector->collectNormalization('traceIdOne', DateTimeNormalizer::class, 2.0, 'default'); + $dataCollector->collectNormalization('traceIdOne', ObjectNormalizer::class, 5.0, 'default'); + $dataCollector->collectNormalization('traceIdOne', ObjectNormalizer::class, 10.0, 'default'); - $dataCollector->collectNormalization('traceIdTwo', DateTimeNormalizer::class, 1.0); - $dataCollector->collectNormalization('traceIdTwo', DateTimeNormalizer::class, 2.0); - $dataCollector->collectNormalization('traceIdTwo', ObjectNormalizer::class, 5.0); - $dataCollector->collectNormalization('traceIdTwo', ObjectNormalizer::class, 10.0); + $dataCollector->collectNormalization('traceIdTwo', DateTimeNormalizer::class, 1.0, 'default'); + $dataCollector->collectNormalization('traceIdTwo', DateTimeNormalizer::class, 2.0, 'default'); + $dataCollector->collectNormalization('traceIdTwo', ObjectNormalizer::class, 5.0, 'default'); + $dataCollector->collectNormalization('traceIdTwo', ObjectNormalizer::class, 10.0, 'default'); $dataCollector->lateCollect(); $collectedData = $dataCollector->getData(); @@ -189,18 +195,18 @@ public function testCollectEncoding() $dataCollector = new SerializerDataCollector(); $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; - $dataCollector->collectEncode('traceIdOne', 'data', 'format', ['foo' => 'bar'], 20.0, $caller); - $dataCollector->collectDecode('traceIdTwo', 'data', 'format', ['foo' => 'bar'], 20.0, $caller); + $dataCollector->collectEncode('traceIdOne', 'data', 'format', ['foo' => 'bar'], 20.0, $caller, 'default'); + $dataCollector->collectDecode('traceIdTwo', 'data', 'format', ['foo' => 'bar'], 20.0, $caller, 'default'); - $dataCollector->collectEncoding('traceIdOne', JsonEncoder::class, 1.0); - $dataCollector->collectEncoding('traceIdOne', JsonEncoder::class, 2.0); - $dataCollector->collectEncoding('traceIdOne', CsvEncoder::class, 5.0); - $dataCollector->collectEncoding('traceIdOne', CsvEncoder::class, 10.0); + $dataCollector->collectEncoding('traceIdOne', JsonEncoder::class, 1.0, 'default'); + $dataCollector->collectEncoding('traceIdOne', JsonEncoder::class, 2.0, 'default'); + $dataCollector->collectEncoding('traceIdOne', CsvEncoder::class, 5.0, 'default'); + $dataCollector->collectEncoding('traceIdOne', CsvEncoder::class, 10.0, 'default'); - $dataCollector->collectDecoding('traceIdTwo', JsonEncoder::class, 1.0); - $dataCollector->collectDecoding('traceIdTwo', JsonEncoder::class, 2.0); - $dataCollector->collectDecoding('traceIdTwo', CsvEncoder::class, 5.0); - $dataCollector->collectDecoding('traceIdTwo', CsvEncoder::class, 10.0); + $dataCollector->collectDecoding('traceIdTwo', JsonEncoder::class, 1.0, 'default'); + $dataCollector->collectDecoding('traceIdTwo', JsonEncoder::class, 2.0, 'default'); + $dataCollector->collectDecoding('traceIdTwo', CsvEncoder::class, 5.0, 'default'); + $dataCollector->collectDecoding('traceIdTwo', CsvEncoder::class, 10.0, 'default'); $dataCollector->lateCollect(); $collectedData = $dataCollector->getData(); @@ -245,13 +251,13 @@ public function testCountHandled() $dataCollector = new SerializerDataCollector(); $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; - $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller); - $dataCollector->collectDeserialize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 1.0, $caller); - $dataCollector->collectNormalize('traceIdThree', 'data', 'format', ['foo' => 'bar'], 20.0, $caller); - $dataCollector->collectDenormalize('traceIdFour', 'data', 'type', 'format', ['foo' => 'bar'], 20.0, $caller); - $dataCollector->collectEncode('traceIdFive', 'data', 'format', ['foo' => 'bar'], 20.0, $caller); - $dataCollector->collectDecode('traceIdSix', 'data', 'format', ['foo' => 'bar'], 20.0, $caller); - $dataCollector->collectSerialize('traceIdSeven', 'data', 'format', ['foo' => 'bar'], 1.0, $caller); + $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller, 'default'); + $dataCollector->collectDeserialize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 1.0, $caller, 'default'); + $dataCollector->collectNormalize('traceIdThree', 'data', 'format', ['foo' => 'bar'], 20.0, $caller, 'default'); + $dataCollector->collectDenormalize('traceIdFour', 'data', 'type', 'format', ['foo' => 'bar'], 20.0, $caller, 'default'); + $dataCollector->collectEncode('traceIdFive', 'data', 'format', ['foo' => 'bar'], 20.0, $caller, 'default'); + $dataCollector->collectDecode('traceIdSix', 'data', 'format', ['foo' => 'bar'], 20.0, $caller, 'default'); + $dataCollector->collectSerialize('traceIdSeven', 'data', 'format', ['foo' => 'bar'], 1.0, $caller, 'default'); $dataCollector->lateCollect(); @@ -264,13 +270,13 @@ public function testGetTotalTime() $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; - $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller); - $dataCollector->collectDeserialize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 2.0, $caller); - $dataCollector->collectNormalize('traceIdThree', 'data', 'format', ['foo' => 'bar'], 3.0, $caller); - $dataCollector->collectDenormalize('traceIdFour', 'data', 'type', 'format', ['foo' => 'bar'], 4.0, $caller); - $dataCollector->collectEncode('traceIdFive', 'data', 'format', ['foo' => 'bar'], 5.0, $caller); - $dataCollector->collectDecode('traceIdSix', 'data', 'format', ['foo' => 'bar'], 6.0, $caller); - $dataCollector->collectSerialize('traceIdSeven', 'data', 'format', ['foo' => 'bar'], 7.0, $caller); + $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller, 'default'); + $dataCollector->collectDeserialize('traceIdTwo', 'data', 'type', 'format', ['foo' => 'bar'], 2.0, $caller, 'default'); + $dataCollector->collectNormalize('traceIdThree', 'data', 'format', ['foo' => 'bar'], 3.0, $caller, 'default'); + $dataCollector->collectDenormalize('traceIdFour', 'data', 'type', 'format', ['foo' => 'bar'], 4.0, $caller, 'default'); + $dataCollector->collectEncode('traceIdFive', 'data', 'format', ['foo' => 'bar'], 5.0, $caller, 'default'); + $dataCollector->collectDecode('traceIdSix', 'data', 'format', ['foo' => 'bar'], 6.0, $caller, 'default'); + $dataCollector->collectSerialize('traceIdSeven', 'data', 'format', ['foo' => 'bar'], 7.0, $caller, 'default'); $dataCollector->lateCollect(); @@ -282,7 +288,7 @@ public function testReset() $dataCollector = new SerializerDataCollector(); $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; - $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller); + $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 1.0, $caller, 'default'); $dataCollector->lateCollect(); $this->assertNotSame([], $dataCollector->getData()); @@ -295,10 +301,10 @@ public function testDoNotCollectPartialTraces() { $dataCollector = new SerializerDataCollector(); - $dataCollector->collectNormalization('traceIdOne', DateTimeNormalizer::class, 1.0); - $dataCollector->collectDenormalization('traceIdTwo', DateTimeNormalizer::class, 1.0); - $dataCollector->collectEncoding('traceIdThree', CsvEncoder::class, 10.0); - $dataCollector->collectDecoding('traceIdFour', JsonEncoder::class, 1.0); + $dataCollector->collectNormalization('traceIdOne', DateTimeNormalizer::class, 1.0, 'default'); + $dataCollector->collectDenormalization('traceIdTwo', DateTimeNormalizer::class, 1.0, 'default'); + $dataCollector->collectEncoding('traceIdThree', CsvEncoder::class, 10.0, 'default'); + $dataCollector->collectDecoding('traceIdFour', JsonEncoder::class, 1.0, 'default'); $dataCollector->lateCollect(); @@ -312,6 +318,84 @@ public function testDoNotCollectPartialTraces() $this->assertSame([], $data['decode']); } + public function testNamedSerializers() + { + $dataCollector = new SerializerDataCollector(); + + $caller = ['name' => 'Foo.php', 'file' => 'src/Foo.php', 'line' => 123]; + $dataCollector->collectNormalization('traceIdOne', DateTimeNormalizer::class, 3.0, 'default'); + $dataCollector->collectEncoding('traceIdOne', CsvEncoder::class, 4.0, 'default'); + $dataCollector->collectSerialize('traceIdOne', 'data', 'format', ['foo' => 'bar'], 7.0, $caller, 'default'); + $dataCollector->collectNormalization('traceIdTwo', ObjectNormalizer::class, 3.0, 'default'); + $dataCollector->collectNormalize('traceIdTwo', 'data', 'format', ['foo' => 'bar'], 5.0, $caller, 'default'); + + $dataCollector->collectEncoding('traceIdThree', JsonEncoder::class, 4.0, 'api'); + $dataCollector->collectEncode('traceIdThree', 'data', 'format', ['foo' => 'bar'], 5.0, $caller, 'api'); + $dataCollector->collectDenormalization('traceIdFour', DateTimeNormalizer::class, 3.0, 'api'); + $dataCollector->collectDecoding('traceIdFour', CsvEncoder::class, 4.0, 'api'); + $dataCollector->collectDeserialize('traceIdFour', 'data', 'type', 'format', ['foo' => 'bar'], 7.0, $caller, 'api'); + $dataCollector->collectDenormalization('traceIdFive', ObjectNormalizer::class, 3.0, 'api'); + $dataCollector->collectDenormalize('traceIdFive', 'data', 'type', 'format', ['foo' => 'bar'], 5.0, $caller, 'api'); + $dataCollector->collectDecoding('traceIdSix', JsonEncoder::class, 4.0, 'api'); + $dataCollector->collectDecode('traceIdSix', 'data', 'format', ['foo' => 'bar'], 5.0, $caller, 'api'); + + $dataCollector->lateCollect(); + + $this->assertSame(6, $dataCollector->getHandledCount()); + + $collectedData = $dataCollector->getData(); + + $this->assertSame('default', $collectedData['serialize'][0]['name']); + $this->assertSame('DateTimeNormalizer', $collectedData['serialize'][0]['normalizer']['class']); + $this->assertSame('CsvEncoder', $collectedData['serialize'][0]['encoder']['class']); + $this->assertSame('default', $collectedData['normalize'][0]['name']); + $this->assertSame('ObjectNormalizer', $collectedData['normalize'][0]['normalizer']['class']); + + $this->assertSame('api', $collectedData['encode'][0]['name']); + $this->assertSame('JsonEncoder', $collectedData['encode'][0]['encoder']['class']); + $this->assertSame('api', $collectedData['deserialize'][0]['name']); + $this->assertSame('DateTimeNormalizer', $collectedData['deserialize'][0]['normalizer']['class']); + $this->assertSame('CsvEncoder', $collectedData['deserialize'][0]['encoder']['class']); + $this->assertSame('api', $collectedData['denormalize'][0]['name']); + $this->assertSame('ObjectNormalizer', $collectedData['denormalize'][0]['normalizer']['class']); + $this->assertSame('api', $collectedData['decode'][0]['name']); + $this->assertSame('JsonEncoder', $collectedData['decode'][0]['encoder']['class']); + + $this->assertSame(['default', 'api'], $dataCollector->getSerializerNames()); + + $this->assertSame(2, $dataCollector->getHandledCount('default')); + + $collectedData = $dataCollector->getData('default'); + + $this->assertSame('default', $collectedData['serialize'][0]['name']); + $this->assertSame('DateTimeNormalizer', $collectedData['serialize'][0]['normalizer']['class']); + $this->assertSame('CsvEncoder', $collectedData['serialize'][0]['encoder']['class']); + $this->assertSame('default', $collectedData['normalize'][0]['name']); + $this->assertSame('ObjectNormalizer', $collectedData['normalize'][0]['normalizer']['class']); + + $this->assertEmpty($collectedData['encode']); + $this->assertEmpty($collectedData['deserialize']); + $this->assertEmpty($collectedData['denormalize']); + $this->assertEmpty($collectedData['decode']); + + $this->assertSame(4, $dataCollector->getHandledCount('api')); + + $collectedData = $dataCollector->getData('api'); + + $this->assertEmpty($collectedData['serialize']); + $this->assertEmpty($collectedData['normalize']); + + $this->assertSame('api', $collectedData['encode'][0]['name']); + $this->assertSame('JsonEncoder', $collectedData['encode'][0]['encoder']['class']); + $this->assertSame('api', $collectedData['deserialize'][0]['name']); + $this->assertSame('DateTimeNormalizer', $collectedData['deserialize'][0]['normalizer']['class']); + $this->assertSame('CsvEncoder', $collectedData['deserialize'][0]['encoder']['class']); + $this->assertSame('api', $collectedData['denormalize'][0]['name']); + $this->assertSame('ObjectNormalizer', $collectedData['denormalize'][0]['normalizer']['class']); + $this->assertSame('api', $collectedData['decode'][0]['name']); + $this->assertSame('JsonEncoder', $collectedData['decode'][0]['encoder']['class']); + } + /** * Cast cloned vars to be able to test nested values. */ diff --git a/Tests/Debug/TraceableEncoderTest.php b/Tests/Debug/TraceableEncoderTest.php index ec38c0ef..2ac0b8f1 100644 --- a/Tests/Debug/TraceableEncoderTest.php +++ b/Tests/Debug/TraceableEncoderTest.php @@ -36,12 +36,14 @@ public function testForwardsToEncoder() ->with('data', 'format', $this->isType('array')) ->willReturn('decoded'); - $this->assertSame('encoded', (new TraceableEncoder($encoder, new SerializerDataCollector()))->encode('data', 'format')); - $this->assertSame('decoded', (new TraceableEncoder($decoder, new SerializerDataCollector()))->decode('data', 'format')); + $this->assertSame('encoded', (new TraceableEncoder($encoder, new SerializerDataCollector(), 'default'))->encode('data', 'format')); + $this->assertSame('decoded', (new TraceableEncoder($decoder, new SerializerDataCollector(), 'default'))->decode('data', 'format')); } public function testCollectEncodingData() { + $serializerName = uniqid('name', true); + $encoder = $this->createMock(EncoderInterface::class); $decoder = $this->createMock(DecoderInterface::class); @@ -49,14 +51,14 @@ public function testCollectEncodingData() $dataCollector ->expects($this->once()) ->method('collectEncoding') - ->with($this->isType('string'), $encoder::class, $this->isType('float')); + ->with($this->isType('string'), $encoder::class, $this->isType('float'), $serializerName); $dataCollector ->expects($this->once()) ->method('collectDecoding') - ->with($this->isType('string'), $decoder::class, $this->isType('float')); + ->with($this->isType('string'), $decoder::class, $this->isType('float'), $serializerName); - (new TraceableEncoder($encoder, $dataCollector))->encode('data', 'format', [TraceableSerializer::DEBUG_TRACE_ID => 'debug']); - (new TraceableEncoder($decoder, $dataCollector))->decode('data', 'format', [TraceableSerializer::DEBUG_TRACE_ID => 'debug']); + (new TraceableEncoder($encoder, $dataCollector, $serializerName))->encode('data', 'format', [TraceableSerializer::DEBUG_TRACE_ID => 'debug']); + (new TraceableEncoder($decoder, $dataCollector, $serializerName))->decode('data', 'format', [TraceableSerializer::DEBUG_TRACE_ID => 'debug']); } public function testNotCollectEncodingDataIfNoDebugTraceId() @@ -68,22 +70,22 @@ public function testNotCollectEncodingDataIfNoDebugTraceId() $dataCollector->expects($this->never())->method('collectEncoding'); $dataCollector->expects($this->never())->method('collectDecoding'); - (new TraceableEncoder($encoder, $dataCollector))->encode('data', 'format'); - (new TraceableEncoder($decoder, $dataCollector))->decode('data', 'format'); + (new TraceableEncoder($encoder, $dataCollector, 'default'))->encode('data', 'format'); + (new TraceableEncoder($decoder, $dataCollector, 'default'))->decode('data', 'format'); } public function testCannotEncodeIfNotEncoder() { $this->expectException(\BadMethodCallException::class); - (new TraceableEncoder($this->createMock(DecoderInterface::class), new SerializerDataCollector()))->encode('data', 'format'); + (new TraceableEncoder($this->createMock(DecoderInterface::class), new SerializerDataCollector(), 'default'))->encode('data', 'format'); } public function testCannotDecodeIfNotDecoder() { $this->expectException(\BadMethodCallException::class); - (new TraceableEncoder($this->createMock(EncoderInterface::class), new SerializerDataCollector()))->decode('data', 'format'); + (new TraceableEncoder($this->createMock(EncoderInterface::class), new SerializerDataCollector(), 'default'))->decode('data', 'format'); } public function testSupports() @@ -94,8 +96,8 @@ public function testSupports() $decoder = $this->createMock(DecoderInterface::class); $decoder->method('supportsDecoding')->willReturn(true); - $traceableEncoder = new TraceableEncoder($encoder, new SerializerDataCollector()); - $traceableDecoder = new TraceableEncoder($decoder, new SerializerDataCollector()); + $traceableEncoder = new TraceableEncoder($encoder, new SerializerDataCollector(), 'default'); + $traceableDecoder = new TraceableEncoder($decoder, new SerializerDataCollector(), 'default'); $this->assertTrue($traceableEncoder->supportsEncoding('data')); $this->assertTrue($traceableDecoder->supportsDecoding('data')); diff --git a/Tests/Debug/TraceableNormalizerTest.php b/Tests/Debug/TraceableNormalizerTest.php index 307bc7b6..56c16139 100644 --- a/Tests/Debug/TraceableNormalizerTest.php +++ b/Tests/Debug/TraceableNormalizerTest.php @@ -38,12 +38,14 @@ public function testForwardsToNormalizer() ->with('data', 'type', 'format', $this->isType('array')) ->willReturn('denormalized'); - $this->assertSame('normalized', (new TraceableNormalizer($normalizer, new SerializerDataCollector()))->normalize('data', 'format')); - $this->assertSame('denormalized', (new TraceableNormalizer($denormalizer, new SerializerDataCollector()))->denormalize('data', 'type', 'format')); + $this->assertSame('normalized', (new TraceableNormalizer($normalizer, new SerializerDataCollector(), 'default'))->normalize('data', 'format')); + $this->assertSame('denormalized', (new TraceableNormalizer($denormalizer, new SerializerDataCollector(), 'default'))->denormalize('data', 'type', 'format')); } public function testCollectNormalizationData() { + $serializerName = uniqid('name', true); + $normalizer = $this->createMock(NormalizerInterface::class); $normalizer->method('getSupportedTypes')->willReturn(['*' => false]); $denormalizer = $this->createMock(DenormalizerInterface::class); @@ -53,14 +55,14 @@ public function testCollectNormalizationData() $dataCollector ->expects($this->once()) ->method('collectNormalization') - ->with($this->isType('string'), $normalizer::class, $this->isType('float')); + ->with($this->isType('string'), $normalizer::class, $this->isType('float'), $serializerName); $dataCollector ->expects($this->once()) ->method('collectDenormalization') - ->with($this->isType('string'), $denormalizer::class, $this->isType('float')); + ->with($this->isType('string'), $denormalizer::class, $this->isType('float'), $serializerName); - (new TraceableNormalizer($normalizer, $dataCollector))->normalize('data', 'format', [TraceableSerializer::DEBUG_TRACE_ID => 'debug']); - (new TraceableNormalizer($denormalizer, $dataCollector))->denormalize('data', 'type', 'format', [TraceableSerializer::DEBUG_TRACE_ID => 'debug']); + (new TraceableNormalizer($normalizer, $dataCollector, $serializerName))->normalize('data', 'format', [TraceableSerializer::DEBUG_TRACE_ID => 'debug']); + (new TraceableNormalizer($denormalizer, $dataCollector, $serializerName))->denormalize('data', 'type', 'format', [TraceableSerializer::DEBUG_TRACE_ID => 'debug']); } public function testNotCollectNormalizationDataIfNoDebugTraceId() @@ -74,22 +76,22 @@ public function testNotCollectNormalizationDataIfNoDebugTraceId() $dataCollector->expects($this->never())->method('collectNormalization'); $dataCollector->expects($this->never())->method('collectDenormalization'); - (new TraceableNormalizer($normalizer, $dataCollector))->normalize('data', 'format'); - (new TraceableNormalizer($denormalizer, $dataCollector))->denormalize('data', 'type', 'format'); + (new TraceableNormalizer($normalizer, $dataCollector, 'default'))->normalize('data', 'format'); + (new TraceableNormalizer($denormalizer, $dataCollector, 'default'))->denormalize('data', 'type', 'format'); } public function testCannotNormalizeIfNotNormalizer() { $this->expectException(\BadMethodCallException::class); - (new TraceableNormalizer($this->createMock(DenormalizerInterface::class), new SerializerDataCollector()))->normalize('data'); + (new TraceableNormalizer($this->createMock(DenormalizerInterface::class), new SerializerDataCollector(), 'default'))->normalize('data'); } public function testCannotDenormalizeIfNotDenormalizer() { $this->expectException(\BadMethodCallException::class); - (new TraceableNormalizer($this->createMock(NormalizerInterface::class), new SerializerDataCollector()))->denormalize('data', 'type'); + (new TraceableNormalizer($this->createMock(NormalizerInterface::class), new SerializerDataCollector(), 'default'))->denormalize('data', 'type'); } public function testSupports() @@ -102,8 +104,8 @@ public function testSupports() $denormalizer->method('getSupportedTypes')->willReturn(['*' => false]); $denormalizer->method('supportsDenormalization')->willReturn(true); - $traceableNormalizer = new TraceableNormalizer($normalizer, new SerializerDataCollector()); - $traceableDenormalizer = new TraceableNormalizer($denormalizer, new SerializerDataCollector()); + $traceableNormalizer = new TraceableNormalizer($normalizer, new SerializerDataCollector(), 'default'); + $traceableDenormalizer = new TraceableNormalizer($denormalizer, new SerializerDataCollector(), 'default'); $this->assertTrue($traceableNormalizer->supportsNormalization('data')); $this->assertTrue($traceableDenormalizer->supportsDenormalization('data', 'type')); diff --git a/Tests/Debug/TraceableSerializerTest.php b/Tests/Debug/TraceableSerializerTest.php index ea3c851c..d697b270 100644 --- a/Tests/Debug/TraceableSerializerTest.php +++ b/Tests/Debug/TraceableSerializerTest.php @@ -56,7 +56,7 @@ public function testForwardsToSerializer() ->with('data', 'format', $this->isType('array')) ->willReturn('decoded'); - $traceableSerializer = new TraceableSerializer($serializer, new SerializerDataCollector()); + $traceableSerializer = new TraceableSerializer($serializer, new SerializerDataCollector(), 'default'); $this->assertSame('serialized', $traceableSerializer->serialize('data', 'format')); $this->assertSame('deserialized', $traceableSerializer->deserialize('data', 'type', 'format')); @@ -68,33 +68,35 @@ public function testForwardsToSerializer() public function testCollectData() { + $serializerName = uniqid('name', true); + $dataCollector = $this->createMock(SerializerDataCollector::class); $dataCollector ->expects($this->once()) ->method('collectSerialize') - ->with($this->isType('string'), 'data', 'format', $this->isType('array'), $this->isType('float')); + ->with($this->isType('string'), 'data', 'format', $this->isType('array'), $this->isType('float'), $this->isType('array'), $serializerName); $dataCollector ->expects($this->once()) ->method('collectDeserialize') - ->with($this->isType('string'), 'data', 'type', 'format', $this->isType('array'), $this->isType('float')); + ->with($this->isType('string'), 'data', 'type', 'format', $this->isType('array'), $this->isType('float'), $this->isType('array'), $serializerName); $dataCollector ->expects($this->once()) ->method('collectNormalize') - ->with($this->isType('string'), 'data', 'format', $this->isType('array'), $this->isType('float')); + ->with($this->isType('string'), 'data', 'format', $this->isType('array'), $this->isType('float'), $this->isType('array'), $serializerName); $dataCollector ->expects($this->once()) ->method('collectDenormalize') - ->with($this->isType('string'), 'data', 'type', 'format', $this->isType('array'), $this->isType('float')); + ->with($this->isType('string'), 'data', 'type', 'format', $this->isType('array'), $this->isType('float'), $this->isType('array'), $serializerName); $dataCollector ->expects($this->once()) ->method('collectEncode') - ->with($this->isType('string'), 'data', 'format', $this->isType('array'), $this->isType('float')); + ->with($this->isType('string'), 'data', 'format', $this->isType('array'), $this->isType('float'), $this->isType('array'), $serializerName); $dataCollector ->expects($this->once()) ->method('collectDecode') - ->with($this->isType('string'), 'data', 'format', $this->isType('array'), $this->isType('float')); + ->with($this->isType('string'), 'data', 'format', $this->isType('array'), $this->isType('float'), $this->isType('array'), $serializerName); - $traceableSerializer = new TraceableSerializer(new Serializer(), $dataCollector); + $traceableSerializer = new TraceableSerializer(new Serializer(), $dataCollector, $serializerName); $traceableSerializer->serialize('data', 'format'); $traceableSerializer->deserialize('data', 'type', 'format'); @@ -117,7 +119,7 @@ public function testAddDebugTraceIdInContext() }); } - $traceableSerializer = new TraceableSerializer($serializer, new SerializerDataCollector()); + $traceableSerializer = new TraceableSerializer($serializer, new SerializerDataCollector(), 'default'); $traceableSerializer->serialize('data', 'format'); $traceableSerializer->deserialize('data', 'format', 'type'); diff --git a/Tests/DependencyInjection/SerializerPassTest.php b/Tests/DependencyInjection/SerializerPassTest.php index 037eafdb..b721b1ba 100644 --- a/Tests/DependencyInjection/SerializerPassTest.php +++ b/Tests/DependencyInjection/SerializerPassTest.php @@ -17,7 +17,9 @@ use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\Serializer\Debug\TraceableEncoder; use Symfony\Component\Serializer\Debug\TraceableNormalizer; +use Symfony\Component\Serializer\Debug\TraceableSerializer; use Symfony\Component\Serializer\DependencyInjection\SerializerPass; +use Symfony\Component\Serializer\SerializerInterface; /** * Tests for the SerializerPass class. @@ -94,7 +96,7 @@ public function testBindSerializerDefaultContext() $this->assertEquals($bindings['array $defaultContext'], new BoundArgument(['enable_max_depth' => true], false)); } - public function testNormalizersAndEncodersAreDecoredAndOrderedWhenCollectingData() + public function testNormalizersAndEncodersAreDecoratedAndOrderedWhenCollectingData() { $container = new ContainerBuilder(); @@ -114,9 +116,525 @@ public function testNormalizersAndEncodersAreDecoredAndOrderedWhenCollectingData $this->assertEquals(TraceableNormalizer::class, $traceableNormalizerDefinition->getClass()); $this->assertEquals(new Reference('n'), $traceableNormalizerDefinition->getArgument(0)); $this->assertEquals(new Reference('serializer.data_collector'), $traceableNormalizerDefinition->getArgument(1)); + $this->assertSame('default', $traceableNormalizerDefinition->getArgument(2)); $this->assertEquals(TraceableEncoder::class, $traceableEncoderDefinition->getClass()); $this->assertEquals(new Reference('e'), $traceableEncoderDefinition->getArgument(0)); $this->assertEquals(new Reference('serializer.data_collector'), $traceableEncoderDefinition->getArgument(1)); + $this->assertSame('default', $traceableEncoderDefinition->getArgument(2)); + } + + /** + * @dataProvider provideDefaultSerializerTagsData + */ + public function testDefaultSerializerTagsAreResolvedCorrectly( + array $normalizerTagAttributes, + array $encoderTagAttributes, + array $expectedNormalizerTags, + array $expectedEncoderTags, + ) { + $container = new ContainerBuilder(); + + $container->setParameter('kernel.debug', false); + $container->setParameter('.serializer.named_serializers', []); + + $container->register('serializer')->setArguments([null, null]); + $container->register('n0')->addTag('serializer.normalizer', ['serializer' => 'default']); + $container->register('e0')->addTag('serializer.encoder', ['serializer' => 'default']); + + $normalizerDefinition = $container->register('n1')->addTag('serializer.normalizer', $normalizerTagAttributes); + $encoderDefinition = $container->register('e1')->addTag('serializer.encoder', $encoderTagAttributes); + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $this->assertSame($expectedNormalizerTags, $normalizerDefinition->getTag('serializer.normalizer.default')); + $this->assertSame($expectedEncoderTags, $encoderDefinition->getTag('serializer.encoder.default')); + } + + public static function provideDefaultSerializerTagsData(): iterable + { + yield 'include no name' => [ + [], + [], + [[]], + [[]], + ]; + + yield 'include name' => [ + ['serializer' => 'default'], + ['serializer' => 'default'], + [[]], + [[]], + ]; + + yield 'include built-in with different name' => [ + ['built_in' => true, 'serializer' => 'api'], + ['built_in' => true, 'serializer' => 'api'], + [[]], + [[]], + ]; + + yield 'include no name with priority' => [ + ['priority' => 200], + ['priority' => 100], + [['priority' => 200]], + [['priority' => 100]], + ]; + + yield 'include name with priority' => [ + ['serializer' => 'default', 'priority' => 200], + ['serializer' => 'default', 'priority' => 100], + [['priority' => 200]], + [['priority' => 100]], + ]; + + yield 'include wildcard' => [ + ['serializer' => '*'], + ['serializer' => '*'], + [[]], + [[]], + ]; + + yield 'is unique when built-in with name' => [ + ['built_in' => true, 'serializer' => 'default'], + ['built_in' => true, 'serializer' => 'default'], + [[]], + [[]], + ]; + + yield 'do not include different name' => [ + ['serializer' => 'api'], + ['serializer' => 'api'], + [], + [], + ]; + } + + /** + * @dataProvider provideNamedSerializerTagsData + */ + public function testNamedSerializerTagsAreResolvedCorrectly( + array $config, + array $normalizerTagAttributes, + array $encoderTagAttributes, + array $expectedNormalizerTags, + array $expectedEncoderTags, + ) { + $container = new ContainerBuilder(); + + $container->setParameter('kernel.debug', false); + $container->setParameter('.serializer.named_serializers', ['api' => $config]); + + $container->register('serializer')->setArguments([null, null]); + $container->register('n0')->addTag('serializer.normalizer', ['serializer' => ['default', 'api']]); + $container->register('e0')->addTag('serializer.encoder', ['serializer' => ['default', 'api']]); + + $normalizerDefinition = $container->register('n1')->addTag('serializer.normalizer', $normalizerTagAttributes); + $encoderDefinition = $container->register('e1')->addTag('serializer.encoder', $encoderTagAttributes); + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $this->assertSame($expectedNormalizerTags, $normalizerDefinition->getTag('serializer.normalizer.api')); + $this->assertSame($expectedEncoderTags, $encoderDefinition->getTag('serializer.encoder.api')); + } + + public static function provideNamedSerializerTagsData(): iterable + { + yield 'include built-in' => [ + ['include_built_in_normalizers' => true, 'include_built_in_encoders' => true], + ['built_in' => true], + ['built_in' => true], + [[]], + [[]], + ]; + + yield 'include built-in normalizers only' => [ + ['include_built_in_normalizers' => true, 'include_built_in_encoders' => false], + ['built_in' => true], + ['built_in' => true], + [[]], + [], + ]; + + yield 'include built-in encoders only' => [ + ['include_built_in_normalizers' => false, 'include_built_in_encoders' => true], + ['built_in' => true], + ['built_in' => true], + [], + [[]], + ]; + + yield 'include name' => [ + ['include_built_in_normalizers' => false, 'include_built_in_encoders' => false], + ['serializer' => 'api'], + ['serializer' => 'api'], + [[]], + [[]], + ]; + + yield 'include name with priority' => [ + ['include_built_in_normalizers' => false, 'include_built_in_encoders' => false], + ['serializer' => 'api', 'priority' => 200], + ['serializer' => 'api', 'priority' => 100], + [['priority' => 200]], + [['priority' => 100]], + ]; + + yield 'include wildcard' => [ + ['include_built_in_normalizers' => false, 'include_built_in_encoders' => false], + ['serializer' => '*'], + ['serializer' => '*'], + [[]], + [[]], + ]; + + yield 'do not include when include built-in not set' => [ + [], + ['built_in' => true], + ['built_in' => true], + [], + [], + ]; + + yield 'do not include not built-in and no name' => [ + ['include_built_in_normalizers' => false, 'include_built_in_encoders' => false], + [], + [], + [], + [], + ]; + + yield 'do not include different name' => [ + ['include_built_in_normalizers' => false, 'include_built_in_encoders' => false], + ['serializer' => 'api2'], + ['serializer' => 'api2'], + [], + [], + ]; + } + + public function testMultipleNamedSerializerTagsAreResolvedCorrectly() + { + $container = new ContainerBuilder(); + + $container->setParameter('kernel.debug', false); + $container->setParameter('.serializer.named_serializers', [ + 'api' => [], + 'api2' => [], + ]); + + $container->register('serializer')->setArguments([null, null]); + $container->register('n0')->addTag('serializer.normalizer', ['serializer' => 'default']); + $container->register('e0')->addTag('serializer.encoder', ['serializer' => 'default']); + + $normalizerDefinition = $container->register('n1')->addTag('serializer.normalizer', ['serializer' => ['api', 'api2']]); + $encoderDefinition = $container->register('e1') + ->addTag('serializer.encoder', ['serializer' => ['api', 'api2']]) + ->addTag('serializer.encoder', ['serializer' => ['api', 'api2'], 'priority' => 100]) + ; + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $this->assertTrue($normalizerDefinition->hasTag('serializer.normalizer.api')); + $this->assertCount(1, $normalizerDefinition->getTag('serializer.normalizer.api')); + $this->assertTrue($normalizerDefinition->hasTag('serializer.normalizer.api2')); + $this->assertCount(1, $normalizerDefinition->getTag('serializer.normalizer.api2')); + + $this->assertTrue($encoderDefinition->hasTag('serializer.encoder.api')); + $this->assertCount(2, $encoderDefinition->getTag('serializer.encoder.api')); + $this->assertTrue($encoderDefinition->hasTag('serializer.encoder.api2')); + $this->assertCount(2, $encoderDefinition->getTag('serializer.encoder.api2')); + } + + public function testThrowExceptionWhenNoNormalizersForNamedSerializers() + { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); + $container->setParameter('.serializer.named_serializers', [ + 'api' => [], + ]); + + $container->register('serializer')->setArguments([null, null]); + $container->register('n0')->addTag('serializer.normalizer'); + $container->register('e0')->addTag('serializer.encoder', ['serializer' => '*']); + + $serializerPass = new SerializerPass(); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('The named serializer "api" requires at least one registered normalizer. Tag the normalizers as "serializer.normalizer" with the "serializer" attribute set to "api".'); + + $serializerPass->process($container); + } + + public function testThrowExceptionWhenNoEncodersForNamedSerializers() + { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); + $container->setParameter('.serializer.named_serializers', [ + 'api' => [], + ]); + + $container->register('serializer')->setArguments([null, null]); + $container->register('n0')->addTag('serializer.normalizer', ['serializer' => '*']); + $container->register('e0')->addTag('serializer.encoder'); + + $serializerPass = new SerializerPass(); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('The named serializer "api" requires at least one registered encoder. Tag the encoders as "serializer.encoder" with the "serializer" attribute set to "api".'); + + $serializerPass->process($container); + } + + /** + * @testWith [null] + * ["some.converter"] + */ + public function testChildNameConverterIsNotBuiltWhenExpected(?string $nameConverter) + { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); + $container->setParameter('.serializer.name_converter', $nameConverter); + $container->setParameter('.serializer.named_serializers', [ + 'api' => ['name_converter' => $nameConverter], + ]); + + $container->register('serializer')->setArguments([null, null]); + $container->register('n')->addTag('serializer.normalizer', ['serializer' => '*']); + $container->register('e')->addTag('serializer.encoder', ['serializer' => '*']); + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $this->assertFalse($container->hasDefinition('serializer.name_converter.metadata_aware.'.ContainerBuilder::hash($nameConverter))); + } + + /** + * @dataProvider provideChildNameConverterCases + */ + public function testChildNameConverterIsBuiltWhenExpected( + ?string $defaultSerializerNameConverter, + ?string $namedSerializerNameConverter, + string $nameConverterIdExists, + string $nameConverterIdDoesNotExist, + array $nameConverterArguments, + ) { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); + $container->setParameter('.serializer.name_converter', $defaultSerializerNameConverter); + $container->setParameter('.serializer.named_serializers', [ + 'api' => ['name_converter' => $namedSerializerNameConverter], + ]); + + $container->register('serializer')->setArguments([null, null]); + $container->register('n')->addTag('serializer.normalizer', ['serializer' => '*']); + $container->register('e')->addTag('serializer.encoder', ['serializer' => '*']); + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $this->assertFalse($container->hasDefinition($nameConverterIdExists)); + $this->assertTrue($container->hasDefinition($nameConverterIdDoesNotExist)); + $this->assertEquals($nameConverterArguments, $container->getDefinition($nameConverterIdDoesNotExist)->getArguments()); + } + + public static function provideChildNameConverterCases(): iterable + { + $withNull = 'serializer.name_converter.metadata_aware.'.ContainerBuilder::hash(null); + $withConverter = 'serializer.name_converter.metadata_aware.'.ContainerBuilder::hash('some.converter'); + + yield [null, 'some.converter', $withNull, $withConverter, [new Reference('some.converter')]]; + yield ['some.converter', null, $withConverter, $withNull, []]; + } + + /** + * @dataProvider provideDifferentNamedSerializerConfigsCases + */ + public function testNamedSerializersCreateNewServices( + array $defaultSerializerDefaultContext, + ?string $defaultSerializerNameConverter, + array $namedSerializerConfig, + string $nameConverterId, + ) { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); + $container->setParameter('serializer.default_context', $defaultSerializerDefaultContext); + $container->setParameter('.serializer.name_converter', $defaultSerializerNameConverter); + $container->setParameter('.serializer.named_serializers', [ + 'api' => $namedSerializerConfig, + ]); + + $container->register('serializer')->setArguments([null, null]); + $container->register('n') + ->addArgument(new Reference('serializer.name_converter.metadata_aware')) + ->addTag('serializer.normalizer', ['serializer' => '*']) + ; + $container->register('e') + ->addArgument(new Reference('serializer.name_converter.metadata_aware')) + ->addTag('serializer.encoder', ['serializer' => '*']) + ; + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $this->assertEquals([new Reference('n.api')], $container->getDefinition('serializer.api')->getArgument(0)); + $this->assertEquals(new Reference($nameConverterId), $container->getDefinition('n.api')->getArgument(0)); + $this->assertEquals([new Reference('e.api')], $container->getDefinition('serializer.api')->getArgument(1)); + $this->assertEquals(new Reference($nameConverterId), $container->getDefinition('e.api')->getArgument(0)); + } + + public static function provideDifferentNamedSerializerConfigsCases(): iterable + { + yield [ + ['a' => true, 'b' => 3], + null, + ['default_context' => ['c' => 3, 'a' => true]], + 'serializer.name_converter.metadata_aware', + ]; + yield [ + [], + 'some.converter', + ['name_converter' => null], + 'serializer.name_converter.metadata_aware.'.ContainerBuilder::hash(null), + ]; + yield [ + ['a' => true, 'b' => 3], + null, + ['default_context' => ['c' => 3, 'a' => true], 'name_converter' => 'some.converter'], + 'serializer.name_converter.metadata_aware.'.ContainerBuilder::hash('some.converter'), + ]; + } + + public function testServicesAreOrderedAccordingToPriorityForNamedSerializers() + { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); + $container->setParameter('.serializer.named_serializers', [ + 'api' => [], + ]); + + $container->register('serializer')->setArguments([null, null]); + $container->register('n2') + ->addTag('serializer.normalizer', ['serializer' => '*', 'priority' => 100]) + ->addTag('serializer.encoder', ['serializer' => '*', 'priority' => 100]) + ; + $container->register('n1') + ->addTag('serializer.normalizer', ['serializer' => 'api', 'priority' => 200]) + ->addTag('serializer.encoder', ['serializer' => 'api', 'priority' => 200]) + ; + $container->register('n3') + ->addTag('serializer.normalizer', ['serializer' => 'api']) + ->addTag('serializer.encoder', ['serializer' => 'api']) + ; + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $this->assertTrue($container->hasDefinition('serializer.api')); + $definition = $container->getDefinition('serializer.api'); + + $expected = [ + new Reference('n1.api'), + new Reference('n2.api'), + new Reference('n3.api'), + ]; + $this->assertEquals($expected, $definition->getArgument(0)); + $this->assertEquals($expected, $definition->getArgument(1)); + } + + public function testBindSerializerDefaultContextToNamedSerializers() + { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); + $container->setParameter('.serializer.named_serializers', [ + 'api' => ['default_context' => $defaultContext = ['enable_max_depth' => true]], + ]); + + $container->register('serializer')->setArguments([null, null]); + $definition = $container->register('n1') + ->addTag('serializer.normalizer', ['serializer' => '*']) + ->addTag('serializer.encoder', ['serializer' => '*']) + ; + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $this->assertEmpty($definition->getBindings()); + + $bindings = $container->getDefinition('n1.api')->getBindings(); + $this->assertArrayHasKey('array $defaultContext', $bindings); + $this->assertEquals($bindings['array $defaultContext'], new BoundArgument($defaultContext, false)); + } + + public function testNamedSerializersAreRegistered() + { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); + $container->setParameter('.serializer.named_serializers', [ + 'api' => [], + 'api2' => [], + ]); + + $container->register('serializer')->setArguments([null, null]); + $container->register('n')->addTag('serializer.normalizer', ['serializer' => '*']); + $container->register('e')->addTag('serializer.encoder', ['serializer' => '*']); + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $this->assertFalse($container->hasAlias(\sprintf('%s $defaultSerializer', SerializerInterface::class))); + + $this->assertTrue($container->hasDefinition('serializer.api')); + $this->assertTrue($container->hasAlias(\sprintf('%s $apiSerializer', SerializerInterface::class))); + $this->assertTrue($container->hasDefinition('serializer.api2')); + $this->assertTrue($container->hasAlias(\sprintf('%s $api2Serializer', SerializerInterface::class))); + } + + public function testNormalizersAndEncodersAreDecoratedAndOrderedWhenCollectingDataForNamedSerializers() + { + $container = new ContainerBuilder(); + + $container->setParameter('kernel.debug', true); + $container->setParameter('.serializer.named_serializers', [ + 'api' => ['default_context' => ['enable_max_depth' => true]], + ]); + $container->register('serializer.data_collector'); + + $container->register('serializer')->setArguments([null, null]); + $container->register('n')->addTag('serializer.normalizer', ['serializer' => '*']); + $container->register('e')->addTag('serializer.encoder', ['serializer' => '*']); + + $container->register('debug.serializer', TraceableSerializer::class) + ->setDecoratedService('serializer') + ->setArguments([ + new Reference('debug.serializer.inner'), + new Reference('serializer.data_collector'), + 'default', + ]) + ; + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $traceableNormalizerDefinition = $container->getDefinition('.debug.serializer.normalizer.n.api'); + $traceableEncoderDefinition = $container->getDefinition('.debug.serializer.encoder.e.api'); + + $traceableSerializerDefinition = $container->getDefinition('debug.serializer.api'); + $this->assertSame('serializer.api', $traceableSerializerDefinition->getDecoratedService()[0]); + $this->assertEquals(new Reference('debug.serializer.api.inner'), $traceableSerializerDefinition->getArgument(0)); + $this->assertSame('api', $traceableSerializerDefinition->getArgument(2)); + + $this->assertEquals(TraceableNormalizer::class, $traceableNormalizerDefinition->getClass()); + $this->assertEquals(new Reference('n.api'), $traceableNormalizerDefinition->getArgument(0)); + $this->assertEquals(new Reference('serializer.data_collector'), $traceableNormalizerDefinition->getArgument(1)); + $this->assertSame('api', $traceableNormalizerDefinition->getArgument(2)); + + $this->assertEquals(TraceableEncoder::class, $traceableEncoderDefinition->getClass()); + $this->assertEquals(new Reference('e.api'), $traceableEncoderDefinition->getArgument(0)); + $this->assertEquals(new Reference('serializer.data_collector'), $traceableEncoderDefinition->getArgument(1)); + $this->assertSame('api', $traceableEncoderDefinition->getArgument(2)); } } diff --git a/composer.json b/composer.json index 0092a964..4d3af935 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "symfony/cache": "^6.4|^7.0", "symfony/config": "^6.4|^7.0", "symfony/console": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", + "symfony/dependency-injection": "^7.2", "symfony/error-handler": "^6.4|^7.0", "symfony/filesystem": "^6.4|^7.0", "symfony/form": "^6.4|^7.0", From 267cddf01bc284d6079818e71294b7452e2e4dab Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Thu, 19 Sep 2024 09:56:35 +0200 Subject: [PATCH 23/38] Switch to ExpectUserDeprecationMessageTrait --- Tests/Context/Encoder/CsvEncoderContextBuilderTest.php | 8 ++++---- Tests/Encoder/CsvEncoderTest.php | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Tests/Context/Encoder/CsvEncoderContextBuilderTest.php b/Tests/Context/Encoder/CsvEncoderContextBuilderTest.php index bcaaf2a8..fe39feb8 100644 --- a/Tests/Context/Encoder/CsvEncoderContextBuilderTest.php +++ b/Tests/Context/Encoder/CsvEncoderContextBuilderTest.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Serializer\Tests\Context\Encoder; use PHPUnit\Framework\TestCase; -use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; +use Symfony\Bridge\PhpUnit\ExpectUserDeprecationMessageTrait; use Symfony\Component\Serializer\Context\Encoder\CsvEncoderContextBuilder; use Symfony\Component\Serializer\Encoder\CsvEncoder; use Symfony\Component\Serializer\Exception\InvalidArgumentException; @@ -22,7 +22,7 @@ */ class CsvEncoderContextBuilderTest extends TestCase { - use ExpectDeprecationTrait; + use ExpectUserDeprecationMessageTrait; private CsvEncoderContextBuilder $contextBuilder; @@ -127,7 +127,7 @@ public function testCannotSetMultipleBytesAsEnclosure() */ public function testCannotSetMultipleBytesAsEscapeChar() { - $this->expectDeprecation('Since symfony/serializer 7.2: The "Symfony\Component\Serializer\Context\Encoder\CsvEncoderContextBuilder::withEscapeChar" method is deprecated. It will be removed in 8.0.'); + $this->expectUserDeprecationMessage('Since symfony/serializer 7.2: The "Symfony\Component\Serializer\Context\Encoder\CsvEncoderContextBuilder::withEscapeChar" method is deprecated. It will be removed in 8.0.'); $this->expectException(InvalidArgumentException::class); $this->contextBuilder->withEscapeChar('ọ'); @@ -138,7 +138,7 @@ public function testCannotSetMultipleBytesAsEscapeChar() */ public function testWithEscapeCharIsDeprecated() { - $this->expectDeprecation('Since symfony/serializer 7.2: The "Symfony\Component\Serializer\Context\Encoder\CsvEncoderContextBuilder::withEscapeChar" method is deprecated. It will be removed in 8.0.'); + $this->expectUserDeprecationMessage('Since symfony/serializer 7.2: The "Symfony\Component\Serializer\Context\Encoder\CsvEncoderContextBuilder::withEscapeChar" method is deprecated. It will be removed in 8.0.'); $context = $this->contextBuilder->withEscapeChar('\\'); $this->assertSame(['csv_escape_char' => '\\'], $context->toArray()); diff --git a/Tests/Encoder/CsvEncoderTest.php b/Tests/Encoder/CsvEncoderTest.php index e250d1c6..048d790b 100644 --- a/Tests/Encoder/CsvEncoderTest.php +++ b/Tests/Encoder/CsvEncoderTest.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Serializer\Tests\Encoder; use PHPUnit\Framework\TestCase; -use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; +use Symfony\Bridge\PhpUnit\ExpectUserDeprecationMessageTrait; use Symfony\Component\Serializer\Encoder\CsvEncoder; use Symfony\Component\Serializer\Exception\UnexpectedValueException; @@ -21,7 +21,7 @@ */ class CsvEncoderTest extends TestCase { - use ExpectDeprecationTrait; + use ExpectUserDeprecationMessageTrait; private CsvEncoder $encoder; @@ -713,7 +713,7 @@ public function testEndOfLinePassedInConstructor() */ public function testPassingNonEmptyEscapeCharIsDeprecated() { - $this->expectDeprecation('Since symfony/serializer 7.2: Setting the "csv_escape_char" option is deprecated. The option will be removed in 8.0.'); + $this->expectUserDeprecationMessage('Since symfony/serializer 7.2: Setting the "csv_escape_char" option is deprecated. The option will be removed in 8.0.'); $encoder = new CsvEncoder(['csv_escape_char' => '@']); $this->assertSame( From 8f02a9837542c71b91f40a6cf7f38fa67b2b5f29 Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Wed, 18 Sep 2024 19:28:51 +0200 Subject: [PATCH 24/38] [Serializer] Deprecate `AdvancedNameConverterInterface` --- CHANGELOG.md | 1 + NameConverter/AdvancedNameConverterInterface.php | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79a14b50..a04c323d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ CHANGELOG * Add support for configuring multiple serializer instances with different default contexts, name converters, sets of normalizers and encoders * Add support for collection profiles of multiple serializer instances + * Deprecate `AdvancedNameConverterInterface`, use `NameConverterInterface` instead 7.1 --- diff --git a/NameConverter/AdvancedNameConverterInterface.php b/NameConverter/AdvancedNameConverterInterface.php index 1e74f4d2..975d28fd 100644 --- a/NameConverter/AdvancedNameConverterInterface.php +++ b/NameConverter/AdvancedNameConverterInterface.php @@ -15,6 +15,8 @@ * Gives access to the class, the format and the context in the property name converters. * * @author Kévin Dunglas + * + * @deprecated since Symfony 7.2, use NameConverterInterface instead */ interface AdvancedNameConverterInterface extends NameConverterInterface { From e4c7264a86225ccf7e71cda8070667f30e968d06 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Thu, 26 Sep 2024 10:09:09 +0200 Subject: [PATCH 25/38] Remove unused imports --- Tests/Fixtures/DummyObjectWithEnumProperty.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/Tests/Fixtures/DummyObjectWithEnumProperty.php b/Tests/Fixtures/DummyObjectWithEnumProperty.php index f2677195..70c6cff7 100644 --- a/Tests/Fixtures/DummyObjectWithEnumProperty.php +++ b/Tests/Fixtures/DummyObjectWithEnumProperty.php @@ -2,8 +2,6 @@ namespace Symfony\Component\Serializer\Tests\Fixtures; -use Symfony\Component\Serializer\Tests\Fixtures\StringBackedEnumDummy; - class DummyObjectWithEnumProperty { public StringBackedEnumDummy $get; From e71f70329e19a18607ea0a53391a4837e10218c8 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Thu, 3 Oct 2024 14:15:19 +0200 Subject: [PATCH 26/38] Various CS fix for consistency --- Tests/Normalizer/AbstractObjectNormalizerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Normalizer/AbstractObjectNormalizerTest.php b/Tests/Normalizer/AbstractObjectNormalizerTest.php index 28324c48..499fa8bf 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -1230,7 +1230,7 @@ public static function provideDenormalizeWithFilterBoolData(): array public function testDenormalizeArrayObject() { - $normalizer = new class() extends AbstractObjectNormalizerDummy { + $normalizer = new class extends AbstractObjectNormalizerDummy { public function __construct() { parent::__construct(null, null, new PhpDocExtractor()); From 7c0c971e7cd929e6b34bf991cc16f245979885e6 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Mon, 14 Oct 2024 20:03:05 +0200 Subject: [PATCH 27/38] Reduce common control flows --- Command/DebugCommand.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Command/DebugCommand.php b/Command/DebugCommand.php index 3e70c93a..7df4d6bc 100644 --- a/Command/DebugCommand.php +++ b/Command/DebugCommand.php @@ -75,15 +75,14 @@ private function dumpSerializerDataForClass(InputInterface $input, OutputInterfa ]; } + $io->section($title); + if (!$rows) { - $io->section($title); $io->text('No Serializer data were found for this class.'); return; } - $io->section($title); - $table = new Table($output); $table->setHeaders(['Property', 'Options']); $table->setRows($rows); From b8f4851e37b94914bf269f32a720a203e7501a52 Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Wed, 14 Aug 2024 15:51:51 +0200 Subject: [PATCH 28/38] [TypeInfo] Redesign Type methods and nullability --- Normalizer/AbstractObjectNormalizer.php | 41 +++++++++++++++---------- Normalizer/ArrayDenormalizer.php | 7 ++++- composer.json | 4 +-- 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 9db24129..82aaa290 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -32,12 +32,14 @@ use Symfony\Component\Serializer\Mapping\ClassMetadataInterface; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; -use Symfony\Component\TypeInfo\Exception\LogicException as TypeInfoLogicException; use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\Type\BuiltinType; use Symfony\Component\TypeInfo\Type\CollectionType; use Symfony\Component\TypeInfo\Type\IntersectionType; +use Symfony\Component\TypeInfo\Type\NullableType; use Symfony\Component\TypeInfo\Type\ObjectType; use Symfony\Component\TypeInfo\Type\UnionType; +use Symfony\Component\TypeInfo\Type\WrappingTypeInterface; use Symfony\Component\TypeInfo\TypeIdentifier; /** @@ -644,11 +646,9 @@ private function validateAndDenormalizeLegacy(array $types, string $currentClass private function validateAndDenormalize(Type $type, string $currentClass, string $attribute, mixed $data, ?string $format, array $context): mixed { $expectedTypes = []; - $isUnionType = $type->asNonNullable() instanceof UnionType; $e = null; $extraAttributesException = null; $missingConstructorArgumentsException = null; - $isNullable = false; $types = match (true) { $type instanceof IntersectionType => throw new LogicException('Unable to handle intersection type.'), @@ -667,11 +667,13 @@ private function validateAndDenormalize(Type $type, string $currentClass, string $collectionValueType = $t->getCollectionValueType(); } - $t = $t->getBaseType(); + while ($t instanceof WrappingTypeInterface) { + $t = $t->getWrappedType(); + } // Fix a collection that contains the only one element // This is special to xml format only - if ('xml' === $format && $collectionValueType && !$collectionValueType->isA(TypeIdentifier::MIXED) && (!\is_array($data) || !\is_int(key($data)))) { + if ('xml' === $format && $collectionValueType && !$collectionValueType->isIdentifiedBy(TypeIdentifier::MIXED) && (!\is_array($data) || !\is_int(key($data)))) { $data = [$data]; } @@ -694,8 +696,6 @@ private function validateAndDenormalize(Type $type, string $currentClass, string if (TypeIdentifier::STRING === $typeIdentifier) { return ''; } - - $isNullable = $isNullable ?: $type->isNullable(); } switch ($typeIdentifier) { @@ -731,10 +731,9 @@ private function validateAndDenormalize(Type $type, string $currentClass, string } if ($collectionValueType) { - try { - $collectionValueBaseType = $collectionValueType->getBaseType(); - } catch (TypeInfoLogicException) { - $collectionValueBaseType = Type::mixed(); + $collectionValueBaseType = $collectionValueType; + while ($collectionValueBaseType instanceof WrappingTypeInterface) { + $collectionValueBaseType = $collectionValueBaseType->getWrappedType(); } if ($collectionValueBaseType instanceof ObjectType) { @@ -742,15 +741,25 @@ private function validateAndDenormalize(Type $type, string $currentClass, string $class = $collectionValueBaseType->getClassName().'[]'; $context['key_type'] = $collectionKeyType; $context['value_type'] = $collectionValueType; - } elseif (TypeIdentifier::ARRAY === $collectionValueBaseType->getTypeIdentifier()) { + } elseif ($collectionValueBaseType instanceof BuiltinType && TypeIdentifier::ARRAY === $collectionValueBaseType->getTypeIdentifier()) { // get inner type for any nested array $innerType = $collectionValueType; + if ($innerType instanceof NullableType) { + $innerType = $innerType->getWrappedType(); + } // note that it will break for any other builtinType $dimensions = '[]'; while ($innerType instanceof CollectionType) { $dimensions .= '[]'; $innerType = $innerType->getCollectionValueType(); + if ($innerType instanceof NullableType) { + $innerType = $innerType->getWrappedType(); + } + } + + while ($innerType instanceof WrappingTypeInterface) { + $innerType = $innerType->getWrappedType(); } if ($innerType instanceof ObjectType) { @@ -832,17 +841,17 @@ private function validateAndDenormalize(Type $type, string $currentClass, string return $data; } } catch (NotNormalizableValueException|InvalidArgumentException $e) { - if (!$isUnionType && !$isNullable) { + if (!$type instanceof UnionType) { throw $e; } } catch (ExtraAttributesException $e) { - if (!$isUnionType && !$isNullable) { + if (!$type instanceof UnionType) { throw $e; } $extraAttributesException ??= $e; } catch (MissingConstructorArgumentsException $e) { - if (!$isUnionType && !$isNullable) { + if (!$type instanceof UnionType) { throw $e; } @@ -862,7 +871,7 @@ private function validateAndDenormalize(Type $type, string $currentClass, string throw $missingConstructorArgumentsException; } - if (!$isUnionType && $e) { + if ($e && !($type instanceof UnionType && !$type instanceof NullableType)) { throw $e; } diff --git a/Normalizer/ArrayDenormalizer.php b/Normalizer/ArrayDenormalizer.php index 94de2de3..08fae04d 100644 --- a/Normalizer/ArrayDenormalizer.php +++ b/Normalizer/ArrayDenormalizer.php @@ -16,7 +16,9 @@ use Symfony\Component\Serializer\Exception\InvalidArgumentException; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\Type\BuiltinType; use Symfony\Component\TypeInfo\Type\UnionType; +use Symfony\Component\TypeInfo\TypeIdentifier; /** * Denormalizes arrays of objects. @@ -54,7 +56,10 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a $typeIdentifiers = []; if (null !== $keyType = ($context['key_type'] ?? null)) { if ($keyType instanceof Type) { - $typeIdentifiers = array_map(fn (Type $t): string => $t->getBaseType()->getTypeIdentifier()->value, $keyType instanceof UnionType ? $keyType->getTypes() : [$keyType]); + /** @var list|BuiltinType> */ + $keyTypes = $keyType instanceof UnionType ? $keyType->getTypes() : [$keyType]; + + $typeIdentifiers = array_map(fn (BuiltinType $t): string => $t->getTypeIdentifier()->value, $keyTypes); } else { $typeIdentifiers = array_map(fn (LegacyType $t): string => $t->getBuiltinType(), \is_array($keyType) ? $keyType : [$keyType]); } diff --git a/composer.json b/composer.json index 8691e224..bb325dfe 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,7 @@ "symfony/property-access": "^6.4|^7.0", "symfony/property-info": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3", - "symfony/type-info": "^7.1.5", + "symfony/type-info": "^7.2", "symfony/uid": "^6.4|^7.0", "symfony/validator": "^6.4|^7.0", "symfony/var-dumper": "^6.4|^7.0", @@ -51,7 +51,7 @@ "symfony/dependency-injection": "<6.4", "symfony/property-access": "<6.4", "symfony/property-info": "<6.4", - "symfony/type-info": "<7.1.5", + "symfony/type-info": "<7.2", "symfony/uid": "<6.4", "symfony/validator": "<6.4", "symfony/yaml": "<6.4" From 56bcd41f8e8792b15ca363b730e26c7c536a7873 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Thu, 10 Oct 2024 12:40:40 +0200 Subject: [PATCH 29/38] [Serializer] Improve `AttributeLoader` --- Mapping/Loader/AttributeLoader.php | 69 +++++++++++++----------------- 1 file changed, 30 insertions(+), 39 deletions(-) diff --git a/Mapping/Loader/AttributeLoader.php b/Mapping/Loader/AttributeLoader.php index 272e236b..6bcbed36 100644 --- a/Mapping/Loader/AttributeLoader.php +++ b/Mapping/Loader/AttributeLoader.php @@ -58,23 +58,12 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool $attributesMetadata = $classMetadata->getAttributesMetadata(); foreach ($this->loadAttributes($reflectionClass) as $annotation) { - if ($annotation instanceof DiscriminatorMap) { - $classMetadata->setClassDiscriminatorMapping(new ClassDiscriminatorMapping( - $annotation->getTypeProperty(), - $annotation->getMapping() - )); - continue; - } - - if ($annotation instanceof Groups) { - $classGroups = $annotation->getGroups(); - - continue; - } - - if ($annotation instanceof Context) { - $classContextAnnotation = $annotation; - } + match (true) { + $annotation instanceof DiscriminatorMap => $classMetadata->setClassDiscriminatorMapping(new ClassDiscriminatorMapping($annotation->getTypeProperty(), $annotation->getMapping())), + $annotation instanceof Groups => $classGroups = $annotation->getGroups(), + $annotation instanceof Context => $classContextAnnotation = $annotation, + default => null, + }; } foreach ($reflectionClass->getProperties() as $property) { @@ -83,33 +72,35 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool $classMetadata->addAttributeMetadata($attributesMetadata[$property->name]); } + $attributeMetadata = $attributesMetadata[$property->name]; if ($property->getDeclaringClass()->name === $className) { if ($classContextAnnotation) { - $this->setAttributeContextsForGroups($classContextAnnotation, $attributesMetadata[$property->name]); + $this->setAttributeContextsForGroups($classContextAnnotation, $attributeMetadata); } foreach ($classGroups as $group) { - $attributesMetadata[$property->name]->addGroup($group); + $attributeMetadata->addGroup($group); } foreach ($this->loadAttributes($property) as $annotation) { + $loaded = true; + if ($annotation instanceof Groups) { foreach ($annotation->getGroups() as $group) { - $attributesMetadata[$property->name]->addGroup($group); + $attributeMetadata->addGroup($group); } - } elseif ($annotation instanceof MaxDepth) { - $attributesMetadata[$property->name]->setMaxDepth($annotation->getMaxDepth()); - } elseif ($annotation instanceof SerializedName) { - $attributesMetadata[$property->name]->setSerializedName($annotation->getSerializedName()); - } elseif ($annotation instanceof SerializedPath) { - $attributesMetadata[$property->name]->setSerializedPath($annotation->getSerializedPath()); - } elseif ($annotation instanceof Ignore) { - $attributesMetadata[$property->name]->setIgnore(true); - } elseif ($annotation instanceof Context) { - $this->setAttributeContextsForGroups($annotation, $attributesMetadata[$property->name]); + + continue; } - $loaded = true; + match (true) { + $annotation instanceof MaxDepth => $attributeMetadata->setMaxDepth($annotation->getMaxDepth()), + $annotation instanceof SerializedName => $attributeMetadata->setSerializedName($annotation->getSerializedName()), + $annotation instanceof SerializedPath => $attributeMetadata->setSerializedPath($annotation->getSerializedPath()), + $annotation instanceof Ignore => $attributeMetadata->setIgnore(true), + $annotation instanceof Context => $this->setAttributeContextsForGroups($annotation, $attributeMetadata), + default => null, + }; } } } @@ -206,17 +197,17 @@ private function loadAttributes(\ReflectionMethod|\ReflectionClass|\ReflectionPr private function setAttributeContextsForGroups(Context $annotation, AttributeMetadataInterface $attributeMetadata): void { - if ($annotation->getContext()) { - $attributeMetadata->setNormalizationContextForGroups($annotation->getContext(), $annotation->getGroups()); - $attributeMetadata->setDenormalizationContextForGroups($annotation->getContext(), $annotation->getGroups()); - } + $context = $annotation->getContext(); + $groups = $annotation->getGroups(); + $normalizationContext = $annotation->getNormalizationContext(); + $denormalizationContext = $annotation->getDenormalizationContext(); - if ($annotation->getNormalizationContext()) { - $attributeMetadata->setNormalizationContextForGroups($annotation->getNormalizationContext(), $annotation->getGroups()); + if ($normalizationContext || $context) { + $attributeMetadata->setNormalizationContextForGroups($normalizationContext ?: $context, $groups); } - if ($annotation->getDenormalizationContext()) { - $attributeMetadata->setDenormalizationContextForGroups($annotation->getDenormalizationContext(), $annotation->getGroups()); + if ($denormalizationContext || $context) { + $attributeMetadata->setDenormalizationContextForGroups($denormalizationContext ?: $context, $groups); } } From 96421e664c2c3077a1e0858a02e62a7a4d66ce79 Mon Sep 17 00:00:00 2001 From: Antoine Lamirault Date: Wed, 16 Oct 2024 22:04:16 +0200 Subject: [PATCH 30/38] [Serializer] Rename annotations to attributes in AttributeLoader --- Mapping/Loader/AttributeLoader.php | 64 +++++++++---------- .../{Annotation => Attribute}/ContextTest.php | 2 +- .../DiscriminatorMapTest.php | 8 +-- .../{Annotation => Attribute}/GroupsTest.php | 2 +- .../MaxDepthTest.php | 2 +- .../SerializedNameTest.php | 2 +- .../SerializedPathTest.php | 2 +- Tests/Fixtures/Attributes/GroupDummy.php | 4 +- ...ditionalGetterWithoutIgnoreAttributes.php} | 2 +- ...my.php => ChildOfGroupsAttributeDummy.php} | 2 +- Tests/Mapping/Loader/AttributeLoaderTest.php | 8 +-- 11 files changed, 49 insertions(+), 49 deletions(-) rename Tests/{Annotation => Attribute}/ContextTest.php (98%) rename Tests/{Annotation => Attribute}/DiscriminatorMapTest.php (82%) rename Tests/{Annotation => Attribute}/GroupsTest.php (95%) rename Tests/{Annotation => Attribute}/MaxDepthTest.php (94%) rename Tests/{Annotation => Attribute}/SerializedNameTest.php (94%) rename Tests/{Annotation => Attribute}/SerializedPathTest.php (95%) rename Tests/Fixtures/Attributes/{IgnoreDummyAdditionalGetterWithoutIgnoreAnnotations.php => IgnoreDummyAdditionalGetterWithoutIgnoreAttributes.php} (85%) rename Tests/Fixtures/{ChildOfGroupsAnnotationDummy.php => ChildOfGroupsAttributeDummy.php} (82%) diff --git a/Mapping/Loader/AttributeLoader.php b/Mapping/Loader/AttributeLoader.php index 6bcbed36..ca92aa88 100644 --- a/Mapping/Loader/AttributeLoader.php +++ b/Mapping/Loader/AttributeLoader.php @@ -53,15 +53,15 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool $className = $reflectionClass->name; $loaded = false; $classGroups = []; - $classContextAnnotation = null; + $classContextAttribute = null; $attributesMetadata = $classMetadata->getAttributesMetadata(); - foreach ($this->loadAttributes($reflectionClass) as $annotation) { + foreach ($this->loadAttributes($reflectionClass) as $attribute) { match (true) { - $annotation instanceof DiscriminatorMap => $classMetadata->setClassDiscriminatorMapping(new ClassDiscriminatorMapping($annotation->getTypeProperty(), $annotation->getMapping())), - $annotation instanceof Groups => $classGroups = $annotation->getGroups(), - $annotation instanceof Context => $classContextAnnotation = $annotation, + $attribute instanceof DiscriminatorMap => $classMetadata->setClassDiscriminatorMapping(new ClassDiscriminatorMapping($attribute->getTypeProperty(), $attribute->getMapping())), + $attribute instanceof Groups => $classGroups = $attribute->getGroups(), + $attribute instanceof Context => $classContextAttribute = $attribute, default => null, }; } @@ -74,19 +74,19 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool $attributeMetadata = $attributesMetadata[$property->name]; if ($property->getDeclaringClass()->name === $className) { - if ($classContextAnnotation) { - $this->setAttributeContextsForGroups($classContextAnnotation, $attributeMetadata); + if ($classContextAttribute) { + $this->setAttributeContextsForGroups($classContextAttribute, $attributeMetadata); } foreach ($classGroups as $group) { $attributeMetadata->addGroup($group); } - foreach ($this->loadAttributes($property) as $annotation) { + foreach ($this->loadAttributes($property) as $attribute) { $loaded = true; - if ($annotation instanceof Groups) { - foreach ($annotation->getGroups() as $group) { + if ($attribute instanceof Groups) { + foreach ($attribute->getGroups() as $group) { $attributeMetadata->addGroup($group); } @@ -94,11 +94,11 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool } match (true) { - $annotation instanceof MaxDepth => $attributeMetadata->setMaxDepth($annotation->getMaxDepth()), - $annotation instanceof SerializedName => $attributeMetadata->setSerializedName($annotation->getSerializedName()), - $annotation instanceof SerializedPath => $attributeMetadata->setSerializedPath($annotation->getSerializedPath()), - $annotation instanceof Ignore => $attributeMetadata->setIgnore(true), - $annotation instanceof Context => $this->setAttributeContextsForGroups($annotation, $attributeMetadata), + $attribute instanceof MaxDepth => $attributeMetadata->setMaxDepth($attribute->getMaxDepth()), + $attribute instanceof SerializedName => $attributeMetadata->setSerializedName($attribute->getSerializedName()), + $attribute instanceof SerializedPath => $attributeMetadata->setSerializedPath($attribute->getSerializedPath()), + $attribute instanceof Ignore => $attributeMetadata->setIgnore(true), + $attribute instanceof Context => $this->setAttributeContextsForGroups($attribute, $attributeMetadata), default => null, }; } @@ -126,43 +126,43 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool } } - foreach ($this->loadAttributes($method) as $annotation) { - if ($annotation instanceof Groups) { + foreach ($this->loadAttributes($method) as $attribute) { + if ($attribute instanceof Groups) { if (!$accessorOrMutator) { throw new MappingException(\sprintf('Groups on "%s::%s()" cannot be added. Groups can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); } - foreach ($annotation->getGroups() as $group) { + foreach ($attribute->getGroups() as $group) { $attributeMetadata->addGroup($group); } - } elseif ($annotation instanceof MaxDepth) { + } elseif ($attribute instanceof MaxDepth) { if (!$accessorOrMutator) { throw new MappingException(\sprintf('MaxDepth on "%s::%s()" cannot be added. MaxDepth can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); } - $attributeMetadata->setMaxDepth($annotation->getMaxDepth()); - } elseif ($annotation instanceof SerializedName) { + $attributeMetadata->setMaxDepth($attribute->getMaxDepth()); + } elseif ($attribute instanceof SerializedName) { if (!$accessorOrMutator) { throw new MappingException(\sprintf('SerializedName on "%s::%s()" cannot be added. SerializedName can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); } - $attributeMetadata->setSerializedName($annotation->getSerializedName()); - } elseif ($annotation instanceof SerializedPath) { + $attributeMetadata->setSerializedName($attribute->getSerializedName()); + } elseif ($attribute instanceof SerializedPath) { if (!$accessorOrMutator) { throw new MappingException(\sprintf('SerializedPath on "%s::%s()" cannot be added. SerializedPath can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); } - $attributeMetadata->setSerializedPath($annotation->getSerializedPath()); - } elseif ($annotation instanceof Ignore) { + $attributeMetadata->setSerializedPath($attribute->getSerializedPath()); + } elseif ($attribute instanceof Ignore) { if ($accessorOrMutator) { $attributeMetadata->setIgnore(true); } - } elseif ($annotation instanceof Context) { + } elseif ($attribute instanceof Context) { if (!$accessorOrMutator) { throw new MappingException(\sprintf('Context on "%s::%s()" cannot be added. Context can only be added on methods beginning with "get", "is", "has" or "set".', $className, $method->name)); } - $this->setAttributeContextsForGroups($annotation, $attributeMetadata); + $this->setAttributeContextsForGroups($attribute, $attributeMetadata); } $loaded = true; @@ -195,12 +195,12 @@ private function loadAttributes(\ReflectionMethod|\ReflectionClass|\ReflectionPr } } - private function setAttributeContextsForGroups(Context $annotation, AttributeMetadataInterface $attributeMetadata): void + private function setAttributeContextsForGroups(Context $attribute, AttributeMetadataInterface $attributeMetadata): void { - $context = $annotation->getContext(); - $groups = $annotation->getGroups(); - $normalizationContext = $annotation->getNormalizationContext(); - $denormalizationContext = $annotation->getDenormalizationContext(); + $context = $attribute->getContext(); + $groups = $attribute->getGroups(); + $normalizationContext = $attribute->getNormalizationContext(); + $denormalizationContext = $attribute->getDenormalizationContext(); if ($normalizationContext || $context) { $attributeMetadata->setNormalizationContextForGroups($normalizationContext ?: $context, $groups); diff --git a/Tests/Annotation/ContextTest.php b/Tests/Attribute/ContextTest.php similarity index 98% rename from Tests/Annotation/ContextTest.php rename to Tests/Attribute/ContextTest.php index 84ff41b8..cfe17505 100644 --- a/Tests/Annotation/ContextTest.php +++ b/Tests/Attribute/ContextTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Serializer\Tests\Annotation; +namespace Symfony\Component\Serializer\Tests\Attribute; use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Attribute\Context; diff --git a/Tests/Annotation/DiscriminatorMapTest.php b/Tests/Attribute/DiscriminatorMapTest.php similarity index 82% rename from Tests/Annotation/DiscriminatorMapTest.php rename to Tests/Attribute/DiscriminatorMapTest.php index bbd112e2..497bc620 100644 --- a/Tests/Annotation/DiscriminatorMapTest.php +++ b/Tests/Attribute/DiscriminatorMapTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Serializer\Tests\Annotation; +namespace Symfony\Component\Serializer\Tests\Attribute; use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Attribute\DiscriminatorMap; @@ -22,16 +22,16 @@ class DiscriminatorMapTest extends TestCase { public function testGetTypePropertyAndMapping() { - $annotation = new DiscriminatorMap(typeProperty: 'type', mapping: [ + $attribute = new DiscriminatorMap(typeProperty: 'type', mapping: [ 'foo' => 'FooClass', 'bar' => 'BarClass', ]); - $this->assertEquals('type', $annotation->getTypeProperty()); + $this->assertEquals('type', $attribute->getTypeProperty()); $this->assertEquals([ 'foo' => 'FooClass', 'bar' => 'BarClass', - ], $annotation->getMapping()); + ], $attribute->getMapping()); } public function testExceptionWithEmptyTypeProperty() diff --git a/Tests/Annotation/GroupsTest.php b/Tests/Attribute/GroupsTest.php similarity index 95% rename from Tests/Annotation/GroupsTest.php rename to Tests/Attribute/GroupsTest.php index 38ec518b..266cbc4f 100644 --- a/Tests/Annotation/GroupsTest.php +++ b/Tests/Attribute/GroupsTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Serializer\Tests\Annotation; +namespace Symfony\Component\Serializer\Tests\Attribute; use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Attribute\Groups; diff --git a/Tests/Annotation/MaxDepthTest.php b/Tests/Attribute/MaxDepthTest.php similarity index 94% rename from Tests/Annotation/MaxDepthTest.php rename to Tests/Attribute/MaxDepthTest.php index f2ff35c8..e611bfbc 100644 --- a/Tests/Annotation/MaxDepthTest.php +++ b/Tests/Attribute/MaxDepthTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Serializer\Tests\Annotation; +namespace Symfony\Component\Serializer\Tests\Attribute; use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Attribute\MaxDepth; diff --git a/Tests/Annotation/SerializedNameTest.php b/Tests/Attribute/SerializedNameTest.php similarity index 94% rename from Tests/Annotation/SerializedNameTest.php rename to Tests/Attribute/SerializedNameTest.php index 3a829aec..c645e7e8 100644 --- a/Tests/Annotation/SerializedNameTest.php +++ b/Tests/Attribute/SerializedNameTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Serializer\Tests\Annotation; +namespace Symfony\Component\Serializer\Tests\Attribute; use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Attribute\SerializedName; diff --git a/Tests/Annotation/SerializedPathTest.php b/Tests/Attribute/SerializedPathTest.php similarity index 95% rename from Tests/Annotation/SerializedPathTest.php rename to Tests/Attribute/SerializedPathTest.php index f5bbfa62..7ba31fc2 100644 --- a/Tests/Annotation/SerializedPathTest.php +++ b/Tests/Attribute/SerializedPathTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Serializer\Tests\Annotation; +namespace Symfony\Component\Serializer\Tests\Attribute; use PHPUnit\Framework\TestCase; use Symfony\Component\PropertyAccess\PropertyPath; diff --git a/Tests/Fixtures/Attributes/GroupDummy.php b/Tests/Fixtures/Attributes/GroupDummy.php index 5c34c95a..dd88507b 100644 --- a/Tests/Fixtures/Attributes/GroupDummy.php +++ b/Tests/Fixtures/Attributes/GroupDummy.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Attributes; use Symfony\Component\Serializer\Attribute\Groups; -use Symfony\Component\Serializer\Tests\Fixtures\ChildOfGroupsAnnotationDummy; +use Symfony\Component\Serializer\Tests\Fixtures\ChildOfGroupsAttributeDummy; /** * @author Kévin Dunglas @@ -23,7 +23,7 @@ class GroupDummy extends GroupDummyParent implements GroupDummyInterface private $foo; #[Groups(['b', 'c', 'name_converter'])] protected $bar; - #[ChildOfGroupsAnnotationDummy] + #[ChildOfGroupsAttributeDummy] protected $quux; private $fooBar; private $symfony; diff --git a/Tests/Fixtures/Attributes/IgnoreDummyAdditionalGetterWithoutIgnoreAnnotations.php b/Tests/Fixtures/Attributes/IgnoreDummyAdditionalGetterWithoutIgnoreAttributes.php similarity index 85% rename from Tests/Fixtures/Attributes/IgnoreDummyAdditionalGetterWithoutIgnoreAnnotations.php rename to Tests/Fixtures/Attributes/IgnoreDummyAdditionalGetterWithoutIgnoreAttributes.php index 21abb870..5d3e8f47 100644 --- a/Tests/Fixtures/Attributes/IgnoreDummyAdditionalGetterWithoutIgnoreAnnotations.php +++ b/Tests/Fixtures/Attributes/IgnoreDummyAdditionalGetterWithoutIgnoreAttributes.php @@ -2,7 +2,7 @@ namespace Symfony\Component\Serializer\Tests\Fixtures\Attributes; -class IgnoreDummyAdditionalGetterWithoutIgnoreAnnotations +class IgnoreDummyAdditionalGetterWithoutIgnoreAttributes { private $myValue; diff --git a/Tests/Fixtures/ChildOfGroupsAnnotationDummy.php b/Tests/Fixtures/ChildOfGroupsAttributeDummy.php similarity index 82% rename from Tests/Fixtures/ChildOfGroupsAnnotationDummy.php rename to Tests/Fixtures/ChildOfGroupsAttributeDummy.php index 9a163012..4fc81fa9 100644 --- a/Tests/Fixtures/ChildOfGroupsAnnotationDummy.php +++ b/Tests/Fixtures/ChildOfGroupsAttributeDummy.php @@ -5,7 +5,7 @@ use Symfony\Component\Serializer\Attribute\Groups; #[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)] -final class ChildOfGroupsAnnotationDummy extends Groups +final class ChildOfGroupsAttributeDummy extends Groups { public function __construct() { diff --git a/Tests/Mapping/Loader/AttributeLoaderTest.php b/Tests/Mapping/Loader/AttributeLoaderTest.php index 5b6ef3de..fd7f2b72 100644 --- a/Tests/Mapping/Loader/AttributeLoaderTest.php +++ b/Tests/Mapping/Loader/AttributeLoaderTest.php @@ -33,7 +33,7 @@ use Symfony\Component\Serializer\Tests\Fixtures\Attributes\GroupDummyParent; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\IgnoreDummy; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\IgnoreDummyAdditionalGetter; -use Symfony\Component\Serializer\Tests\Fixtures\Attributes\IgnoreDummyAdditionalGetterWithoutIgnoreAnnotations; +use Symfony\Component\Serializer\Tests\Fixtures\Attributes\IgnoreDummyAdditionalGetterWithoutIgnoreAttributes; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\MaxDepthDummy; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\SerializedNameDummy; use Symfony\Component\Serializer\Tests\Fixtures\Attributes\SerializedPathDummy; @@ -181,7 +181,7 @@ public function testCanHandleUnrelatedIgnoredMethods() $this->assertSame(['id'], array_keys($metadata->getAttributesMetadata())); } - public function testIgnoreGetterWithRequiredParameterIfIgnoreAnnotationIsUsed() + public function testIgnoreGetterWithRequiredParameterIfIgnoreAttributeIsUsed() { $classMetadata = new ClassMetadata(IgnoreDummyAdditionalGetter::class); $this->getLoaderForContextMapping()->loadClassMetadata($classMetadata); @@ -191,9 +191,9 @@ public function testIgnoreGetterWithRequiredParameterIfIgnoreAnnotationIsUsed() self::assertArrayHasKey('extraValue2', $attributes); } - public function testIgnoreGetterWithRequiredParameterIfIgnoreAnnotationIsNotUsed() + public function testIgnoreGetterWithRequiredParameterIfIgnoreAttributeIsNotUsed() { - $classMetadata = new ClassMetadata(IgnoreDummyAdditionalGetterWithoutIgnoreAnnotations::class); + $classMetadata = new ClassMetadata(IgnoreDummyAdditionalGetterWithoutIgnoreAttributes::class); $this->getLoaderForContextMapping()->loadClassMetadata($classMetadata); $attributes = $classMetadata->getAttributesMetadata(); From 5fff3abe545e26b2e024d18ad6e3f797e649f513 Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Thu, 14 Nov 2024 13:01:20 +0100 Subject: [PATCH 31/38] [TypeInfo][Serializer][PropertyInfo][Validator] TypeInfo 7.1 compatibility --- Normalizer/AbstractObjectNormalizer.php | 59 ++++++++++++++++++++----- Normalizer/ArrayDenormalizer.php | 13 ++++-- composer.json | 3 +- 3 files changed, 59 insertions(+), 16 deletions(-) diff --git a/Normalizer/AbstractObjectNormalizer.php b/Normalizer/AbstractObjectNormalizer.php index 3dbb750f..fb45a924 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -32,6 +32,7 @@ use Symfony\Component\Serializer\Mapping\ClassMetadataInterface; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\NameConverter\NameConverterInterface; +use Symfony\Component\TypeInfo\Exception\LogicException as TypeInfoLogicException; use Symfony\Component\TypeInfo\Type; use Symfony\Component\TypeInfo\Type\BuiltinType; use Symfony\Component\TypeInfo\Type\CollectionType; @@ -646,6 +647,14 @@ private function validateAndDenormalizeLegacy(array $types, string $currentClass private function validateAndDenormalize(Type $type, string $currentClass, string $attribute, mixed $data, ?string $format, array $context): mixed { $expectedTypes = []; + + // BC layer for type-info < 7.2 + if (method_exists(Type::class, 'asNonNullable')) { + $isUnionType = $type->asNonNullable() instanceof UnionType; + } else { + $isUnionType = $type instanceof UnionType; + } + $e = null; $extraAttributesException = null; $missingConstructorArgumentsException = null; @@ -667,14 +676,23 @@ private function validateAndDenormalize(Type $type, string $currentClass, string $collectionValueType = $t->getCollectionValueType(); } - while ($t instanceof WrappingTypeInterface) { - $t = $t->getWrappedType(); + // BC layer for type-info < 7.2 + if (method_exists(Type::class, 'getBaseType')) { + $t = $t->getBaseType(); + } else { + while ($t instanceof WrappingTypeInterface) { + $t = $t->getWrappedType(); + } } // Fix a collection that contains the only one element // This is special to xml format only - if ('xml' === $format && $collectionValueType && !$collectionValueType->isIdentifiedBy(TypeIdentifier::MIXED) && (!\is_array($data) || !\is_int(key($data)))) { - $data = [$data]; + if ('xml' === $format && $collectionValueType && (!\is_array($data) || !\is_int(key($data)))) { + // BC layer for type-info < 7.2 + $isMixedType = method_exists(Type::class, 'isA') ? $collectionValueType->isA(TypeIdentifier::MIXED) : $collectionValueType->isIdentifiedBy(TypeIdentifier::MIXED); + if (!$isMixedType) { + $data = [$data]; + } } // This try-catch should cover all NotNormalizableValueException (and all return branches after the first @@ -731,9 +749,19 @@ private function validateAndDenormalize(Type $type, string $currentClass, string } if ($collectionValueType) { - $collectionValueBaseType = $collectionValueType; - while ($collectionValueBaseType instanceof WrappingTypeInterface) { - $collectionValueBaseType = $collectionValueBaseType->getWrappedType(); + try { + $collectionValueBaseType = $collectionValueType; + + // BC layer for type-info < 7.2 + if (!interface_exists(WrappingTypeInterface::class)) { + $collectionValueBaseType = $collectionValueType->getBaseType(); + } else { + while ($collectionValueBaseType instanceof WrappingTypeInterface) { + $collectionValueBaseType = $collectionValueBaseType->getWrappedType(); + } + } + } catch (TypeInfoLogicException) { + $collectionValueBaseType = Type::mixed(); } if ($collectionValueBaseType instanceof ObjectType) { @@ -741,7 +769,11 @@ private function validateAndDenormalize(Type $type, string $currentClass, string $class = $collectionValueBaseType->getClassName().'[]'; $context['key_type'] = $collectionKeyType; $context['value_type'] = $collectionValueType; - } elseif ($collectionValueBaseType instanceof BuiltinType && TypeIdentifier::ARRAY === $collectionValueBaseType->getTypeIdentifier()) { + } elseif ( + // BC layer for type-info < 7.2 + !class_exists(NullableType::class) && TypeIdentifier::ARRAY === $collectionValueBaseType->getTypeIdentifier() + || $collectionValueBaseType instanceof BuiltinType && TypeIdentifier::ARRAY === $collectionValueBaseType->getTypeIdentifier() + ) { // get inner type for any nested array $innerType = $collectionValueType; if ($innerType instanceof NullableType) { @@ -871,8 +903,15 @@ private function validateAndDenormalize(Type $type, string $currentClass, string throw $missingConstructorArgumentsException; } - if ($e && !($type instanceof UnionType && !$type instanceof NullableType)) { - throw $e; + // BC layer for type-info < 7.2 + if (!class_exists(NullableType::class)) { + if (!$isUnionType && $e) { + throw $e; + } + } else { + if ($e && !($type instanceof UnionType && !$type instanceof NullableType)) { + throw $e; + } } if ($context[self::DISABLE_TYPE_ENFORCEMENT] ?? $this->defaultContext[self::DISABLE_TYPE_ENFORCEMENT] ?? false) { diff --git a/Normalizer/ArrayDenormalizer.php b/Normalizer/ArrayDenormalizer.php index 08fae04d..96c4d259 100644 --- a/Normalizer/ArrayDenormalizer.php +++ b/Normalizer/ArrayDenormalizer.php @@ -56,10 +56,15 @@ public function denormalize(mixed $data, string $type, ?string $format = null, a $typeIdentifiers = []; if (null !== $keyType = ($context['key_type'] ?? null)) { if ($keyType instanceof Type) { - /** @var list|BuiltinType> */ - $keyTypes = $keyType instanceof UnionType ? $keyType->getTypes() : [$keyType]; - - $typeIdentifiers = array_map(fn (BuiltinType $t): string => $t->getTypeIdentifier()->value, $keyTypes); + // BC layer for type-info < 7.2 + if (method_exists(Type::class, 'getBaseType')) { + $typeIdentifiers = array_map(fn (Type $t): string => $t->getBaseType()->getTypeIdentifier()->value, $keyType instanceof UnionType ? $keyType->getTypes() : [$keyType]); + } else { + /** @var list|BuiltinType> */ + $keyTypes = $keyType instanceof UnionType ? $keyType->getTypes() : [$keyType]; + + $typeIdentifiers = array_map(fn (BuiltinType $t): string => $t->getTypeIdentifier()->value, $keyTypes); + } } else { $typeIdentifiers = array_map(fn (LegacyType $t): string => $t->getBuiltinType(), \is_array($keyType) ? $keyType : [$keyType]); } diff --git a/composer.json b/composer.json index 4e686552..d8809fa0 100644 --- a/composer.json +++ b/composer.json @@ -38,7 +38,7 @@ "symfony/property-access": "^6.4|^7.0", "symfony/property-info": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3", - "symfony/type-info": "^7.2", + "symfony/type-info": "^7.1", "symfony/uid": "^6.4|^7.0", "symfony/validator": "^6.4|^7.0", "symfony/var-dumper": "^6.4|^7.0", @@ -51,7 +51,6 @@ "symfony/dependency-injection": "<6.4", "symfony/property-access": "<6.4", "symfony/property-info": "<6.4", - "symfony/type-info": "<7.2", "symfony/uid": "<6.4", "symfony/validator": "<6.4", "symfony/yaml": "<6.4" From 81f032d2ee6a3cd8b75990941a1e3f87c8ad086e Mon Sep 17 00:00:00 2001 From: wanxiangchwng Date: Sat, 23 Nov 2024 10:47:03 +0800 Subject: [PATCH 32/38] chore: fix some typos Signed-off-by: wanxiangchwng --- Tests/Encoder/XmlEncoderTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Encoder/XmlEncoderTest.php b/Tests/Encoder/XmlEncoderTest.php index 31d2ddfc..0eb332e8 100644 --- a/Tests/Encoder/XmlEncoderTest.php +++ b/Tests/Encoder/XmlEncoderTest.php @@ -149,7 +149,7 @@ public static function validEncodeProvider(): iterable ], ]; - yield 'encode remvoing empty tags' => [ + yield 'encode removing empty tags' => [ ''."\n". 'Peter'."\n", ['person' => ['firstname' => 'Peter', 'lastname' => null]], From c9a49af37c46114a884a9c0945f7f75a3e8b1ec0 Mon Sep 17 00:00:00 2001 From: Dariusz Ruminski Date: Mon, 25 Nov 2024 01:26:19 +0100 Subject: [PATCH 33/38] CS: re-apply trailing_comma_in_multiline --- Tests/Normalizer/ObjectNormalizerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Normalizer/ObjectNormalizerTest.php b/Tests/Normalizer/ObjectNormalizerTest.php index 93ed5e46..d45586b4 100644 --- a/Tests/Normalizer/ObjectNormalizerTest.php +++ b/Tests/Normalizer/ObjectNormalizerTest.php @@ -976,7 +976,7 @@ public function testNormalizeWithMethodNamesSimilarToAccessors() 'tell' => true, 'class' => true, 'responsibility' => true, - 123 => 321 + 123 => 321, ], $normalized); } } From 3f5ed9f5e6c02e3853109190ba38408f5e1d2dd0 Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Sat, 16 Nov 2024 15:49:06 +0100 Subject: [PATCH 34/38] Proofread UPGRADE guide --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b7a1fac..4c36d588 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,8 @@ CHANGELOG 7.2 --- - * Deprecate the `csv_escape_char` context option of `CsvEncoder` and the `CsvEncoder::ESCAPE_CHAR_KEY` constant - * Deprecate `CsvEncoderContextBuilder::withEscapeChar()` method + * Deprecate the `csv_escape_char` context option of `CsvEncoder`, the `CsvEncoder::ESCAPE_CHAR_KEY` constant + and the `CsvEncoderContextBuilder::withEscapeChar()` method, following its deprecation in PHP 8.4 * Add `SnakeCaseToCamelCaseNameConverter` * Support subclasses of `\DateTime` and `\DateTimeImmutable` for denormalization * Add the `UidNormalizer::NORMALIZATION_FORMAT_RFC9562` constant From ed5e8b563e10496a4f62557fbcb52b4191f2157d Mon Sep 17 00:00:00 2001 From: HypeMC Date: Wed, 5 Feb 2025 08:18:49 +0100 Subject: [PATCH 35/38] [Serializer] Handle default context in named Serializer --- DependencyInjection/SerializerPass.php | 2 +- Tests/DependencyInjection/SerializerPassTest.php | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/DependencyInjection/SerializerPass.php b/DependencyInjection/SerializerPass.php index bf1296e9..7b7f6f1c 100644 --- a/DependencyInjection/SerializerPass.php +++ b/DependencyInjection/SerializerPass.php @@ -151,7 +151,7 @@ private function configureNamedSerializers(ContainerBuilder $container): void $this->bindDefaultContext($container, array_merge($normalizers, $encoders), $config['default_context']); - $container->registerChild($serializerId, 'serializer'); + $container->registerChild($serializerId, 'serializer')->setArgument('$defaultContext', $config['default_context']); $container->registerAliasForArgument($serializerId, SerializerInterface::class, $serializerName.'.serializer'); $this->configureSerializer($container, $serializerId, $normalizers, $encoders, $serializerName); diff --git a/Tests/DependencyInjection/SerializerPassTest.php b/Tests/DependencyInjection/SerializerPassTest.php index ca54460a..769243be 100644 --- a/Tests/DependencyInjection/SerializerPassTest.php +++ b/Tests/DependencyInjection/SerializerPassTest.php @@ -556,7 +556,7 @@ public function testBindSerializerDefaultContextToNamedSerializers() 'api' => ['default_context' => $defaultContext = ['enable_max_depth' => true]], ]); - $container->register('serializer')->setArguments([null, null]); + $container->register('serializer')->setArguments([null, null, []]); $definition = $container->register('n1') ->addTag('serializer.normalizer', ['serializer' => '*']) ->addTag('serializer.encoder', ['serializer' => '*']) @@ -570,6 +570,8 @@ public function testBindSerializerDefaultContextToNamedSerializers() $bindings = $container->getDefinition('n1.api')->getBindings(); $this->assertArrayHasKey('array $defaultContext', $bindings); $this->assertEquals($bindings['array $defaultContext'], new BoundArgument($defaultContext, false)); + $this->assertArrayNotHasKey('$defaultContext', $container->getDefinition('serializer')->getArguments()); + $this->assertEquals($defaultContext, $container->getDefinition('serializer.api')->getArgument('$defaultContext')); } public function testNamedSerializersAreRegistered() From a221b2f6066af304d760cff7a26f201b4fab4aef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Hlavat=C3=BD?= <107676055+Pepperoni1337@users.noreply.github.com> Date: Sun, 23 Feb 2025 11:21:59 +0100 Subject: [PATCH 36/38] Update GetSetMethodNormalizer.php Fix: Add length check for setter method detection --- Normalizer/GetSetMethodNormalizer.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Normalizer/GetSetMethodNormalizer.php b/Normalizer/GetSetMethodNormalizer.php index 95100554..3cb9b992 100644 --- a/Normalizer/GetSetMethodNormalizer.php +++ b/Normalizer/GetSetMethodNormalizer.php @@ -118,6 +118,7 @@ private function isSetMethod(\ReflectionMethod $method): bool return !$method->isStatic() && !$method->getAttributes(Ignore::class) && 0 < $method->getNumberOfParameters() + && 3 < \strlen($method->name) && str_starts_with($method->name, 'set') && !ctype_lower($method->name[3]) ; From d8b75b2c8144c29ac43b235738411f7cca6d584d Mon Sep 17 00:00:00 2001 From: HypeMC Date: Mon, 24 Mar 2025 05:35:59 +0100 Subject: [PATCH 37/38] [Serializer] Fix ObjectNormalizer default context with named serializers --- DependencyInjection/SerializerPass.php | 38 +++++++++--- .../SerializerPassTest.php | 62 ++++++++++++++++++- 2 files changed, 92 insertions(+), 8 deletions(-) diff --git a/DependencyInjection/SerializerPass.php b/DependencyInjection/SerializerPass.php index 7b7f6f1c..179b7a3d 100644 --- a/DependencyInjection/SerializerPass.php +++ b/DependencyInjection/SerializerPass.php @@ -19,6 +19,7 @@ use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\Serializer\Debug\TraceableEncoder; use Symfony\Component\Serializer\Debug\TraceableNormalizer; +use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\SerializerInterface; /** @@ -54,17 +55,27 @@ public function process(ContainerBuilder $container): void throw new RuntimeException('You must tag at least one service as "serializer.encoder" to use the "serializer" service.'); } + $defaultContext = []; if ($container->hasParameter('serializer.default_context')) { $defaultContext = $container->getParameter('serializer.default_context'); - $this->bindDefaultContext($container, array_merge($normalizers, $encoders), $defaultContext); $container->getParameterBag()->remove('serializer.default_context'); $container->getDefinition('serializer')->setArgument('$defaultContext', $defaultContext); } + /** @var ?string $circularReferenceHandler */ + $circularReferenceHandler = $container->hasParameter('.serializer.circular_reference_handler') + ? $container->getParameter('.serializer.circular_reference_handler') : null; + + /** @var ?string $maxDepthHandler */ + $maxDepthHandler = $container->hasParameter('.serializer.max_depth_handler') + ? $container->getParameter('.serializer.max_depth_handler') : null; + + $this->bindDefaultContext($container, array_merge($normalizers, $encoders), $defaultContext, $circularReferenceHandler, $maxDepthHandler); + $this->configureSerializer($container, 'serializer', $normalizers, $encoders, 'default'); if ($namedSerializers) { - $this->configureNamedSerializers($container); + $this->configureNamedSerializers($container, $circularReferenceHandler, $maxDepthHandler); } } @@ -98,11 +109,22 @@ private function createNamedSerializerTags(ContainerBuilder $container, string $ } } - private function bindDefaultContext(ContainerBuilder $container, array $services, array $defaultContext): void + private function bindDefaultContext(ContainerBuilder $container, array $services, array $defaultContext, ?string $circularReferenceHandler, ?string $maxDepthHandler): void { foreach ($services as $id) { $definition = $container->getDefinition((string) $id); - $definition->setBindings(['array $defaultContext' => new BoundArgument($defaultContext, false)] + $definition->getBindings()); + + $context = $defaultContext; + if (is_a($definition->getClass(), ObjectNormalizer::class, true)) { + if (null !== $circularReferenceHandler) { + $context += ['circular_reference_handler' => new Reference($circularReferenceHandler)]; + } + if (null !== $maxDepthHandler) { + $context += ['max_depth_handler' => new Reference($maxDepthHandler)]; + } + } + + $definition->setBindings(['array $defaultContext' => new BoundArgument($context, false)] + $definition->getBindings()); } } @@ -125,7 +147,7 @@ private function configureSerializer(ContainerBuilder $container, string $id, ar $serializerDefinition->replaceArgument(1, $encoders); } - private function configureNamedSerializers(ContainerBuilder $container): void + private function configureNamedSerializers(ContainerBuilder $container, ?string $circularReferenceHandler, ?string $maxDepthHandler): void { $defaultSerializerNameConverter = $container->hasParameter('.serializer.name_converter') ? $container->getParameter('.serializer.name_converter') : null; @@ -149,7 +171,7 @@ private function configureNamedSerializers(ContainerBuilder $container): void $normalizers = $this->buildChildDefinitions($container, $serializerName, $normalizers, $config); $encoders = $this->buildChildDefinitions($container, $serializerName, $encoders, $config); - $this->bindDefaultContext($container, array_merge($normalizers, $encoders), $config['default_context']); + $this->bindDefaultContext($container, array_merge($normalizers, $encoders), $config['default_context'], $circularReferenceHandler, $maxDepthHandler); $container->registerChild($serializerId, 'serializer')->setArgument('$defaultContext', $config['default_context']); $container->registerAliasForArgument($serializerId, SerializerInterface::class, $serializerName.'.serializer'); @@ -184,7 +206,9 @@ private function buildChildDefinitions(ContainerBuilder $container, string $seri foreach ($services as &$id) { $childId = $id.'.'.$serializerName; - $definition = $container->registerChild($childId, (string) $id); + $definition = $container->registerChild($childId, (string) $id) + ->setClass($container->getDefinition((string) $id)->getClass()) + ; if (null !== $nameConverterIndex = $this->findNameConverterIndex($container, (string) $id)) { $definition->replaceArgument($nameConverterIndex, new Reference($config['name_converter'])); diff --git a/Tests/DependencyInjection/SerializerPassTest.php b/Tests/DependencyInjection/SerializerPassTest.php index 769243be..88ec02b8 100644 --- a/Tests/DependencyInjection/SerializerPassTest.php +++ b/Tests/DependencyInjection/SerializerPassTest.php @@ -19,6 +19,7 @@ use Symfony\Component\Serializer\Debug\TraceableNormalizer; use Symfony\Component\Serializer\Debug\TraceableSerializer; use Symfony\Component\Serializer\DependencyInjection\SerializerPass; +use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\SerializerInterface; /** @@ -99,6 +100,32 @@ public function testBindSerializerDefaultContext() $this->assertEquals($context, $container->getDefinition('serializer')->getArgument('$defaultContext')); } + /** + * @testWith [{}, {}] + * [{"serializer.default_context": {"enable_max_depth": true}}, {"enable_max_depth": true}] + * [{".serializer.circular_reference_handler": "foo"}, {"circular_reference_handler": "foo"}] + * [{".serializer.max_depth_handler": "bar"}, {"max_depth_handler": "bar"}] + * [{"serializer.default_context": {"enable_max_depth": true}, ".serializer.circular_reference_handler": "foo", ".serializer.max_depth_handler": "bar"}, {"enable_max_depth": true, "circular_reference_handler": "foo", "max_depth_handler": "bar"}] + */ + public function testBindObjectNormalizerDefaultContext(array $parameters, array $context) + { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); + $container->register('serializer')->setArguments([null, null, []]); + $container->getParameterBag()->add($parameters); + $definition = $container->register('serializer.normalizer.object') + ->setClass(ObjectNormalizer::class) + ->addTag('serializer.normalizer') + ->addTag('serializer.encoder') + ; + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $bindings = $definition->getBindings(); + $this->assertEquals($bindings['array $defaultContext'], new BoundArgument($context, false)); + } + public function testNormalizersAndEncodersAreDecoratedAndOrderedWhenCollectingData() { $container = new ContainerBuilder(); @@ -565,7 +592,9 @@ public function testBindSerializerDefaultContextToNamedSerializers() $serializerPass = new SerializerPass(); $serializerPass->process($container); - $this->assertEmpty($definition->getBindings()); + $bindings = $definition->getBindings(); + $this->assertArrayHasKey('array $defaultContext', $bindings); + $this->assertEquals($bindings['array $defaultContext'], new BoundArgument([], false)); $bindings = $container->getDefinition('n1.api')->getBindings(); $this->assertArrayHasKey('array $defaultContext', $bindings); @@ -574,6 +603,37 @@ public function testBindSerializerDefaultContextToNamedSerializers() $this->assertEquals($defaultContext, $container->getDefinition('serializer.api')->getArgument('$defaultContext')); } + /** + * @testWith [{}, {}, {}] + * [{"enable_max_depth": true}, {}, {"enable_max_depth": true}] + * [{}, {".serializer.circular_reference_handler": "foo"}, {"circular_reference_handler": "foo"}] + * [{}, {".serializer.max_depth_handler": "bar"}, {"max_depth_handler": "bar"}] + * [{"enable_max_depth": true}, {".serializer.circular_reference_handler": "foo", ".serializer.max_depth_handler": "bar"}, {"enable_max_depth": true, "circular_reference_handler": "foo", "max_depth_handler": "bar"}] + */ + public function testBindNamedSerializerObjectNormalizerDefaultContext(array $defaultContext, array $parameters, array $context) + { + $container = new ContainerBuilder(); + $container->setParameter('kernel.debug', false); + $container->setParameter('.serializer.named_serializers', [ + 'api' => ['default_context' => $defaultContext], + ]); + + $container->register('serializer')->setArguments([null, null, []]); + $container->getParameterBag()->add($parameters); + $container->register('serializer.normalizer.object') + ->setClass(ObjectNormalizer::class) + ->addTag('serializer.normalizer', ['serializer' => '*']) + ->addTag('serializer.encoder', ['serializer' => '*']) + ; + + $serializerPass = new SerializerPass(); + $serializerPass->process($container); + + $bindings = $container->getDefinition('serializer.normalizer.object.api')->getBindings(); + $this->assertArrayHasKey('array $defaultContext', $bindings); + $this->assertEquals($bindings['array $defaultContext'], new BoundArgument($context, false)); + } + public function testNamedSerializersAreRegistered() { $container = new ContainerBuilder(); From c45f8f7763afb11e85772c0c1debb8f272c17f51 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 27 Apr 2025 15:26:02 +0200 Subject: [PATCH 38/38] Remove unneeded use statements --- Tests/SerializerTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/SerializerTest.php b/Tests/SerializerTest.php index 8a8a54e9..da5ccc15 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -62,7 +62,6 @@ use Symfony\Component\Serializer\Tests\Fixtures\DummyObjectWithEnumProperty; use Symfony\Component\Serializer\Tests\Fixtures\DummyWithObjectOrNull; use Symfony\Component\Serializer\Tests\Fixtures\DummyWithVariadicParameter; -use Symfony\Component\Serializer\Tests\Fixtures\DummyWithVariadicProperty; use Symfony\Component\Serializer\Tests\Fixtures\FalseBuiltInDummy; use Symfony\Component\Serializer\Tests\Fixtures\FooImplementationDummy; use Symfony\Component\Serializer\Tests\Fixtures\FooInterfaceDummyDenormalizer;