diff --git a/Attribute/Context.php b/Attribute/Context.php index 5ea2d2eb5..892af481a 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 a77fc2984..48d0842aa 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 39914f971..8747949a4 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 17562b6c2..16d6a8120 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 6acbebd03..f1c6cefe2 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 118e3adbe..71254bfb9 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/CHANGELOG.md b/CHANGELOG.md index 463572046..4c36d5885 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,19 @@ CHANGELOG ========= +7.2 +--- + + * 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 + * 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/Command/DebugCommand.php b/Command/DebugCommand.php index c85ee213e..7df4d6bc8 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); @@ -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); diff --git a/Context/Encoder/CsvEncoderContextBuilder.php b/Context/Encoder/CsvEncoderContextBuilder.php index f75759031..9f0d6da6f 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); @@ -62,12 +62,16 @@ 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)); + 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 cb5b0544d..a63e1a507 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 a27f00c5b..45b47bc1b 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 e2d289e60..de83b1245 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 1d889e502..b809fe3eb 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 5beb4e984..2945cb961 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/DataCollector/SerializerDataCollector.php b/DataCollector/SerializerDataCollector.php index 2880dea37..e87c51ca1 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 afefee0ee..39e75e34f 100644 --- a/Debug/TraceableEncoder.php +++ b/Debug/TraceableEncoder.php @@ -30,13 +30,14 @@ class TraceableEncoder implements EncoderInterface, DecoderInterface, Serializer public function __construct( private EncoderInterface|DecoderInterface $encoder, private SerializerDataCollector $dataCollector, + private readonly string $serializerName = 'default', ) { } 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); @@ -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; @@ -62,7 +63,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); @@ -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 1aa788049..1b143e295 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', ) { } @@ -42,7 +43,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); @@ -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; @@ -68,7 +69,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); @@ -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 898e89560..a05bf4bf8 100644 --- a/Debug/TraceableSerializer.php +++ b/Debug/TraceableSerializer.php @@ -32,12 +32,13 @@ class TraceableSerializer implements SerializerInterface, NormalizerInterface, D public function __construct( private SerializerInterface&NormalizerInterface&DenormalizerInterface&EncoderInterface&DecoderInterface $serializer, private SerializerDataCollector $dataCollector, + private readonly string $serializerName = 'default', ) { } 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); @@ -45,14 +46,14 @@ 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; } 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); @@ -60,14 +61,14 @@ 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; } 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); @@ -75,14 +76,14 @@ 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; } 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); @@ -90,14 +91,14 @@ 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; } 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); @@ -105,14 +106,14 @@ 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; } 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); @@ -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 8c1e0a7bb..179b7a3d9 100644 --- a/DependencyInjection/SerializerPass.php +++ b/DependencyInjection/SerializerPass.php @@ -19,6 +19,8 @@ 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; /** * Adds all services with the tags "serializer.encoder" and "serializer.normalizer" as @@ -31,45 +33,201 @@ 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.'); } + $defaultContext = []; 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()); - } - $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, $circularReferenceHandler, $maxDepthHandler); + } + } + + 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); + } + } + } + } + + private function bindDefaultContext(ContainerBuilder $container, array $services, array $defaultContext, ?string $circularReferenceHandler, ?string $maxDepthHandler): void + { + foreach ($services as $id) { + $definition = $container->getDefinition((string) $id); + + $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()); + } + } + + 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, ?string $circularReferenceHandler, ?string $maxDepthHandler): 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'], $circularReferenceHandler, $maxDepthHandler); + + $container->registerChild($serializerId, 'serializer')->setArgument('$defaultContext', $config['default_context']); + $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) + ->setClass($container->getDefinition((string) $id)->getClass()) + ; + + 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/Encoder/ChainDecoder.php b/Encoder/ChainDecoder.php index ad182a9a4..8df5cd8b8 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 5445761f3..3621471b4 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 a3112ae36..3902b5613 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); } @@ -241,7 +248,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 0dbc9f6df..b50538175 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 5dcb2ba7e..e1a816380 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 @@ -389,7 +388,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 +407,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 +456,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 24b031ae3..639808b10 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 4f9ead9a6..03e42fca5 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/Extractor/ObjectPropertyListExtractor.php b/Extractor/ObjectPropertyListExtractor.php index 8422b0786..42a546805 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/ClassMetadata.php b/Mapping/ClassMetadata.php index e68f4c71f..b635ee10b 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 diff --git a/Mapping/Factory/ClassMetadataFactoryCompiler.php b/Mapping/Factory/ClassMetadataFactoryCompiler.php index f01fe9ce2..1e9202b7d 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 7af722d55..d7037f894 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 f4d41c1e6..ec25d7440 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 1284e91e0..13c59d1c3 100644 --- a/Mapping/Loader/AttributeLoader.php +++ b/Mapping/Loader/AttributeLoader.php @@ -53,28 +53,17 @@ 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) { - 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; - } + foreach ($this->loadAttributes($reflectionClass) as $attribute) { + match (true) { + $attribute instanceof DiscriminatorMap => $classMetadata->setClassDiscriminatorMapping(new ClassDiscriminatorMapping($attribute->getTypeProperty(), $attribute->getMapping())), + $attribute instanceof Groups => $classGroups = $attribute->getGroups(), + $attribute instanceof Context => $classContextAttribute = $attribute, + 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]); + if ($classContextAttribute) { + $this->setAttributeContextsForGroups($classContextAttribute, $attributeMetadata); } foreach ($classGroups as $group) { - $attributesMetadata[$property->name]->addGroup($group); + $attributeMetadata->addGroup($group); } - foreach ($this->loadAttributes($property) as $annotation) { - if ($annotation instanceof Groups) { - foreach ($annotation->getGroups() as $group) { - $attributesMetadata[$property->name]->addGroup($group); + foreach ($this->loadAttributes($property) as $attribute) { + $loaded = true; + + if ($attribute instanceof Groups) { + foreach ($attribute->getGroups() as $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) { + $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, + }; } } } @@ -135,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)); + 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)); + 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)); + 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)); + 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)); + 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; @@ -193,30 +184,30 @@ 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); } } } } - private function setAttributeContextsForGroups(Context $annotation, AttributeMetadataInterface $attributeMetadata): void + private function setAttributeContextsForGroups(Context $attribute, AttributeMetadataInterface $attributeMetadata): void { - if ($annotation->getContext()) { - $attributeMetadata->setNormalizationContextForGroups($annotation->getContext(), $annotation->getGroups()); - $attributeMetadata->setDenormalizationContextForGroups($annotation->getContext(), $annotation->getGroups()); - } + $context = $attribute->getContext(); + $groups = $attribute->getGroups(); + $normalizationContext = $attribute->getNormalizationContext(); + $denormalizationContext = $attribute->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); } } diff --git a/Mapping/Loader/FileLoader.php b/Mapping/Loader/FileLoader.php index 7fda4ebd5..ac68d21e8 100644 --- a/Mapping/Loader/FileLoader.php +++ b/Mapping/Loader/FileLoader.php @@ -20,23 +20,20 @@ */ 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)); + 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)); } - - $this->file = $file; } } diff --git a/Mapping/Loader/LoaderChain.php b/Mapping/Loader/LoaderChain.php index e93a0a7b0..41abf8cec 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 b36fb871d..44ba89df1 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 b0398355a..ca71cbcba 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/AdvancedNameConverterInterface.php b/NameConverter/AdvancedNameConverterInterface.php index 1e74f4d20..975d28fd3 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 { diff --git a/NameConverter/CamelCaseToSnakeCaseNameConverter.php b/NameConverter/CamelCaseToSnakeCaseNameConverter.php index 47d69d3ab..033ec94b7 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, diff --git a/NameConverter/MetadataAwareNameConverter.php b/NameConverter/MetadataAwareNameConverter.php index 445ad7422..c72f148eb 100644 --- a/NameConverter/MetadataAwareNameConverter.php +++ b/NameConverter/MetadataAwareNameConverter.php @@ -80,7 +80,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; @@ -124,7 +124,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/NameConverter/SnakeCaseToCamelCaseNameConverter.php b/NameConverter/SnakeCaseToCamelCaseNameConverter.php new file mode 100644 index 000000000..cb93d3e98 --- /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/Normalizer/AbstractNormalizer.php b/Normalizer/AbstractNormalizer.php index d8f796b4c..04f378c46 100644 --- a/Normalizer/AbstractNormalizer.php +++ b/Normalizer/AbstractNormalizer.php @@ -139,22 +139,21 @@ 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'); 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)); } } @@ -200,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])); } /** @@ -217,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; @@ -342,7 +341,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 = []; @@ -404,7 +403,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, @@ -415,7 +414,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()) { @@ -450,7 +449,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(); @@ -467,13 +466,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; @@ -515,12 +514,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 1860425f9..00d2e3b00 100644 --- a/Normalizer/AbstractObjectNormalizer.php +++ b/Normalizer/AbstractObjectNormalizer.php @@ -137,7 +137,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]); @@ -177,7 +177,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; @@ -218,7 +218,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); @@ -344,7 +344,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)); } } @@ -397,7 +397,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, @@ -488,14 +488,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: @@ -507,7 +507,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), }; } } @@ -550,7 +550,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); @@ -633,7 +633,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); } /** @@ -658,7 +658,6 @@ private function validateAndDenormalize(Type $type, string $currentClass, string $e = null; $extraAttributesException = null; $missingConstructorArgumentsException = null; - $isNullable = false; $types = match (true) { $type instanceof IntersectionType => throw new LogicException('Unable to handle intersection type.'), @@ -715,11 +714,6 @@ private function validateAndDenormalize(Type $type, string $currentClass, string if (TypeIdentifier::STRING === $typeIdentifier) { return ''; } - - // BC layer for type-info < 7.2 - if (method_exists(Type::class, 'isNullable')) { - $isNullable = $isNullable ?: $type->isNullable(); - } } switch ($typeIdentifier) { @@ -730,14 +724,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: @@ -749,7 +743,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), }; } } @@ -835,7 +829,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); @@ -879,17 +873,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; } @@ -924,7 +918,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); } /** @@ -1011,7 +1005,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); @@ -1040,7 +1034,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; @@ -1129,7 +1123,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; @@ -1162,11 +1156,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 964d74b61..96c4d259c 100644 --- a/Normalizer/ArrayDenormalizer.php +++ b/Normalizer/ArrayDenormalizer.php @@ -31,11 +31,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]; @@ -50,7 +45,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); @@ -77,7 +72,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']); @@ -90,7 +85,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 (!isset($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, '[]') @@ -112,6 +107,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/BackedEnumNormalizer.php b/Normalizer/BackedEnumNormalizer.php index 504047bc1..3d8e7e7c5 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/ConstraintViolationListNormalizer.php b/Normalizer/ConstraintViolationListNormalizer.php index eeb6ab462..eda3b758e 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 bd1dc822b..5ee076be6 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 @@ -68,10 +64,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 +93,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 +106,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 995d4f6a3..05d1a8529 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 71ce26496..dfc498c19 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; } /** @@ -94,10 +90,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 +117,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; @@ -142,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); } /** @@ -155,7 +151,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/GetSetMethodNormalizer.php b/Normalizer/GetSetMethodNormalizer.php index ec3d7cb10..4d2d7829a 100644 --- a/Normalizer/GetSetMethodNormalizer.php +++ b/Normalizer/GetSetMethodNormalizer.php @@ -100,6 +100,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]) ; @@ -166,7 +167,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/JsonSerializableNormalizer.php b/Normalizer/JsonSerializableNormalizer.php index 1287627fb..31c224175 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 7006ab321..633edf369 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/NormalizerInterface.php b/Normalizer/NormalizerInterface.php index 562f87c28..bbc8a94e7 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. diff --git a/Normalizer/ObjectNormalizer.php b/Normalizer/ObjectNormalizer.php index 4466e7428..1d60cba50 100644 --- a/Normalizer/ObjectNormalizer.php +++ b/Normalizer/ObjectNormalizer.php @@ -179,7 +179,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/Normalizer/ProblemNormalizer.php b/Normalizer/ProblemNormalizer.php index 04c680647..08aca6796 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 e1d893be8..1619f35bf 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 398e00b79..463616e72 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 843d7a550..0bee1f704 100644 --- a/Normalizer/UidNormalizer.php +++ b/Normalizer/UidNormalizer.php @@ -22,7 +22,9 @@ final class UidNormalizer implements NormalizerInterface, DenormalizerInterface public const NORMALIZATION_FORMAT_CANONICAL = 'canonical'; public const NORMALIZATION_FORMAT_BASE58 = 'base58'; public const NORMALIZATION_FORMAT_BASE32 = 'base32'; - public const NORMALIZATION_FORMAT_RFC4122 = 'rfc4122'; // RFC 9562 obsoleted RFC 4122 but the format is the same + public const NORMALIZATION_FORMAT_RFC4122 = 'rfc4122'; + public const NORMALIZATION_FORMAT_RFC9562 = self::NORMALIZATION_FORMAT_RFC4122; // RFC 9562 obsoleted RFC 4122 but the format is the same + public const NORMALIZATION_FORMATS = [ self::NORMALIZATION_FORMAT_CANONICAL, self::NORMALIZATION_FORMAT_BASE58, @@ -56,7 +58,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 +72,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 d61f009d9..f95f2d72e 100644 --- a/Serializer.php +++ b/Serializer.php @@ -96,7 +96,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)); } } @@ -114,7 +114,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); @@ -124,7 +124,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)) { @@ -137,7 +137,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); @@ -178,10 +178,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)))); } /** @@ -199,7 +199,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; @@ -210,7 +210,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]) || isset($this->defaultContext[DenormalizerInterface::COLLECT_DENORMALIZATION_ERRORS])) { diff --git a/Tests/Annotation/ContextTest.php b/Tests/Attribute/ContextTest.php similarity index 94% rename from Tests/Annotation/ContextTest.php rename to Tests/Attribute/ContextTest.php index 9584d6f1b..cfe175050 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; @@ -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/Annotation/DiscriminatorMapTest.php b/Tests/Attribute/DiscriminatorMapTest.php similarity index 82% rename from Tests/Annotation/DiscriminatorMapTest.php rename to Tests/Attribute/DiscriminatorMapTest.php index bbd112e26..497bc6201 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 38ec518bd..266cbc4f4 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 f2ff35c87..e611bfbc4 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 3a829aecf..c645e7e85 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 f5bbfa62b..7ba31fc22 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/Context/ContextBuilderTraitTest.php b/Tests/Context/ContextBuilderTraitTest.php index 17ad231a0..edd3937e2 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/Encoder/CsvEncoderContextBuilderTest.php b/Tests/Context/Encoder/CsvEncoderContextBuilderTest.php index c71d41b63..fe39feb81 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\ExpectUserDeprecationMessageTrait; 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 ExpectUserDeprecationMessageTrait; + 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->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('ọ'); } + + /** + * @group legacy + */ + public function testWithEscapeCharIsDeprecated() + { + $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/Context/Normalizer/AbstractNormalizerContextBuilderTest.php b/Tests/Context/Normalizer/AbstractNormalizerContextBuilderTest.php index 4b8f0cc3f..4e92c54d8 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 410f2972b..c13760118 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/DataCollector/SerializerDataCollectorTest.php b/Tests/DataCollector/SerializerDataCollectorTest.php index aebde8efa..6a26565a8 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 ec38c0ef5..2ac0b8f1f 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 307bc7b6f..56c161392 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 ea3c851c6..d697b270f 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 a15322a1b..88ec02b87 100644 --- a/Tests/DependencyInjection/SerializerPassTest.php +++ b/Tests/DependencyInjection/SerializerPassTest.php @@ -17,7 +17,10 @@ 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\Normalizer\ObjectNormalizer; +use Symfony\Component\Serializer\SerializerInterface; /** * Tests for the SerializerPass class. @@ -97,7 +100,33 @@ public function testBindSerializerDefaultContext() $this->assertEquals($context, $container->getDefinition('serializer')->getArgument('$defaultContext')); } - public function testNormalizersAndEncodersAreDecoredAndOrderedWhenCollectingData() + /** + * @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(); @@ -117,9 +146,560 @@ 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); + + $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); + $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')); + } + + /** + * @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(); + $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/Tests/Encoder/CsvEncoderTest.php b/Tests/Encoder/CsvEncoderTest.php index c0be73a8b..048d790b0 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\ExpectUserDeprecationMessageTrait; use Symfony\Component\Serializer\Encoder\CsvEncoder; use Symfony\Component\Serializer\Exception\UnexpectedValueException; @@ -20,6 +21,8 @@ */ class CsvEncoderTest extends TestCase { + use ExpectUserDeprecationMessageTrait; + 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 => '', 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 => '', CsvEncoder::KEY_SEPARATOR_KEY => '-', ])); } @@ -185,7 +186,6 @@ public function testEncodeCustomSettingsPassedInConstructor() $encoder = new CsvEncoder([ CsvEncoder::DELIMITER_KEY => ';', CsvEncoder::ENCLOSURE_KEY => "'", - CsvEncoder::ESCAPE_CHAR_KEY => '', 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 => '', CsvEncoder::KEY_SEPARATOR_KEY => '-', ]); @@ -596,7 +595,6 @@ public function testDecodeCustomSettingsPassedInContext() , 'csv', [ CsvEncoder::DELIMITER_KEY => ';', CsvEncoder::ENCLOSURE_KEY => "'", - CsvEncoder::ESCAPE_CHAR_KEY => '', CsvEncoder::KEY_SEPARATOR_KEY => '-', ])); } @@ -606,7 +604,6 @@ public function testDecodeCustomSettingsPassedInConstructor() $encoder = new CsvEncoder([ CsvEncoder::DELIMITER_KEY => ';', CsvEncoder::ENCLOSURE_KEY => "'", - CsvEncoder::ESCAPE_CHAR_KEY => '', 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->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( + [[ + 'A, B@"' => 'D', + 'C' => 'E', + ]], + $encoder->decode(<<<'CSV' + "A, B@"", "C" + "D", "E" + CSV, + 'csv' + ) + ); + } } diff --git a/Tests/Encoder/XmlEncoderTest.php b/Tests/Encoder/XmlEncoderTest.php index 5be6be232..0eb332e80 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 removing 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); @@ -671,8 +611,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 +635,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 +670,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')); @@ -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'; @@ -1050,14 +993,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/Fixtures/Attributes/GroupDummy.php b/Tests/Fixtures/Attributes/GroupDummy.php index 749e841a5..41457d796 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 21abb870b..5d3e8f47f 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 9a163012f..4fc81fa9d 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/Fixtures/DummyObjectWithEnumProperty.php b/Tests/Fixtures/DummyObjectWithEnumProperty.php index f2677195f..70c6cff7b 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; diff --git a/Tests/Mapping/Factory/CacheMetadataFactoryTest.php b/Tests/Mapping/Factory/CacheMetadataFactoryTest.php index 6db0b95ae..e18dd707f 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/Mapping/Factory/ClassMetadataFactoryCompilerTest.php b/Tests/Mapping/Factory/ClassMetadataFactoryCompilerTest.php index 20b057211..40dcb5015 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', true).'.php'; + $this->dumpPath = tempnam(sys_get_temp_dir(), 'sf_serializer_metadata_'); } protected function tearDown(): void diff --git a/Tests/Mapping/Loader/AttributeLoaderTest.php b/Tests/Mapping/Loader/AttributeLoaderTest.php index 5a078bf36..2af244a6f 100644 --- a/Tests/Mapping/Loader/AttributeLoaderTest.php +++ b/Tests/Mapping/Loader/AttributeLoaderTest.php @@ -34,7 +34,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; @@ -163,7 +163,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(); @@ -182,7 +182,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); @@ -192,9 +192,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(); diff --git a/Tests/NameConverter/SnakeCaseToCamelCaseNameConverterTest.php b/Tests/NameConverter/SnakeCaseToCamelCaseNameConverterTest.php new file mode 100644 index 000000000..2d2799e2c --- /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]); + } +} diff --git a/Tests/Normalizer/AbstractNormalizerTest.php b/Tests/Normalizer/AbstractNormalizerTest.php index 3108fe3c6..74ca3d6d6 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 27f3c2084..270b65f33 100644 --- a/Tests/Normalizer/AbstractObjectNormalizerTest.php +++ b/Tests/Normalizer/AbstractObjectNormalizerTest.php @@ -529,7 +529,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) { @@ -564,7 +564,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) { @@ -617,7 +617,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) { @@ -870,7 +870,7 @@ public function testDenormalizeWithNumberAsSerializedNameAndNoArrayReindex() '99' => 'baz', ]; - $obj = new class() { + $obj = new class { #[SerializedName('1')] public $foo; @@ -894,7 +894,7 @@ public function testDenormalizeWithCorrectOrderOfAttributeAndProperty() ], ]; - $obj = new class() { + $obj = new class { #[SerializedPath('[data][id]')] public $id; }; @@ -905,7 +905,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]; @@ -985,7 +985,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 @@ -1025,7 +1025,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 @@ -1060,7 +1060,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 @@ -1090,7 +1090,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()))); @@ -1249,7 +1249,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()); diff --git a/Tests/Normalizer/ConstraintViolationListNormalizerTest.php b/Tests/Normalizer/ConstraintViolationListNormalizerTest.php index bb69392f5..1d0afb3cb 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/DateTimeNormalizerTest.php b/Tests/Normalizer/DateTimeNormalizerTest.php index 5dbf36fbe..81219652b 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,11 @@ 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 +{ +} diff --git a/Tests/Normalizer/Features/CallbacksTestTrait.php b/Tests/Normalizer/Features/CallbacksTestTrait.php index 09e2f8867..3a9191ae8 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/Features/CircularReferenceTestTrait.php b/Tests/Normalizer/Features/CircularReferenceTestTrait.php index d02e245f7..85720bcfe 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 72652f340..0a5f6f249 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/MapDenormalizationTest.php b/Tests/Normalizer/MapDenormalizationTest.php index ea4515955..75ecbccb9 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 600fd7cd5..d45586b44 100644 --- a/Tests/Normalizer/ObjectNormalizerTest.php +++ b/Tests/Normalizer/ObjectNormalizerTest.php @@ -747,7 +747,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); @@ -782,15 +782,15 @@ 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']); + 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']); } }; @@ -976,7 +976,7 @@ public function testNormalizeWithMethodNamesSimilarToAccessors() 'tell' => true, 'class' => true, 'responsibility' => true, - 123 => 321 + 123 => 321, ], $normalized); } } diff --git a/Tests/Normalizer/PropertyNormalizerTest.php b/Tests/Normalizer/PropertyNormalizerTest.php index 9ac85920e..ecdae436e 100644 --- a/Tests/Normalizer/PropertyNormalizerTest.php +++ b/Tests/Normalizer/PropertyNormalizerTest.php @@ -336,12 +336,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']] + ) ); } @@ -401,13 +405,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); @@ -446,7 +456,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'], @@ -463,7 +474,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); @@ -531,7 +542,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 1471074a0..734b15b48 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 0a0622576..8fd0ff885 100644 --- a/Tests/SerializerTest.php +++ b/Tests/SerializerTest.php @@ -65,7 +65,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; @@ -428,7 +427,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) { @@ -609,10 +608,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()) { @@ -1216,7 +1215,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); } @@ -1466,8 +1465,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); } diff --git a/composer.json b/composer.json index d7bef296b..d8809fa07 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,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", @@ -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.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.1.5", "symfony/uid": "<6.4", "symfony/validator": "<6.4", "symfony/yaml": "<6.4"