Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit fb0db56

Browse files
committed
[Serializer] Introduces Symfony\Component\Serializer\Normalizer\SupportedTypesMethodInterface
This interface acts as a marker allowing the Serializer component to detect normalizers or denormalizers that can provide their supported classe without a call to their supports*() methods. This allows more heavy caching for almost static formats and prevent several calls to supports which can dramatically improves performance on heavy graphs.
1 parent 1c25b7e commit fb0db56

File tree

7 files changed

+170
-1
lines changed

7 files changed

+170
-1
lines changed

src/Symfony/Component/Serializer/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ CHANGELOG
55
---
66

77
* Add `XmlEncoder::SAVE_OPTIONS` context option
8+
* Add `SupportedTypesMethodInterface` for normalizers and denormalizers that can provide supported types outside from calls to their `supports*()` methods
9+
* Deprecate `CacheableSupportsMethodInterface` in favor of implementing `SupportedTypesMethodInterface`
810

911
6.2
1012
---

src/Symfony/Component/Serializer/Debug/TraceableNormalizer.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
1818
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
1919
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
20+
use Symfony\Component\Serializer\Normalizer\SupportedTypesMethodInterface;
2021
use Symfony\Component\Serializer\SerializerAwareInterface;
2122
use Symfony\Component\Serializer\SerializerInterface;
2223

@@ -27,14 +28,23 @@
2728
*
2829
* @internal
2930
*/
30-
class TraceableNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface, NormalizerAwareInterface, DenormalizerAwareInterface, CacheableSupportsMethodInterface
31+
class TraceableNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface, NormalizerAwareInterface, DenormalizerAwareInterface, SupportedTypesMethodInterface, CacheableSupportsMethodInterface
3132
{
3233
public function __construct(
3334
private NormalizerInterface|DenormalizerInterface $normalizer,
3435
private SerializerDataCollector $dataCollector,
3536
) {
3637
}
3738

39+
public function getSupportedTypes(): ?array
40+
{
41+
if ($this->normalizer instanceof SupportedTypesMethodInterface) {
42+
return $this->normalizer->getSupportedTypes();
43+
}
44+
45+
return null;
46+
}
47+
3848
public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null
3949
{
4050
if (!$this->normalizer instanceof NormalizerInterface) {

src/Symfony/Component/Serializer/Normalizer/CacheableSupportsMethodInterface.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
* supports*() methods will be cached by type and format.
2020
*
2121
* @author Kévin Dunglas <[email protected]>
22+
*
23+
* @deprecated since Symfony 6.3, implement "getSupportedTypes()" instead
2224
*/
2325
interface CacheableSupportsMethodInterface
2426
{
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Normalizer;
13+
14+
/**
15+
* Marker interface for normalizers and denormalizers that can provide their
16+
* supported class(es) outside a call to their supports*() methods.
17+
*
18+
* By implementing this interface, the return value of the
19+
* getSupportedTypes() method will allow more efficient caching type and format.
20+
*
21+
* @author Tugdual Saunier <[email protected]>
22+
*/
23+
interface SupportedTypesMethodInterface
24+
{
25+
/**
26+
* Returns the types supported for the normalization/denormalization process.
27+
*
28+
* The implementation should return the classes supported by the normalizer
29+
* and/or denormalizer associated to a boolean indicating if the result of
30+
* supports*() methods can be cached or if the result can not be cached
31+
* because it depends on the context.
32+
* Returning null means the normalizer/denormalizer will be considered for
33+
* every format/class (backward compatible behavior).
34+
*
35+
* @return array<class-string|string, bool>|null
36+
*/
37+
public function getSupportedTypes(): ?array;
38+
}

src/Symfony/Component/Serializer/Serializer.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
3131
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
3232
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
33+
use Symfony\Component\Serializer\Normalizer\SupportedTypesMethodInterface;
3334

3435
/**
3536
* Serializer serializes and deserializes data.
@@ -256,6 +257,24 @@ private function getNormalizer(mixed $data, ?string $format, array $context): ?N
256257
continue;
257258
}
258259

260+
if ($normalizer instanceof SupportedTypesMethodInterface && null !== $supportedTypes = $normalizer->getSupportedTypes()) {
261+
foreach ($supportedTypes as $supportedType => $isCacheable) {
262+
if ($type !== $supportedType && !is_subclass_of($type, $supportedType, true)) {
263+
continue;
264+
}
265+
266+
if ($isCacheable && $normalizer->supportsNormalization($data, $format, $context)) {
267+
$this->normalizerCache[$format][$type][$k] = true;
268+
break 2;
269+
}
270+
271+
$this->normalizerCache[$format][$type][$k] = false;
272+
break;
273+
}
274+
275+
continue;
276+
}
277+
259278
if (!$normalizer instanceof CacheableSupportsMethodInterface || !$normalizer->hasCacheableSupportsMethod()) {
260279
$this->normalizerCache[$format][$type][$k] = false;
261280
} elseif ($normalizer->supportsNormalization($data, $format, $context)) {
@@ -293,6 +312,24 @@ private function getDenormalizer(mixed $data, string $class, ?string $format, ar
293312
continue;
294313
}
295314

315+
if ($normalizer instanceof SupportedTypesMethodInterface && null !== $supportedTypes = $normalizer->getSupportedTypes()) {
316+
foreach ($supportedTypes as $supportedType => $isCacheable) {
317+
if ($class !== $supportedType && !is_subclass_of($class, $supportedType, true)) {
318+
continue;
319+
}
320+
321+
if ($isCacheable && $normalizer->supportsDenormalization(null, $class, $format, $context)) {
322+
$this->denormalizerCache[$format][$class][$k] = true;
323+
break 2;
324+
}
325+
326+
$this->denormalizerCache[$format][$class][$k] = false;
327+
break;
328+
}
329+
330+
continue;
331+
}
332+
296333
if (!$normalizer instanceof CacheableSupportsMethodInterface || !$normalizer->hasCacheableSupportsMethod()) {
297334
$this->denormalizerCache[$format][$class][$k] = false;
298335
} elseif ($normalizer->supportsDenormalization(null, $class, $format, $context)) {

src/Symfony/Component/Serializer/Tests/Encoder/XmlEncoderTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -942,6 +942,11 @@ private function createMockDateTimeNormalizer(): MockObject&NormalizerInterface
942942
->with(new \DateTime($this->exampleDateTimeString), 'xml', [])
943943
->willReturn($this->exampleDateTimeString);
944944

945+
$mock
946+
->expects($this->once())
947+
->method('getSupportedTypes')
948+
->willReturn([\DateTime::class => true]);
949+
945950
$mock
946951
->expects($this->once())
947952
->method('supportsNormalization')

src/Symfony/Component/Serializer/Tests/SerializerTest.php

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
4949
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
5050
use Symfony\Component\Serializer\Normalizer\PropertyNormalizer;
51+
use Symfony\Component\Serializer\Normalizer\SupportedTypesMethodInterface;
5152
use Symfony\Component\Serializer\Normalizer\UidNormalizer;
5253
use Symfony\Component\Serializer\Normalizer\UnwrappingDenormalizer;
5354
use Symfony\Component\Serializer\Serializer;
@@ -1235,6 +1236,76 @@ public function provideCollectDenormalizationErrors()
12351236
[new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()))],
12361237
];
12371238
}
1239+
1240+
public function testSerializerUsesSupportedTypesMethod()
1241+
{
1242+
$neverCalledNormalizer = $this->createMock(DummyNormalizer::class);
1243+
$neverCalledNormalizer
1244+
// once for normalization, once for denormalization
1245+
->expects($this->exactly(2))
1246+
->method('getSupportedTypes')
1247+
->willReturn([
1248+
Foo::class => true,
1249+
Bar::class => false,
1250+
]);
1251+
1252+
$supportedAndCachedNormalizer = $this->createMock(DummyNormalizer::class);
1253+
$supportedAndCachedNormalizer
1254+
// once for normalization, once for denormalization
1255+
->expects($this->exactly(2))
1256+
->method('getSupportedTypes')
1257+
->willReturn([
1258+
Model::class => true,
1259+
]);
1260+
1261+
$serializer = new Serializer(
1262+
[
1263+
$neverCalledNormalizer,
1264+
$supportedAndCachedNormalizer,
1265+
new ObjectNormalizer(),
1266+
],
1267+
['json' => new JsonEncoder()]
1268+
);
1269+
1270+
// Normalization process
1271+
$neverCalledNormalizer
1272+
->expects($this->never())
1273+
->method('supportsNormalization');
1274+
$neverCalledNormalizer
1275+
->expects($this->never())
1276+
->method('normalize');
1277+
1278+
$supportedAndCachedNormalizer
1279+
->expects($this->once())
1280+
->method('supportsNormalization')
1281+
->willReturn(true);
1282+
$supportedAndCachedNormalizer
1283+
->expects($this->exactly(2))
1284+
->method('normalize')
1285+
->willReturn(['foo' => 'bar']);
1286+
1287+
$serializer->normalize(new Model(), 'json');
1288+
$serializer->normalize(new Model(), 'json');
1289+
1290+
// Denormalization pass
1291+
$neverCalledNormalizer
1292+
->expects($this->never())
1293+
->method('supportsDenormalization');
1294+
$neverCalledNormalizer
1295+
->expects($this->never())
1296+
->method('denormalize');
1297+
$supportedAndCachedNormalizer
1298+
->expects($this->once())
1299+
->method('supportsDenormalization')
1300+
->willReturn(true);
1301+
$supportedAndCachedNormalizer
1302+
->expects($this->exactly(2))
1303+
->method('denormalize')
1304+
->willReturn(new Model());
1305+
1306+
$serializer->denormalize('foo', Model::class, 'json');
1307+
$serializer->denormalize('foo', Model::class, 'json');
1308+
}
12381309
}
12391310

12401311
class Model
@@ -1389,6 +1460,10 @@ public function getIterator(): \ArrayIterator
13891460
}
13901461
}
13911462

1463+
abstract class DummyNormalizer implements NormalizerInterface, DenormalizerInterface, SupportedTypesMethodInterface
1464+
{
1465+
}
1466+
13921467
interface NormalizerAwareNormalizer extends NormalizerInterface, NormalizerAwareInterface
13931468
{
13941469
}

0 commit comments

Comments
 (0)