diff --git a/composer.json b/composer.json index 6d99ca8f..4126f9e2 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,8 @@ "slevomat/coding-standard": "^4.5.2", "phpstan/phpstan-phpunit": "^0.11", "symfony/framework-bundle": "^3.0 || ^4.0", - "squizlabs/php_codesniffer": "^3.3.2" + "squizlabs/php_codesniffer": "^3.3.2", + "symfony/serializer": "^3|^4" }, "conflict": { "symfony/framework-bundle": "<3.0" diff --git a/extension.neon b/extension.neon index 4c4b7077..07424feb 100644 --- a/extension.neon +++ b/extension.neon @@ -45,3 +45,8 @@ services: - class: PHPStan\Type\Symfony\HeaderBagDynamicReturnTypeExtension tags: [phpstan.broker.dynamicMethodReturnTypeExtension] + + # SerializerInterface::deserialize() return type + - + class: PHPStan\Type\Symfony\SerializerInterfaceDynamicReturnTypeExtension + tags: [phpstan.broker.dynamicMethodReturnTypeExtension] diff --git a/src/Type/Symfony/SerializerInterfaceDynamicReturnTypeExtension.php b/src/Type/Symfony/SerializerInterfaceDynamicReturnTypeExtension.php new file mode 100644 index 00000000..671a106d --- /dev/null +++ b/src/Type/Symfony/SerializerInterfaceDynamicReturnTypeExtension.php @@ -0,0 +1,45 @@ +getName() === 'deserialize'; + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type + { + $argType = $scope->getType($methodCall->args[1]->value); + if (!$argType instanceof ConstantStringType) { + return new MixedType(); + } + + $objectName = $argType->getValue(); + + if (substr($objectName, -2) === '[]') { + // The key type is determined by the data + return new ArrayType(new MixedType(false), new ObjectType(substr($objectName, 0, -2))); + } + + return new ObjectType($objectName); + } + +} diff --git a/tests/Symfony/NeonTest.php b/tests/Symfony/NeonTest.php index 77eda070..e14ef8f2 100644 --- a/tests/Symfony/NeonTest.php +++ b/tests/Symfony/NeonTest.php @@ -40,7 +40,7 @@ public function testExtensionNeon(): void ], $container->getParameters()); self::assertCount(2, $container->findByTag('phpstan.rules.rule')); - self::assertCount(5, $container->findByTag('phpstan.broker.dynamicMethodReturnTypeExtension')); + self::assertCount(6, $container->findByTag('phpstan.broker.dynamicMethodReturnTypeExtension')); self::assertCount(3, $container->findByTag('phpstan.typeSpecifier.methodTypeSpecifyingExtension')); self::assertInstanceOf(ServiceMap::class, $container->getByType(ServiceMap::class)); } diff --git a/tests/Type/Symfony/SerializerInterfaceDynamicReturnTypeExtensionTest.php b/tests/Type/Symfony/SerializerInterfaceDynamicReturnTypeExtensionTest.php new file mode 100644 index 00000000..3cf715b5 --- /dev/null +++ b/tests/Type/Symfony/SerializerInterfaceDynamicReturnTypeExtensionTest.php @@ -0,0 +1,29 @@ +processFile( + __DIR__ . '/serializer.php', + $expression, + $type, + new SerializerInterfaceDynamicReturnTypeExtension() + ); + } + + public function getContentProvider(): Iterator + { + yield ['$first', 'Bar']; + yield ['$second', 'array']; + } + +} diff --git a/tests/Type/Symfony/serializer.php b/tests/Type/Symfony/serializer.php new file mode 100644 index 00000000..98550c5f --- /dev/null +++ b/tests/Type/Symfony/serializer.php @@ -0,0 +1,8 @@ +deserialize('bar', 'Bar', 'format'); +$second = $serializer->deserialize('bar', 'Bar[]', 'format'); + +die;