8000 [Serializer] Add SnakeCaseToCamelCaseNameConverter · symfony/symfony@b8ec3d9 · GitHub
[go: up one dir, main page]

Skip to content

Commit b8ec3d9

Browse files
dunglasfabpot
authored andcommitted
[Serializer] Add SnakeCaseToCamelCaseNameConverter
1 parent 1c8cd56 commit b8ec3d9

File tree

6 files changed

+153
-1
lines changed

6 files changed

+153
-1
lines changed

src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ CHANGELOG
1111
* [BC BREAK] The `secrets:decrypt-to-local` command terminates with a non-zero exit code when a secret could not be read
1212
* Deprecate making `cache.app` adapter taggable, use the `cache.app.taggable` adapter instead
1313
* Enable `json_decode_detailed_errors` in the default serializer context in debug mode by default when `seld/jsonlint` is installed
14+
* Register `Symfony\Component\Serializer\NameConverter\SnakeCaseToCamelCaseNameConverter` as a service named `serializer.name_converter.snake_case_to_camel_case` if available
1415

1516
7.1
1617
---

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@
158158
use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader;
159159
use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader;
160160
use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader;
161+
use Symfony\Component\Serializer\NameConverter\SnakeCaseToCamelCaseNameConverter;
161162
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
162163
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
163164
use Symfony\Component\Serializer\Serializer;
@@ -1849,6 +1850,11 @@ private function regi 8000 sterSerializerConfiguration(array $config, ContainerBuilder
18491850
$container->removeDefinition('serializer.normalizer.mime_message');
18501851
}
18511852

1853+
// BC layer Serializer < 7.2
1854+
if (!class_exists(SnakeCaseToCamelCaseNameConverter::class)) {
1855+
$container->removeDefinition('serializer.name_converter.snake_case_to_camel_case');
1856+
}
1857+
18521858
if ($container->getParameter('kernel.debug')) {
18531859
$container->removeDefinition('serializer.mapping.cache_class_metadata_factory');
18541860
}

src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
use Symfony\Component\Serializer\Mapping\Loader\LoaderChain;
3232
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
3333
use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter;
34+
use Symfony\Component\Serializer\NameConverter\SnakeCaseToCamelCaseNameConverter;
3435
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
3536
use Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer;
3637
use Symfony\Component\Serializer\Normalizer\ConstraintViolationListNormalizer;
@@ -186,8 +187,9 @@
186187
->set('serializer.encoder.csv', CsvEncoder::class)
187188
->tag('serializer.encoder')
188189

189-
// Name converter
190+
// Name converters
190191
->set('serializer.name_converter.camel_case_to_snake_case', CamelCaseToSnakeCaseNameConverter::class)
192+
->set('serializer.name_converter.snake_case_to_camel_case', SnakeCaseToCamelCaseNameConverter::class)
191193

192194
->set('serializer.name_converter.metadata_aware', MetadataAwareNameConverter::class)
193195
->args([service('serializer.mapping.class_metadata_factory')])

src/Symfony/Component/Serializer/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ CHANGELOG
66

77
* Deprecate the `csv_escape_char` context option of `CsvEncoder` and the `CsvEncoder::ESCAPE_CHAR_KEY` constant
88
* Deprecate `CsvEncoderContextBuilder::withEscapeChar()` method
9+
* Add `SnakeCaseToCamelCaseNameConverter`
910

