Skip to content

[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

Conversation

amne
Copy link
Contributor

@amne amne commented Jan 12, 2024

Q A
Branch? 6.4
Bug fix? yes
New feature? no
Deprecations? no
Issues N/A
License MIT

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.

@carsonbot
Copy link

Hey!

I see that this is your first PR. That is great! Welcome!

Symfony has a contribution guide which I suggest you to read.

In short:

  • Always add tests
  • Keep backward compatibility (see https://symfony.com/bc).
  • Bug fixes must be submitted against the lowest maintained branch where they apply (see https://symfony.com/releases)
  • Features and deprecations must be submitted against the 7.1 branch.

Review the GitHub status checks of your pull request and try to solve the reported issues. If some tests are failing, try to see if they are failing because of this change.

When two Symfony core team members approve this change, it will be merged and you will become an official Symfony contributor!
If this PR is merged in a lower version branch, it will be merged up to all maintained branches within a few days.

I am going to sit back now and wait for the reviews.

Cheers!

Carsonbot

… 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.
Copy link
Contributor

@mtarld mtarld left a comment

Choose a reason for hiding this comment

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

As it's a bug, you must target the 5.4 branch instead.

@@ -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.

@@ -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.

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

Comment on lines 803 to 825
$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);

@@ -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']

Copy link
Contributor

@mtarld mtarld left a comment

Choose a reason for hiding this comment

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

Can you rebase your PR on the 5.4 branch please? 🙂


$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.

Unexpected comment I guess

@@ -302,9 +302,6 @@ 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

@mtarld
Copy link
Contributor

mtarld commented Jan 13, 2024

This can be closed in favor of #53530, thanks!

@OskarStark OskarStark changed the title [Serializer] Rewrite AbstractObjectNormalizer::createChildContext() to use the provided cache_key from original context [Serializer] Rewrite AbstractObjectNormalizer::createChildContext() to use the provided cache_key from original context Jan 13, 2024
@amne amne closed this Jan 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants