Skip to content

[PropertyInfo] PhpStanExtractor is not correctly resolving types declared in traits #54569

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
alexandre-le-borgne opened this issue Apr 11, 2024 · 2 comments · Fixed by #54675

Comments

@alexandre-le-borgne
Copy link

alexandre-le-borgne commented Apr 11, 2024

Symfony version(s) affected

7.0.6 (latest) and at least 6.4.3

Description

When I denormalize an array to a class,
If this class has a property declared in a trait whose type described in phpDoc is a class in the same namespace as the trait but not in the same namespace as the class, then I get this error :

image

PHP Fatal error: Uncaught Symfony\Component\Serializer\Exception\NotNormalizableValueException: The type of the "children" attribute for class "App\Example\Example" must be one of "App\Example\ExampleChild[]" ("array" given). in /home/aleborgne/Sites/bug-symfony-php-stan-extractor-traits/vendor/symfony/serializer/Exception/NotNormalizableValueException.php:32
Stack trace:
#0 /home/aleborgne/Sites/bug-symfony-php-stan-extractor-traits/vendor/symfony/serializer/Normalizer/AbstractObjectNormalizer.php(589): Symfony\Component\Serializer\Exception\NotNormalizableValueException::createForUnexpectedDataType()
#1 /home/aleborgne/Sites/bug-symfony-php-stan-extractor-traits/vendor/symfony/serializer/Normalizer/AbstractObjectNormalizer.php(358): Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer->validateAndDenormalize()
#2 /home/aleborgne/Sites/bug-symfony-php-stan-extractor-traits/vendor/symfony/serializer/Serializer.php(238): Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer->denormalize()
#3 /home/aleborgne/Sites/bug-symfony-php-stan-extractor-traits/index.php(55): Symfony\Component\Serializer\Serializer->denormalize()
#4 {main}
thrown in /home/aleborgne/Sites/bug-symfony-php-stan-extractor-traits/vendor/symfony/serializer/Exception/NotNormalizableValueException.php on line 32

How to reproduce

Clone https://github.com/alexandre-le-borgne/bug-symfony-php-stan-extractor-traits
and run php index.php

Dependencies:

    "php": ">=8.3",
    "symfony/property-info": "^7.0",
    "phpdocumentor/reflection-docblock": "^5.3",
    "symfony/serializer": "^7.0",
    "symfony/property-access": "^7.0"
<?php

use App\Example\Example;
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
use Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor;
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;

require_once __DIR__ . '/vendor/autoload.php';

$phpDocExtractor = new PhpDocExtractor();
$phpStanExtractor = new PhpStanExtractor();
$reflectionExtractor = new ReflectionExtractor();

$listExtractors = [$reflectionExtractor];

$typeExtractors = [
    $phpStanExtractor, // Comment this line to avoid the bug
    $phpDocExtractor,
    $reflectionExtractor,
];

$descriptionExtractors = [$phpDocExtractor];
$accessExtractors = [$reflectionExtractor];
$propertyInitializableExtractors = [$reflectionExtractor];

$propertyInfo = new PropertyInfoExtractor(
    $listExtractors,
    $typeExtractors,
    $descriptionExtractors,
    $accessExtractors,
    $propertyInitializableExtractors
);

$properties = $propertyInfo->getTypes(Example::class, 'children');

// MUST returns ""App\Child\ExampleChild"" but it returns "App\Example\ExampleChild"
dump($properties[0]->getCollectionValueTypes()[0]->getClassName());

$normalizers = [
    new ArrayDenormalizer(),
    new ObjectNormalizer(propertyTypeExtractor: $propertyInfo),
];

$serializer = new Serializer($normalizers);

$result = $serializer->denormalize([
    'children' => [
        'testProperty' => '123',
    ],
], Example::class);

dump($result);

image

Possible Solution

Disable phpStanExtractor
Fix phpStanExtractor

Additional Context

It works when using only PhpDocExtractor

I think this bug was fixed in the PhpDocExtractor and then reintroduced when phpDocExtractor was replaced by PhpStanExtractor

The fix of PhpDocExtractor: #40175

The replacement of PhpDocExtractor by PhpStanExtractor: #40457

@alexandre-le-borgne
Copy link
Author

Workarround :

class Kernel extends BaseKernel
{
    use MicroKernelTrait;

    protected function build(ContainerBuilder $container): void
    {
        $container->addCompilerPass(new ServicesOverridePass());
    }
}

class ServicesOverridePass implements CompilerPassInterface
{
    #[\Override]
    public function process(ContainerBuilder $container): void
    {
   
        $definition = $container->getDefinition('property_info');
        /** @var IteratorArgument $argument */
        $argument = $definition->getArgument(1);
        $argument->setValues(array_filter($argument->getValues(), function (Reference $reference) {
            return $reference->__toString() !== 'property_info.phpstan_extractor';
        }));
    }
}

@mtarld
Copy link
Contributor

mtarld commented Apr 19, 2024

Thanks for that great issue description @alexandre-le-borgne, I just submitted a fix.

nicolas-grekas added a commit that referenced this issue Apr 22, 2024
This PR was merged into the 5.4 branch.

Discussion
----------

[PropertyInfo] Fix PHPStan properties type in trait

| Q             | A
| ------------- | ---
| Branch?       | 5.4
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Issues        | Fix #54569
| License       | MIT

Handle traits properly in `PhpStanExtractor`.

Commits
-------

0b1b275 [PropertyInfo] Fix PHPStan properties type in trait
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants