Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

[JsonEncoder] Replace normalizers by value transformers #59290

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ class UnusedTagsPass implements CompilerPassInterface
'form.type_guesser',
'html_sanitizer',
'http_client.client',
'json_encoder.denormalizer',
'json_encoder.normalizer',
'json_encoder.value_transformer',
'kernel.cache_clearer',
'kernel.cache_warmer',
'kernel.event_listener',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,10 @@
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\HttpKernel\Log\DebugLoggerConfigurator;
use Symfony\Component\JsonEncoder\Attribute\JsonEncodable;
use Symfony\Component\JsonEncoder\Decode\Denormalizer\DenormalizerInterface as JsonEncoderDenormalizerInterface;
use Symfony\Component\JsonEncoder\DecoderInterface as JsonEncoderDecoderInterface;
use Symfony\Component\JsonEncoder\Encode\Normalizer\NormalizerInterface as JsonEncoderNormalizerInterface;
use Symfony\Component\JsonEncoder\EncoderInterface as JsonEncoderEncoderInterface;
use Symfony\Component\JsonEncoder\JsonEncoder;
use Symfony\Component\JsonEncoder\ValueTransformer\ValueTransformerInterface;
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Lock\LockInterface;
use Symfony\Component\Lock\PersistingStoreInterface;
Expand Down Expand Up @@ -2027,10 +2026,8 @@ private function registerJsonEncoderConfiguration(array $config, ContainerBuilde
throw new LogicException('JsonEncoder support cannot be enabled as the JsonEncoder component is not installed. Try running "composer require symfony/json-encoder".');
}

$container->registerForAutoconfiguration(JsonEncoderNormalizerInterface::class)
->addTag('json_encoder.normalizer');
$container->registerForAutoconfiguration(JsonEncoderDenormalizerInterface::class)
->addTag('json_encoder.denormalizer');
$container->registerForAutoconfiguration(ValueTransformerInterface::class)
->addTag('json_encoder.value_transformer');

$loader->load('json_encoder.php');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@

use Symfony\Component\JsonEncoder\CacheWarmer\EncoderDecoderCacheWarmer;
use Symfony\Component\JsonEncoder\CacheWarmer\LazyGhostCacheWarmer;
use Symfony\Component\JsonEncoder\Decode\Denormalizer\DateTimeDenormalizer;
use Symfony\Component\JsonEncoder\Encode\Normalizer\DateTimeNormalizer;
use Symfony\Component\JsonEncoder\JsonDecoder;
use Symfony\Component\JsonEncoder\JsonEncoder;
use Symfony\Component\JsonEncoder\Mapping\Decode\AttributePropertyMetadataLoader as DecodeAttributePropertyMetadataLoader;
Expand All @@ -23,19 +21,21 @@
use Symfony\Component\JsonEncoder\Mapping\Encode\DateTimeTypePropertyMetadataLoader as EncodeDateTimeTypePropertyMetadataLoader;
use Symfony\Component\JsonEncoder\Mapping\GenericTypePropertyMetadataLoader;
use Symfony\Component\JsonEncoder\Mapping\PropertyMetadataLoader;
use Symfony\Component\JsonEncoder\ValueTransformer\DateTimeToStringValueTransformer;
use Symfony\Component\JsonEncoder\ValueTransformer\StringToDateTimeValueTransformer;

return static function (ContainerConfigurator $container) {
$container->services()
// encoder/decoder
->set('json_encoder.encoder', JsonEncoder::class)
->args([
tagged_locator('json_encoder.normalizer'),
tagged_locator('json_encoder.value_transformer'),
service('json_encoder.encode.property_metadata_loader'),
param('.json_encoder.encoders_dir'),
])
->set('json_encoder.decoder', JsonDecoder::class)
->args([
tagged_locator('json_encoder.denormalizer'),
tagged_locator('json_encoder.value_transformer'),
service('json_encoder.decode.property_metadata_loader'),
param('.json_encoder.decoders_dir'),
param('.json_encoder.lazy_ghosts_dir'),
Expand Down Expand Up @@ -63,7 +63,7 @@
->decorate('json_encoder.encode.property_metadata_loader')
->args([
service('.inner'),
tagged_locator('json_encoder.normalizer'),
tagged_locator('json_encoder.value_transformer'),
service('type_info.resolver'),
])

Expand All @@ -86,23 +86,16 @@
->decorate('json_encoder.decode.property_metadata_loader')
->args([
service('.inner'),
tagged_locator('json_encoder.normalizer'),
tagged_locator('json_encoder.value_transformer'),
service('type_info.resolver'),
])

// normalizers/denormalizers
->set('json_encoder.normalizer.date_time', DateTimeNormalizer::class)
->tag('json_encoder.normalizer')
->set('json_encoder.denormalizer.date_time', DateTimeDenormalizer::class)
->args([
false,
])
->tag('json_encoder.denormalizer')
->set('json_encoder.denormalizer.date_time_immutable', DateTimeDenormalizer::class)
->args([
true,
])
->tag('json_encoder.denormalizer')
// value transformers
->set('json_encoder.value_transformer.date_time_to_string', DateTimeToStringValueTransformer::class)
->tag('json_encoder.value_transformer')

->set('json_encoder.value_transformer.string_to_date_time', StringToDateTimeValueTransformer::class)
->tag('json_encoder.value_transformer')

// cache
->set('.json_encoder.cache_warmer.encoder_decoder', EncoderDecoderCacheWarmer::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@

namespace Symfony\Bundle\FrameworkBundle\Tests\Functional\app\JsonEncoder\Dto;

use Symfony\Bundle\FrameworkBundle\Tests\Functional\app\JsonEncoder\RangeNormalizer;
use Symfony\Component\JsonEncoder\Attribute\Denormalizer;
use Symfony\Bundle\FrameworkBundle\Tests\Functional\app\JsonEncoder\RangeToStringValueTransformer;
use Symfony\Bundle\FrameworkBundle\Tests\Functional\app\JsonEncoder\StringToRangeValueTransformer;
use Symfony\Component\JsonEncoder\Attribute\EncodedName;
use Symfony\Component\JsonEncoder\Attribute\JsonEncodable;
use Symfony\Component\JsonEncoder\Attribute\Normalizer;
use Symfony\Component\JsonEncoder\Attribute\ValueTransformer;

/**
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
Expand All @@ -24,11 +24,9 @@
class Dummy
{
#[EncodedName('@name')]
#[Normalizer('strtoupper')]
#[Denormalizer('strtolower')]
#[ValueTransformer(toJsonValue: 'strtoupper', toNativeValue: 'strtolower')]
public string $name = 'dummy';

#[Normalizer(RangeNormalizer::class)]
#[Denormalizer(RangeNormalizer::class)]
#[ValueTransformer(toJsonValue: RangeToStringValueTransformer::class, toNativeValue: StringToRangeValueTransformer::class)]
public array $range = [10, 20];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\FrameworkBundle\Tests\Functional\app\JsonEncoder;

use Symfony\Component\JsonEncoder\ValueTransformer\ValueTransformerInterface;
use Symfony\Component\TypeInfo\Type;
use Symfony\Component\TypeInfo\Type\BuiltinType;

/**
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
*/
class RangeToStringValueTransformer implements ValueTransformerInterface
{
public function transform(mixed $value, array $options = []): string
{
return $value[0].'..'.$value[1];
}

public static function getJsonValueType(): BuiltinType
{
return Type::string();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,21 @@

namespace Symfony\Bundle\FrameworkBundle\Tests\Functional\app\JsonEncoder;

use Symfony\Component\JsonEncoder\Decode\Denormalizer\DenormalizerInterface;
use Symfony\Component\JsonEncoder\Encode\Normalizer\NormalizerInterface;
use Symfony\Component\JsonEncoder\ValueTransformer\ValueTransformerInterface;
use Symfony\Component\TypeInfo\Type;
use Symfony\Component\TypeInfo\Type\BuiltinType;

/**
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
*/
class RangeNormalizer implements NormalizerInterface, DenormalizerInterface
class StringToRangeValueTransformer implements ValueTransformerInterface
{
public function normalize(mixed $denormalized, array $options = []): string
public function transform(mixed $value, array $options = []): array
{
return $denormalized[0].'..'.$denormalized[1];
return array_map(static fn (string $v): int => (int) $v, explode('..', $value));
}

public function denormalize(mixed $normalized, array $options = []): array
{
return array_map(static fn (string $v): int => (int) $v, explode('..', $normalized));
}

public static function getNormalizedType(): BuiltinType
public static function getJsonValueType(): BuiltinType
{
return Type::string();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ services:
public: true

Symfony\Bundle\FrameworkBundle\Tests\Functional\app\JsonEncoder\Dto\Dummy: ~
Symfony\Bundle\FrameworkBundle\Tests\Functional\app\JsonEncoder\RangeNormalizer: ~
Symfony\Bundle\FrameworkBundle\Tests\Functional\app\JsonEncoder\StringToRangeValueTransformer: ~
Symfony\Bundle\FrameworkBundle\Tests\Functional\app\JsonEncoder\RangeToStringValueTransformer: ~
43 changes: 0 additions & 43 deletions 43 src/Symfony/Component/JsonEncoder/Attribute/Denormalizer.php

This file was deleted.

43 changes: 0 additions & 43 deletions 43 src/Symfony/Component/JsonEncoder/Attribute/Normalizer.php

This file was deleted.

63 changes: 63 additions & 0 deletions 63 src/Symfony/Component/JsonEncoder/Attribute/ValueTransformer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\JsonEncoder\Attribute;

use Symfony\Component\JsonEncoder\Exception\LogicException;

/**
* Defines a callable or a {@see \Symfony\Component\JsonEncoder\ValueTransformer\ValueTransformerInterface} service id
* that will be used to transform the property data during encoding and decoding.
*
* @author Mathias Arlaud <mathias.arlaud@gmail.com>
*
* @experimental
*/
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class ValueTransformer
{
private \Closure|string|null $toNativeValue;
private \Closure|string|null $toJsonValue;

/**
* @param (callable(mixed, array<string, mixed>=): mixed)|string|null $toNativeValue
* @param (callable(mixed, array<string, mixed>=): mixed)|string|null $toJsonValue
*/
public function __construct(
callable|string|null $toNativeValue = null,
callable|string|null $toJsonValue = null,
) {
if (!$toNativeValue && !$toJsonValue) {
throw new LogicException('#[ValueTransformer] attribute must declare either $toNativeValue or $toJsonValue.');
}

if (\is_callable($toNativeValue)) {
$toNativeValue = $toNativeValue(...);
}

if (\is_callable($toJsonValue)) {
$toJsonValue = $toJsonValue(...);
}

$this->toNativeValue = $toNativeValue;
$this->toJsonValue = $toJsonValue;
}

public function getToNativeValueTransformer(): string|\Closure|null
{
return $this->toNativeValue;
}

public function getToJsonValueTransformer(): string|\Closure|null
{
return $this->toJsonValue;
}
}
14 changes: 7 additions & 7 deletions 14 src/Symfony/Component/JsonEncoder/Decode/DecoderGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,23 +136,23 @@ public function createDataModel(Type $type, array $options = [], array $context
'name' => $propertyMetadata->getName(),
'value' => $this->createDataModel($propertyMetadata->getType(), $options, $context),
'accessor' => function (DataAccessorInterface $accessor) use ($propertyMetadata): DataAccessorInterface {
foreach ($propertyMetadata->getDenormalizers() as $denormalizer) {
if (\is_string($denormalizer)) {
$denormalizerServiceAccessor = new FunctionDataAccessor('get', [new ScalarDataAccessor($denormalizer)], new VariableDataAccessor('denormalizers'));
$accessor = new FunctionDataAccessor('denormalize', [$accessor, new VariableDataAccessor('options')], $denormalizerServiceAccessor);
foreach ($propertyMetadata->getToNativeValueTransformers() as $valueTransformer) {
if (\is_string($valueTransformer)) {
$valueTransformerServiceAccessor = new FunctionDataAccessor('get', [new ScalarDataAccessor($valueTransformer)], new VariableDataAccessor('valueTransformers'));
$accessor = new FunctionDataAccessor('transform', [$accessor, new VariableDataAccessor('options')], $valueTransformerServiceAccessor);

continue;
}

try {
$functionReflection = new \ReflectionFunction($denormalizer);
$functionReflection = new \ReflectionFunction($valueTransformer);
} catch (\ReflectionException $e) {
throw new RuntimeException($e->getMessage(), $e->getCode(), $e);
}

$functionName = !$functionReflection->getClosureScopeClass()
$functionName = !$functionReflection->getClosureCalledClass()
? $functionReflection->getName()
: \sprintf('%s::%s', $functionReflection->getClosureScopeClass()->getName(), $functionReflection->getName());
: \sprintf('%s::%s', $functionReflection->getClosureCalledClass()->getName(), $functionReflection->getName());
$arguments = $functionReflection->isUserDefined() ? [$accessor, new VariableDataAccessor('options')] : [$accessor];

$accessor = new FunctionDataAccessor($functionName, $arguments);
Expand Down
Loading
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.