Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit c9f5ceb

Browse files
Merge remote-tracking branch 'origin/1.3.x' into 1.4.x
2 parents 67e208d + d8a0bc0 commit c9f5ceb

22 files changed

+372
-2
lines changed

‎extension.neon‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ parameters:
1414
featureToggles:
1515
skipCheckGenericClasses:
1616
- Symfony\Component\Form\AbstractType
17+
- Symfony\Component\Form\FormBuilderInterface
18+
- Symfony\Component\Form\FormConfigBuilderInterface
19+
- Symfony\Component\Form\FormConfigInterface
1720
- Symfony\Component\Form\FormInterface
1821
- Symfony\Component\Form\FormTypeExtensionInterface
1922
- Symfony\Component\Form\FormTypeInterface
@@ -47,6 +50,8 @@ parameters:
4750
- stubs/Symfony/Component/Form/Exception/TransformationFailedException.stub
4851
- stubs/Symfony/Component/Form/DataTransformerInterface.stub
4952
- stubs/Symfony/Component/Form/FormBuilderInterface.stub
53+
- stubs/Symfony/Component/Form/FormConfigBuilderInterface.stub
54+
- stubs/Symfony/Component/Form/FormConfigInterface.stub
5055
- stubs/Symfony/Component/Form/FormInterface.stub
5156
- stubs/Symfony/Component/Form/FormFactoryInterface.stub
5257
- stubs/Symfony/Component/Form/FormTypeExtensionInterface.stub
@@ -341,3 +346,8 @@ services:
341346
-
342347
factory: PHPStan\Type\Symfony\CacheInterfaceGetDynamicReturnTypeExtension
343348
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
349+
350+
# Extension::getConfiguration() return type
351+
-
352+
factory: PHPStan\Type\Symfony\ExtensionGetConfigurationReturnTypeExtension
353+
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Symfony;
4+
5+
use PhpParser\Node\Expr\MethodCall;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Reflection\ClassReflection;
8+
use PHPStan\Reflection\MethodReflection;
9+
use PHPStan\Reflection\ReflectionProvider;
10+
use PHPStan\Type\DynamicMethodReturnTypeExtension;
11+
use PHPStan\Type\NullType;
12+
use PHPStan\Type\ObjectType;
13+
use PHPStan\Type\Type;
14+
use PHPStan\Type\TypeCombinator;
15+
use function str_contains;
16+
use function strrpos;
17+
use function substr_replace;
18+
19+
class ExtensionGetConfigurationReturnTypeExtension implements DynamicMethodReturnTypeExtension
20+
{
21+
22+
/** @var ReflectionProvider */
23+
private $reflectionProvider;
24+
25+
public function __construct(ReflectionProvider $reflectionProvider)
26+
{
27+
$this->reflectionProvider = $reflectionProvider;
28+
}
29+
30+
public function getClass(): string
31+
{
32+
return 'Symfony\Component\DependencyInjection\Extension\Extension';
33+
}
34+
35+
public function isMethodSupported(MethodReflection $methodReflection): bool
36+
{
37+
return $methodReflection->getName() === 'getConfiguration'
38+
&& $methodReflection->getDeclaringClass()->getName() === 'Symfony\Component\DependencyInjection\Extension\Extension';
39+
}
40+
41+
public function getTypeFromMethodCall(
42+
MethodReflection $methodReflection,
43+
MethodCall $methodCall,
44+
Scope $scope
45+
): ?Type
46+
{
47+
$types = [];
48+
$extensionType = $scope->getType($methodCall->var);
49+
$classes = $extensionType->getObjectClassNames();
50+
51+
foreach ($classes as $extensionName) {
52+
if (str_contains($extensionName, "0円")) {
53+
$types[] = new NullType();
54+
continue;
55+
}
56+
57+
$lastBackslash = strrpos($extensionName, '\\');
58+
if ($lastBackslash === false) {
59+
$types[] = new NullType();
60+
continue;
61+
}
62+
63+
$configurationName = substr_replace($extensionName, '\Configuration', $lastBackslash);
64+
if (!$this->reflectionProvider->hasClass($configurationName)) {
65+
$types[] = new NullType();
66+
continue;
67+
}
68+
69+
$reflection = $this->reflectionProvider->getClass($configurationName);
70+
if ($this->hasRequiredConstructor($reflection)) {
71+
$types[] = new NullType();
72+
continue;
73+
}
74+
75+
$types[] = new ObjectType($configurationName);
76+
}
77+
78+
return TypeCombinator::union(...$types);
79+
}
80+
81+
private function hasRequiredConstructor(ClassReflection $class): bool
82+
{
83+
if (!$class->hasConstructor()) {
84+
return false;
85+
}
86+
87+
$constructor = $class->getConstructor();
88+
foreach ($constructor->getVariants() as $variant) {
89+
$anyRequired = false;
90+
foreach ($variant->getParameters() as $parameter) {
91+
if (!$parameter->isOptional()) {
92+
$anyRequired = true;
93+
break;
94+
}
95+
}
96+
97+
if (!$anyRequired) {
98+
return false;
99+
}
100+
}
101+
102+
return true;
103+
}
104+
105+
}

‎stubs/Symfony/Component/Form/AbstractType.stub‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ abstract class AbstractType implements FormTypeInterface
1111
{
1212

1313
/**
14+
* @param FormBuilderInterface<TData|null> $builder
1415
* @param array<string, mixed> $options
1516
*/
1617
public function buildForm(FormBuilderInterface $builder, array $options): void;

‎stubs/Symfony/Component/Form/FormBuilderInterface.stub‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,17 @@
33
namespace Symfony\Component\Form;
44

55
/**
6-
* @extends \Traversable<int, \Symfony\Component\Form\FormBuilderInterface>
6+
* @template TData
7+
*
8+
* @extends \Traversable<int, \Symfony\Component\Form\FormBuilderInterface<mixed>>
9+
* @extends FormConfigBuilderInterface<TData>
710
*/
8-
interface FormBuilderInterface extends \Traversable
11+
interface FormBuilderInterface extends \Traversable, \Countable, FormConfigBuilderInterface
912
{
1013

14+
/**
15+
* @return FormInterface<TData|null>
16+
*/
17+
public function getForm(): FormInterface;
18+
1119
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Symfony\Component\Form;
4+
5+
/**
6+
* @template TData
7+
*
8+
* @extends FormConfigInterface<TData>
9+
*/
10+
interface FormConfigBuilderInterface extends FormConfigInterface
11+
{
12+
13+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Symfony\Component\Form;
4+
5+
/**
6+
* @template TData
7+
*/
8+
interface FormConfigInterface
9+
{
10+
11+
/**
12+
* @return TData
13+
*/
14+
public function getData(): mixed;
15+
16+
}

‎stubs/Symfony/Component/Form/FormTypeExtensionInterface.stub‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ namespace Symfony\Component\Form;
88
interface FormTypeExtensionInterface
99
{
1010
/**
11+
* @param FormBuilderInterface<TData|null> $builder
1112
* @param array<string, mixed> $options
1213
*/
1314
public function buildForm(FormBuilderInterface $builder, array $options): void;

‎stubs/Symfony/Component/Form/FormTypeInterface.stub‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ namespace Symfony\Component\Form;
88
interface FormTypeInterface
99
{
1010
/**
11+
* @param FormBuilderInterface<TData|null> $builder
1112
* @param array<string, mixed> $options
1213
*/
1314
public function buildForm(FormBuilderInterface $builder, array $options): void;

‎tests/Type/Symfony/ExtensionTest.php‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,15 @@ public function dataFileAsserts(): iterable
5757
yield from $this->gatherAssertTypes(__DIR__ . '/data/FormInterface_getErrors.php');
5858
yield from $this->gatherAssertTypes(__DIR__ . '/data/cache.php');
5959
yield from $this->gatherAssertTypes(__DIR__ . '/data/form_data_type.php');
60+
61+
yield from $this->gatherAssertTypes(__DIR__ . '/data/extension/with-configuration/WithConfigurationExtension.php');
62+
yield from $this->gatherAssertTypes(__DIR__ . '/data/extension/without-configuration/WithoutConfigurationExtension.php');
63+
yield from $this->gatherAssertTypes(__DIR__ . '/data/extension/anonymous/AnonymousExtension.php');
64+
yield from $this->gatherAssertTypes(__DIR__ . '/data/extension/ignore-implemented/IgnoreImplementedExtension.php');
65+
yield from $this->gatherAssertTypes(__DIR__ . '/data/extension/multiple-types/MultipleTypes.php');
66+
yield from $this->gatherAssertTypes(__DIR__ . '/data/extension/with-configuration-with-constructor/WithConfigurationWithConstructorExtension.php');
67+
yield from $this->gatherAssertTypes(__DIR__ . '/data/extension/with-configuration-with-constructor-optional-params/WithConfigurationWithConstructorOptionalParamsExtension.php');
68+
yield from $this->gatherAssertTypes(__DIR__ . '/data/extension/with-configuration-with-constructor-required-params/WithConfigurationWithConstructorRequiredParamsExtension.php');
6069
}
6170

6271
/**
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace PHPStan\Type\Symfony\Extension\Anonymous;
4+
5+
use Symfony\Component\DependencyInjection\ContainerBuilder;
6+
use Symfony\Component\DependencyInjection\Extension\Extension;
7+
8+
new class extends Extension
9+
{
10+
public function load(array $configs, ContainerBuilder $container)
11+
{
12+
\PHPStan\Testing\assertType(
13+
'null',
14+
$this->getConfiguration($configs, $container)
15+
);
16+
}
17+
};

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /