Skip to content

[PropertyInfo] ReflectionExtractor::getReadInfo returns null for property _ #61425

@RafaelKr

Description

@RafaelKr

Symfony version(s) affected

5.1.0+

Description

In my project I have some generated classes (for SOAP operations). Many of those generated classes include a property named $_ and a getter named get_(). When serializing such a class, it won't include the _ property.

Example Class:

class Amount
{
    private float $_;

    private string $currencyCode;

    public function get_() : float
    {
        return $this->_;
    }

    public function with_(float $_) : static
    {
        $new = clone $this;
        $new->_ = $_;

        return $new;
    }

    public function getCurrencyCode() : string
    {
        return $this->currencyCode;
    }

    public function withCurrencyCode(string $currencyCode) : static
    {
        $new = clone $this;
        $new->currencyCode = $currencyCode;

        return $new;
    }
}

How to reproduce

  1. Add this src/Controller/UnderscoreSerializerDemoController.php to your project:
<?php

declare(strict_types=1);

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;

final class UnderscoreSerializerDemoController extends AbstractController
{
    #[Route('/underscore/serializer/demo', name: 'app_underscore_serializer_demo')]
    public function index(
        NormalizerInterface $normalizer,
    ): Response {
        $amount = (new Amount())
            ->with_(100.0)
            ->withCurrencyCode('EUR');
        $amountArray = $normalizer->normalize($amount);

        return new JsonResponse($amountArray);
    }
}

class Amount
{
    private float $_;

    private string $currencyCode;

    public function get_(): float
    {
        return $this->_;
    }

    public function with_(float $_): static
    {
        $new = clone $this;
        $new->_ = $_;

        return $new;
    }

    public function getCurrencyCode(): string
    {
        return $this->currencyCode;
    }

    public function withCurrencyCode(string $currencyCode): static
    {
        $new = clone $this;
        $new->currencyCode = $currencyCode;

        return $new;
    }
}
  1. Go to route http://127.0.0.1:8000/underscore/serializer/demo
  2. See, that it returns {"currencyCode":"EUR"}, the amount is lost!

I would expect it to return {"_": 100, "currencyCode":"EUR"}

Possible Solution

Changing this method:

private function camelize(string $string): string
{
return str_replace(' ', '', ucwords(str_replace('_', ' ', $string)));
}

to

private function camelize(string $string): string
{
    return $string === '_'
        ? $string
        : str_replace(' ', '', ucwords(str_replace('_', ' ', $string)));
}

fixes the issue.

Then $camelProp will be '_' instead of '' (empty string) as the underscore is kept. Thus it can find the correct accessor (get_) in the loop:
The reason can be found here:

$camelProp = $this->camelize($property);
$getsetter = lcfirst($camelProp); // jQuery style, e.g. read: last(), write: last($item)
foreach ($this->accessorPrefixes as $prefix) {
$methodName = $prefix.$camelProp;
if ($reflClass->hasMethod($methodName) && $reflClass->getMethod($methodName)->getModifiers() & $this->methodReflectionFlags && !$reflClass->getMethod($methodName)->getNumberOfRequiredParameters()) {
$method = $reflClass->getMethod($methodName);
return new PropertyReadInfo(PropertyReadInfo::TYPE_METHOD, $methodName, $this->getReadVisibilityForMethod($method), $method->isStatic(), false);
}
}

Additional Context

I'm open to do a PR. I'm not sure against which branch though. It exists since Symfony 5.1, it was introduced with the commit fc25086

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