Description
Symfony version(s) affected: v4.2.2 v4.1.11 v4.1.10 v3.4.21
Description
Commit 8741d00
\Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer::denormalizeParameter()
- calls
$this->propertyTypeExtractor->getTypes()
- from
\Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface::getTypes()
- whitch iterates over registered extractors and tries to extract property type.
PropertyInfoExtractor has extractors:
0 => \Symfony\Bridge\Doctrine\PropertyInfo\DoctrineExtractor,
1 => \Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor,
2 => \Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor
Last two extractors are registered here:
vendor/symfony/framework-bundle/Resources/config/property_info.xml
and here:
\Symfony\Bundle\FrameworkBundle\DependencyInjection\FrameworkExtension::registerPropertyInfoConfiguration
and are tagged as property_info.type_extractor
with priorities PhpDocExtractor[-1001], ReflectionExtractor[-1002]
The problem is that PropertyInfoExtractor does not respect constructor type hinting, but this is wrong.
Argument types from constructor are the only types that are valid for the class instantiation. And neither priority of the extractors nor other magic should affect this.
How to reproduce
<?php declare(strict_types=1);
namespace App\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class Test extends Command
{
/** @var ContainerInterface */
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
parent::__construct('t:t');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$serializer = $this->container->get('serializer');
$obj = $serializer->denormalize(['uuid' => '74a6b770-2ba0-4b35-ae7f-4272ce5d613a'], DTO::class);
var_dump($obj);
}
}
interface UuidInterface {}
class DTO {
/** @var UuidInterface */
private $uuid;
public function __construct(string $uuid)
{
$this->uuid = new class($uuid) implements UuidInterface {
private $prop;
public function __construct($uuid)
{
$this->prop = $uuid;
}
};
}
}
Possible Solution
Respect constructor type hinting
Additional context
ErrorMessage:
The type of the "uuid" attribute for class "App\Command\DTO" must be one of "App\Command\UuidInterface" ("string" given).
Stack trace:
PropertyInfoExtractor.php:109, Symfony\Component\PropertyInfo\PropertyInfoExtractor->extract()
PropertyInfoExtractor.php:74, Symfony\Component\PropertyInfo\PropertyInfoExtractor->getTypes()
AbstractObjectNormalizer.php:383, Symfony\Component\Serializer\Normalizer\ObjectNormalizer->denormalizeParameter()
AbstractNormalizer.php:418, Symfony\Component\Serializer\Normalizer\ObjectNormalizer->instantiateObject()
AbstractObjectNormalizer.php:157, Symfony\Component\Serializer\Normalizer\ObjectNormalizer->instantiateObject()
AbstractObjectNormalizer.php:262, Symfony\Component\Serializer\Normalizer\ObjectNormalizer->denormalize()
Serializer.php:191, Symfony\Component\Serializer\Serializer->denormalize()
Serializer.php:142, Symfony\Component\Serializer\Serializer->deserialize()
SymfonySerializerAdapter.php:49, FOS\RestBundle\Serializer\SymfonySerializerAdapter->deserialize()
RequestBodyParamConverter.php:96, FOS\RestBundle\Request\RequestBodyParamConverter->apply()
ParamConverterManager.php:85, Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterManager->applyConverter()
ParamConverterManager.php:48, Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterManager->apply()
ParamConverterListener.php:78, Sensio\Bundle\FrameworkExtraBundle\EventListener\ParamConverterListener->onKernelController()
WrappedListener.php:111, Symfony\Component\EventDispatcher\Debug\WrappedListener->__invoke()
EventDispatcher.php:212, Symfony\Component\EventDispatcher\EventDispatcher->doDispatch()
EventDispatcher.php:44, Symfony\Component\EventDispatcher\EventDispatcher->dispatch()
TraceableEventDispatcher.php:145, Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher->dispatch()
HttpKernel.php:138, Symfony\Component\HttpKernel\HttpKernel->handleRaw()
HttpKernel.php:67, Symfony\Component\HttpKernel\HttpKernel->handle()
Kernel.php:198, App\Kernel->handle()
index.php:37, {main}()