1011
7.1
1112
---
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Serializer\NameConverter;
13+
14+
use Symfony\Component\Serializer\Exception\UnexpectedPropertyException;
15+
16+
/**
17+
* Underscore to camelCase name converter.
18+
*
19+
* @author Kévin Dunglas <kevin@dunglas.dev>
20+
*/
21+
final readonly class SnakeCaseToCamelCaseNameConverter implements NameConverterInterface
22+
{
23+
/**
24+
* Require all properties to be written in camelCase.
25+
*/
26+
public const REQUIRE_CAMEL_CASE_PROPERTIES = 'require_camel_case_properties';
27+
28+
/**
29+
* @param string[]|null $attributes The list of attributes to rename or null for all attributes
30+
* @param bool $lowerCamelCase Use lowerCamelCase style
31+
*/
32+
public function __construct(
33+
private ?array $attributes = null,
34+
private bool $lowerCamelCase = true,
35+
) {
36+
}
37+
38+
/**
39+
* @param class-string|null $class
40+
* @param array<string, mixed> $context
41+
*/
42+
public function normalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string
43+
{
44+
if (null !== $this->attributes && !\in_array($propertyName, $this->attributes, true)) {
45+
return $propertyName;
46+
}
47+
48+
$camelCasedName = preg_replace_callback(
49+
'/(^|_|\.)+(.)/',
50+
fn ($match) => ('.' === $match[1] ? '_' : '').strtoupper($match[2]),
51+
$propertyName
52+
);
53+
54+
if ($this->lowerCamelCase) {
55+
$camelCasedName = lcfirst($camelCasedName);
56+
}
57+
58+
return $camelCasedName;
59+
}
60+
61+
/**
62+
* @param class-string|null $class
63+
* @param array<string, mixed> $context
64+
*/
65+
public function denormalize(string $propertyName, ?string $class = null, ?string $format = null, array $context = []): string
66+
{
67+
if (($context[self::REQUIRE_CAMEL_CASE_PROPERTIES] ?? false) && $propertyName !== $this->normalize($propertyName, $class, $format, $context)) {
68+
throw new UnexpectedPropertyException($propertyName);
69+
}
70+
71+
$snakeCased = strtolower(preg_replace('/[A-Z]/', '_\\0', lcfirst($propertyName)));
72+
if (null === $this->attributes || \in_array($snakeCased, $this->attributes, true)) {
73+
return $snakeCased;
74+
}
75+
76+
return $propertyName;
77+
}
78+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Serializer\Tests\NameConverter;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Serializer\Exception\UnexpectedPropertyException;
16+
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
17+
use Symfony\Component\Serializer\NameConverter\SnakeCaseToCamelCaseNameConverter;
18+
19+
/**
20+
* @author Kévin Dunglas <dunglas@gmail.com>
21+
* @author Aurélien Pillevesse <aurelienpillevesse@hotmail.fr>
22+
*/
23+
class SnakeCaseToCamelCaseNameConverterTest extends TestCase
24+
{
25+
public function testInterface()
26+
{
27+
$attributeMetadata = new SnakeCaseToCamelCaseNameConverter();
28+
$this->assertInstanceOf(NameConverterInterface::class, $attributeMetadata);
29+
}
30+
31+
/**
32+
* @dataProvider Symfony\Component\Serializer\Tests\NameConverter\CamelCaseToSnakeCaseNameConverterTest::attributeProvider
33+
*/
34+
public function testNormalize($underscored, $camelCased, $useLowerCamelCase)
35+
{
36+
$nameConverter = new SnakeCaseToCamelCaseNameConverter(null, $useLowerCamelCase);
37+
$this->assertEquals($camelCased, $nameConverter->normalize($underscored));
38+
}
39+
40+
/**
41+
* @dataProvider Symfony\Component\Serializer\Tests\NameConverter\CamelCaseToSnakeCaseNameConverterTest::attributeProvider
42+
*/
43+
public function testDenormalize($underscored, $camelCased, $useLowerCamelCase)
44+
{
45+
$nameConverter = new SnakeCaseToCamelCaseNameConverter(null, $useLowerCamelCase);
46+
$this->assertEquals($underscored, $nameConverter->denormalize($camelCased));
47+
}
48+
49+
public function testDenormalizeWithContext()
50+
{
51+
$nameConverter = new SnakeCaseToCamelCaseNameConverter(null, true);
52+
$denormalizedValue = $nameConverter->denormalize('lastName', null, null, [SnakeCaseToCamelCaseNameConverter::REQUIRE_CAMEL_CASE_PROPERTIES => true]);
53+
54+
$this->assertSame('last_name', $denormalizedValue);
55+
}
56+
57+
public function testErrorDenormalizeWithContext()
58+
{
59+
$nameConverter = new SnakeCaseToCamelCaseNameConverter(null, true);
60+
61+
$this->expectException(UnexpectedPropertyException::class);
62+
$nameConverter->denormalize('last_name', null, null, [SnakeCaseToCamelCaseNameConverter::REQUIRE_CAMEL_CASE_PROPERTIES => true]);
63+
}
64+
}

0 commit comments

Comments
 (0)
0