Skip to content

[Serializer] Discrepancy between NormalizerInterface and it's implementation #59495

Closed
@Kefisu

Description

@Kefisu

Description

The "Problem"

Right now there is a discrepancy between the Symfony\Component\Serializer\Normalizer\NormalizerInterface and it's implementations within the Symfony codebase.

If you look at the signature of the NormalizerInterface.normalize method it looks as follows:
public function normalize(mixed $data, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null;

Whereas most implementing classes are implemented like this:

AbstractObjectNormalizer:
public function normalize(mixed $object, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null

DateIntervalNormalizer:
public function normalize(mixed $object, ?string $format = null, array $context = []): string

And the list goes on..

Whilst PHP allows this behavior to exists and doesn't raise any errors for it, with the introduction of named parameters in PHP 8.0 this discrepancy now has the possibility to throw PHP errors such as:
Error: Unknown named parameter $data.

Acknowledgement

Whilst reading through the existing issues I came upon #54880 where it was discussed that the BC does not cover this change or would increase the risk of merging changes back into branches.

However, in it's current implementation it is simply impossible to use named parameters when type hinting the NormalizerInterface and not running the risk of unexpected PHP errors.

Conclusion

Therefore I would like to submit this as a feature ("not a bug") type of change to the 7.3/7.4 branch to solve this discrepancy and reducing the risk of unintentional errors for developers using Symfony.

Linked to this issue is a PR that changes all instances where the NormalizerInterface is implemented but does not follow the Interfaces' signature.

Example

The following is a change to the ObjectNormalizerTest that shows that using named parameters on the interface with an implementation of the ObjectNormalizer PHP will throw an error

<?php

namespace Symfony\Component\Serializer\Tests\Normalizer;

class ObjectNormalizerTest extends TestCase
{
    private ObjectNormalizer $normalizer;
    private NormalizerInterface $normalizerInterface;
    private SerializerInterface&NormalizerInterface&MockObject $serializer;

    protected function setUp(): void
    {
        $this->createNormalizer();

        $this->normalizerInterface = $this->normalizer;
    }

    private function createNormalizer(array $defaultContext = [], ?ClassMetadataFactoryInterface $classMetadataFactory = null): void
    {
        $this->serializer = $this->createMock(ObjectSerializerNormalizer::class);
        $this->normalizer = new ObjectNormalizer($classMetadataFactory, null, null, null, null, null, $defaultContext);
        $this->normalizer->setSerializer($this->serializer);
    }

    // In the current releases this throws an error
    public function testNamedArgumentsOnInterfaceWillThrowError(): void
    {
        $this->normalizerInterface->normalize(
            data: new ObjectDummy(),
            format: 'json',
            context: []
        );
    }
}
php ./phpunit src/Symfony/Component/Serializer
...
Time: 00:00.302, Memory: 39.02 MB

There was 1 error:

1) Symfony\Component\Serializer\Tests\Normalizer\ObjectNormalizerTest::testNamedArgumentsOnInterfaceWillThrowError
Error: Unknown named parameter $data

And after changing all implementations:

php ./phpunit src/Symfony/Component/Serializer

............................................................. 1098 / 1167 ( 94%)
............................................................. 1159 / 1167 ( 99%)
........                                                      1167 / 1167 (100%)

Time: 00:00.365, Memory: 39.02 MB

OK, but incomplete, skipped, or risky tests!
Tests: 1167, Assertions: 2258, Skipped: 11.

Legacy deprecation notices (2)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions