Skip to content

Commit 29ad8a9

Browse files
mitelgondrejmirtes
authored andcommitted
Introduce DynamicReturnTypeExtension for 'Symfony\Component\Form\FormInterface::getErrors'
fixes #166
1 parent 19b0934 commit 29ad8a9

File tree

5 files changed

+92
-0
lines changed

5 files changed

+92
-0
lines changed

composer.json

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"phpunit/phpunit": "^9.5",
2929
"symfony/config": "^4.2 || ^5.0",
3030
"symfony/console": "^4.0 || ^5.0",
31+
"symfony/form": "^4.0 || ^5.0",
3132
"symfony/framework-bundle": "^4.4 || ^5.0",
3233
"symfony/http-foundation": "^4.0 || ^5.0",
3334
"symfony/messenger": "^4.2 || ^5.0",

extension.neon

+5
Original file line numberDiff line numberDiff line change
@@ -249,3 +249,8 @@ services:
249249
class: PHPStan\Symfony\InputBagStubFilesExtension
250250
tags:
251251
- phpstan.stubFilesExtension
252+
253+
# FormInterface::getErrors() return type
254+
-
255+
factory: PHPStan\Type\Symfony\Form\FormInterfaceDynamicReturnTypeExtension
256+
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Symfony\Form;
4+
5+
use PhpParser\Node\Expr\MethodCall;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Reflection\MethodReflection;
8+
use PHPStan\Type\Constant\ConstantBooleanType;
9+
use PHPStan\Type\DynamicMethodReturnTypeExtension;
10+
use PHPStan\Type\Generic\GenericObjectType;
11+
use PHPStan\Type\ObjectType;
12+
use PHPStan\Type\Type;
13+
use PHPStan\Type\UnionType;
14+
use Symfony\Component\Form\FormError;
15+
use Symfony\Component\Form\FormErrorIterator;
16+
use Symfony\Component\Form\FormInterface;
17+
18+
final class FormInterfaceDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
19+
{
20+
21+
public function getClass(): string
22+
{
23+
return FormInterface::class;
24+
}
25+
26+
public function isMethodSupported(MethodReflection $methodReflection): bool
27+
{
28+
return $methodReflection->getName() === 'getErrors';
29+
}
30+
31+
public function getTypeFromMethodCall(
32+
MethodReflection $methodReflection,
33+
MethodCall $methodCall,
34+
Scope $scope
35+
): Type
36+
{
37+
if (!isset($methodCall->getArgs()[1])) {
38+
return new GenericObjectType(FormErrorIterator::class, [new ObjectType(FormError::class)]);
39+
}
40+
41+
$firstArgType = $scope->getType($methodCall->getArgs()[0]->value);
42+
$secondArgType = $scope->getType($methodCall->getArgs()[1]->value);
43+
44+
$firstIsTrueType = (new ConstantBooleanType(true))->isSuperTypeOf($firstArgType);
45+
$firstIsFalseType = (new ConstantBooleanType(false))->isSuperTypeOf($firstArgType);
46+
$secondIsTrueType = (new ConstantBooleanType(true))->isSuperTypeOf($secondArgType);
47+
$secondIsFalseType = (new ConstantBooleanType(false))->isSuperTypeOf($secondArgType);
48+
49+
$firstCompareType = $firstIsTrueType->compareTo($firstIsFalseType);
50+
$secondCompareType = $secondIsTrueType->compareTo($secondIsFalseType);
51+
52+
if ($firstCompareType === $firstIsTrueType && $secondCompareType === $secondIsFalseType) {
53+
return new GenericObjectType(FormErrorIterator::class, [
54+
new UnionType([
55+
new ObjectType(FormError::class),
56+
new ObjectType(FormErrorIterator::class),
57+
]),
58+
]);
59+
}
60+
61+
return new GenericObjectType(FormErrorIterator::class, [new ObjectType(FormError::class)]);
62+
}
63+
64+
}

tests/Type/Symfony/ExtensionTest.php

+2
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ public function dataFileAsserts(): iterable
4949
}
5050

5151
yield from $this->gatherAssertTypes(__DIR__ . '/data/denormalizer.php');
52+
53+
yield from $this->gatherAssertTypes(__DIR__ . '/data/FormInterface_getErrors.php');
5254
}
5355

5456
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php declare(strict_types=1);
2+
3+
use Symfony\Component\Form\FormError;
4+
use Symfony\Component\Form\FormErrorIterator;
5+
use Symfony\Component\Form\FormInterface;
6+
use function PHPStan\Testing\assertType;
7+
8+
/** @var FormInterface $form */
9+
$form = new stdClass();
10+
11+
assertType(FormErrorIterator::class . '<'. FormError::class .'>', $form->getErrors());
12+
assertType(FormErrorIterator::class . '<'. FormError::class .'>', $form->getErrors(false));
13+
assertType(FormErrorIterator::class . '<'. FormError::class .'>', $form->getErrors(false, true));
14+
15+
assertType(FormErrorIterator::class . '<'. FormError::class .'>', $form->getErrors(true));
16+
assertType(FormErrorIterator::class . '<'. FormError::class .'>', $form->getErrors(true, true));
17+
18+
assertType(FormErrorIterator::class . '<'. FormError::class .'>', $form->getErrors(false, false));
19+
20+
assertType(FormErrorIterator::class . '<'. FormError::class .'|'. FormErrorIterator::class . '>', $form->getErrors(true, false));

0 commit comments

Comments
 (0)