Skip to content

Navigation Menu

Sign in
Appearance settings

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

[Serializer] Rewrite AbstractObjectNormalizer::createChildContext() to use the provided cache_key from original context #53523

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

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
[Serializer] Rewrite AbstractObjectNormalizer::createChildContext()
… to use the provided cache_key from original context when creating child contexts

If the normalization starts with an initial 'cache_key' it is currently discarded when creating child contexts. This change fixes that.
The main reason behind it is that if the client code sends a unique identifier (ApiPlatform injects the IRI) and we have a use case
that iterates a big resultset we end up with a big private cache that quickly runs out of allowed memory. The solution should be to
send a 'cache_key' which ignores the entire cache key calculation that hashes the context.
  • Loading branch information
amne committed Jan 12, 2024
commit 96af30f1faf41d323a822eaee7a2ca9c9fcc8fc7
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,7 @@ public function supportsNormalization(mixed $data, string $format = null /* , ar
*/
public function normalize(mixed $object, string $format = null, array $context = [])
{
if (!isset($context['cache_key'])) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd keep this if to avoid calling a method each time. Indeed, calling a method could be costly and we are in the hot path here.

$context['cache_key'] = $this->getCacheKey($format, $context);
}
$context['cache_key'] = $this->getCacheKey($format, $context);

$this->validateCallbackContext($context);

Expand Down Expand Up @@ -287,8 +285,6 @@ abstract protected function extractAttributes(object $object, string $format = n

/**
* Gets the attribute value.
*
* @return mixed
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These coding style changes must not be committed as they don't belong to your bugfix.

*/
abstract protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []);

Expand All @@ -302,14 +298,9 @@ public function supportsDenormalization(mixed $data, string $type, string $forma
return class_exists($type) || (interface_exists($type, false) && null !== $this->classDiscriminatorResolver?->getMappingForClass($type));
}

/**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change should be reverted

* @return mixed
*/
public function denormalize(mixed $data, string $type, string $format = null, array $context = [])
{
if (!isset($context['cache_key'])) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

$context['cache_key'] = $this->getCacheKey($format, $context);
}
$context['cache_key'] = $this->getCacheKey($format, $context);

$this->validateCallbackContext($context);

Expand Down Expand Up @@ -734,7 +725,13 @@ private function isMaxDepthReached(array $attributesMetadata, string $class, str
protected function createChildContext(array $parentContext, string $attribute, ?string $format): array
{
$context = parent::createChildContext($parentContext, $attribute, $format);
$context['cache_key'] = $this->getCacheKey($format, $context);
if (isset($context['cache_key'])) {
if (false !== $context['cache_key']) {
$context['cache_key'] .= '-'.$attribute;
}
} else {
$context['cache_key'] = $this->getCacheKey($format, $context);
}

return $context;
}
Expand All @@ -746,6 +743,10 @@ protected function createChildContext(array $parentContext, string $attribute, ?
*/
private function getCacheKey(?string $format, array $context): bool|string
{
if (isset($context['cache_key'])) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it go after $context[self::EXCLUDE_FROM_CACHE_KEY] foreach loop?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I revert the removal of the inline ifs then this one goes away so it doesn't matter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But to answer, I don't it should go after because the method would not be called if there is an existing $context['cache_key']

return $context['cache_key'];
}

foreach ($context[self::EXCLUDE_FROM_CACHE_KEY] ?? $this->defaultContext[self::EXCLUDE_FROM_CACHE_KEY] as $key) {
unset($context[$key]);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,43 @@ public function supportsNormalization(mixed $data, string $format = null, array
$this->assertSame('called', $object->bar);
}

public function testChildContextKeepsOriginalContextCacheKey()
{
$foobar = new Dummy();
$foobar->foo = new EmptyDummy();
$foobar->bar = 'bar';
$foobar->baz = 'baz';
$data = [
'foo' => [],
'bar' => 'bar',
'baz' => 'baz',
];

$normalizer = new class() extends AbstractObjectNormalizerDummy {
protected function extractAttributes(object $object, string $format = null, array $context = []): array
{
return array_keys((array) $object);
}

protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed
{
return $object->{$attribute};
}

protected function createChildContext(array $parentContext, string $attribute, ?string $format): array
{
$childContext = parent::createChildContext($parentContext, $attribute, $format);
AbstractObjectNormalizerTest::assertSame('hardcoded-'.$attribute, $childContext['cache_key']);

return $childContext;
}
};

$serializer = new Serializer([$normalizer]);
$normalizedObject = $serializer->normalize($foobar, null, ['cache_key' => 'hardcoded']);
$this->assertSame($data, $normalizedObject);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make sure that an assertion has been made, I'd use a property and make an assertion directly in the test:

Suggested change
$normalizer = new class() extends AbstractObjectNormalizerDummy {
protected function extractAttributes(object $object, string $format = null, array $context = []): array
{
return array_keys((array) $object);
}
protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed
{
return $object->{$attribute};
}
protected function createChildContext(array $parentContext, string $attribute, ?string $format): array
{
$childContext = parent::createChildContext($parentContext, $attribute, $format);
AbstractObjectNormalizerTest::assertSame('hardcoded-'.$attribute, $childContext['cache_key']);
return $childContext;
}
};
$serializer = new Serializer([$normalizer]);
$normalizedObject = $serializer->normalize($foobar, null, ['cache_key' => 'hardcoded']);
$this->assertSame($data, $normalizedObject);
$normalizer = new class() extends AbstractObjectNormalizerDummy {
public ?string $childContextCacheKey = null;
protected function extractAttributes(object $object, string $format = null, array $context = []): array
{
return array_keys((array) $object);
}
protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed
{
return $object->{$attribute};
}
protected function createChildContext(array $parentContext, string $attribute, ?string $format): array
{
$childContext = parent::createChildContext($parentContext, $attribute, $format);
$this->childContextCacheKey = $childContext['cache_key'];
return $childContext;
}
};
$serializer = new Serializer([$normalizer]);
$normalizedObject = $serializer->normalize($foobar, null, ['cache_key' => 'hardcoded']);
$this->assertSame($data, $normalizedObject);
$this->assertSame('hardcoded-'.$attribute, $normalizer->childContextCacheKey);

}

public function testDenormalizeUnionOfEnums()
{
$serializer = new Serializer([
Expand Down
Morty Proxy This is a proxified and sanitized view of the page, visit original site.