Description
Symfony version(s) affected
6.4.5
Description
A bundle we use (rompetompe/inertia-bundle) calls the Symfony serializer on any data that's passed to it (that will be converted to JSON for use by Inertia.js), after upgrading to Symfony 6 we're seeing PHP warnings in some situations. It seems to only happen when there is a stdClass object in the data that's passed to the serializer (from a json_decode
call somewhere else in the app).
The line that causes the warning is this one:
if (!($enableMaxDepth = $context[self::ENABLE_MAX_DEPTH] ?? $this->defaultContext[self::ENABLE_MAX_DEPTH] ?? false)
|| null === $maxDepth = $attributesMetadata[$attribute]?->getMaxDepth()
) {
return false;
}
It happens because $attributesMetadata
is an empty array in this context and $attribute
is a string (which is one of the properties of the stdClass object it's trying to normalize). Earlier in the normalizer there is this chunk of code:
$class = ($this->objectClassResolver)($object);
$classMetadata = $this->classMetadataFactory?->getMetadataFor($class);
$attributesMetadata = $this->classMetadataFactory?->getMetadataFor($class)->getAttributesMetadata();
I mention this, because there is a check a little bit below this to see if $attributesMetadata
is null
and then it bypasses the isMaxDepthReached
call:
foreach ($attributes as $attribute) {
$maxDepthReached = false;
if (null !== $attributesMetadata && ($maxDepthReached = $this->isMaxDepthReached($attributesMetadata, $class, $attribute, $context)) && !$maxDepthHandler) {
continue;
}
In the application $attributesMetadata
is an empty array, but in an isolated test case that I've tried to put together, $attributesMetadata
ends up being null
so I can't reproduce the warning in an isolated test case.
Perhaps there's something else to this (why does stdClass
have attributes?) in the context of the app, but it does seem like there should be a check if the $attribute
is set on $attributesMetadata
in the isMaxDepthReached
method to avoid this warning.
Aside from the Warning being thrown, the serialization/normalization works as expected.
How to reproduce
I put this test case together to try to reproduce in the simplest way possible but it doesn't throw the warning as mentioned above:
<?php
ini_set('display_errors', 1);
ini_set('error_reporting', E_ALL);
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
include_once 'vendor/autoload.php';
$object = new stdClass();
$object->string = 'yes';
$serializer = new Symfony\Component\Serializer\Serializer([
new Symfony\Component\Serializer\Normalizer\UnwrappingDenormalizer(),
new Symfony\Component\Serializer\Normalizer\ProblemNormalizer(),
new Symfony\Component\Serializer\Normalizer\UidNormalizer(),
new Symfony\Component\Serializer\Normalizer\DateTimeNormalizer(),
new Symfony\Component\Serializer\Normalizer\DateTimeZoneNormalizer(),
new Symfony\Component\Serializer\Normalizer\DateIntervalNormalizer(),
new Symfony\Component\Serializer\Normalizer\FormErrorNormalizer(),
new Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer(),
new Symfony\Component\Serializer\Normalizer\DataUriNormalizer(),
new Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer(),
new Symfony\Component\Serializer\Normalizer\ArrayDenormalizer(),
new Symfony\Component\Serializer\Normalizer\ObjectNormalizer(),
], [
new Symfony\Component\Serializer\Encoder\XmlEncoder(),
new Symfony\Component\Serializer\Encoder\JsonEncoder(),
new Symfony\Component\Serializer\Encoder\YamlEncoder(),
new Symfony\Component\Serializer\Encoder\CsvEncoder(),
]);
$page = [
'props' => [
'string' => 'yes',
'array' => ['string' => 'yes'],
'object' => $object,
'nested_object' => [
'string' => 'yes',
'array' => ['string' => 'yes'],
'object' => $object,
],
]
];
$context = [];
$json = $serializer->serialize($page, 'json', array_merge([
'json_encode_options' => JsonResponse::DEFAULT_ENCODING_OPTIONS,
AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function () { return null; },
AbstractObjectNormalizer::PRESERVE_EMPTY_OBJECTS => true,
AbstractObjectNormalizer::ENABLE_MAX_DEPTH => true,
], $context));
var_dump($json);
I don't know how to make this test case set attributes on the stdClass
class like it's doing in the app, but I've tried to replicate how the serializer is configured as best as possible (as far as I can tell, the inertia-bundle just uses @serializer
for the serializer dependency, which we're not modifying at all, so should be the Symfony default Serializer config).
Possible Solution
This could probably be fixed in a couple of ways, first, if $attributesMetadata
is an empty array, it seems like it should probably be treated the same as if it were null
.
In the normalize
method of AbstractObjectNormalizer
this check:
foreach ($attributes as $attribute) {
$maxDepthReached = false;
if (null !== $attributesMetadata && ($maxDepthReached = $this->isMaxDepthReached($attributesMetadata, $class, $attribute, $context)) && !$maxDepthHandler) {
continue;
}
could be changed to also check the count of the metadata variable so that the isMaxDepthReached
method isn't called with an empty array:
foreach ($attributes as $attribute) {
$maxDepthReached = false;
if (null !== $attributesMetadata && count($attributesMetadata) > 0 && ($maxDepthReached = $this->isMaxDepthReached($attributesMetadata, $class, $attribute, $context)) && !$maxDepthHandler) {
continue;
}
This does fix it in our application.
Alternatively, the isMaxDepthReached
method could be modified in a similar way to check if the $attribute
is a key on the passed in $attributesMetadata
.
Additional Context
No response