-
-
Notifications
You must be signed in to change notification settings - Fork 9.8k
Description
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
- Add this
src/Controller/UnderscoreSerializerDemoController.phpto 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;
}
}- Go to route http://127.0.0.1:8000/underscore/serializer/demo
- See, that it returns
{"currencyCode":"EUR"}, the amount is lost!
I would expect it to return {"_": 100, "currencyCode":"EUR"}
Possible Solution
Changing this method:
symfony/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php
Lines 900 to 903 in f2acd82
| 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:
symfony/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php
Lines 376 to 387 in f2acd82
| $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