diff --git a/stubs/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.stub b/stubs/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.stub index 075dce6d..0507a9b4 100644 --- a/stubs/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.stub +++ b/stubs/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.stub @@ -9,14 +9,15 @@ use Symfony\Contracts\Service\ServiceSubscriberInterface; abstract class AbstractController implements ServiceSubscriberInterface { /** - * @template TFormType of FormTypeInterface + * @template TFormType of FormTypeInterface * @template TData + * @template TOptions * * @param class-string $type * @param TData $data - * @param array $options + * @param TOptions $options * - * @phpstan-return ($data is null ? FormInterface : FormInterface) + * @phpstan-return ($data is null ? FormInterface : FormInterface) */ protected function createForm(string $type, $data = null, array $options = []): FormInterface { diff --git a/stubs/Symfony/Component/Form/AbstractType.stub b/stubs/Symfony/Component/Form/AbstractType.stub index e99b746c..cfd9ed71 100644 --- a/stubs/Symfony/Component/Form/AbstractType.stub +++ b/stubs/Symfony/Component/Form/AbstractType.stub @@ -4,27 +4,28 @@ namespace Symfony\Component\Form; /** * @template TData + * @template TOptions of array * - * @implements FormTypeInterface + * @implements FormTypeInterface */ abstract class AbstractType implements FormTypeInterface { /** * @param FormBuilderInterface $builder - * @param array $options + * @param TOptions $options */ public function buildForm(FormBuilderInterface $builder, array $options): void; /** * @param FormInterface $form - * @param array $options + * @param TOptions $options */ public function buildView(FormView $view, FormInterface $form, array $options): void; /** * @param FormInterface $form - * @param array $options + * @param TOptions $options */ public function finishView(FormView $view, FormInterface $form, array $options): void; diff --git a/stubs/Symfony/Component/Form/FormFactoryInterface.stub b/stubs/Symfony/Component/Form/FormFactoryInterface.stub index a2221ec0..21c43d8c 100644 --- a/stubs/Symfony/Component/Form/FormFactoryInterface.stub +++ b/stubs/Symfony/Component/Form/FormFactoryInterface.stub @@ -7,14 +7,15 @@ use Symfony\Component\Form\Extension\Core\Type\FormType; interface FormFactoryInterface { /** - * @template TFormType of FormTypeInterface + * @template TFormType of FormTypeInterface * @template TData + * @template TOptions * * @param class-string $type * @param TData $data - * @param array $options + * @param TOptions $options * - * @phpstan-return ($data is null ? FormInterface : FormInterface) + * @phpstan-return ($data is null ? FormInterface : FormInterface) * * @throws \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException */ @@ -24,7 +25,7 @@ interface FormFactoryInterface * @template TFormType of FormTypeInterface * @template TData * - * @param class-string $type + * @param class-string $type * @param TData $data * @param array $options * diff --git a/stubs/Symfony/Component/Form/FormTypeExtensionInterface.stub b/stubs/Symfony/Component/Form/FormTypeExtensionInterface.stub index a03d5e1c..256b6c41 100644 --- a/stubs/Symfony/Component/Form/FormTypeExtensionInterface.stub +++ b/stubs/Symfony/Component/Form/FormTypeExtensionInterface.stub @@ -4,24 +4,25 @@ namespace Symfony\Component\Form; /** * @template TData + * @template TOptions of array */ interface FormTypeExtensionInterface { /** * @param FormBuilderInterface $builder - * @param array $options + * @param TOptions $options */ public function buildForm(FormBuilderInterface $builder, array $options): void; /** - * @phpstan-param FormInterface $form - * @param array $options + * @param FormInterface $form + * @param TOptions $options */ public function buildView(FormView $view, FormInterface $form, array $options): void; /** - * @phpstan-param FormInterface $form - * @param array $options + * @param FormInterface $form + * @param TOptions $options */ public function finishView(FormView $view, FormInterface $form, array $options): void; } diff --git a/stubs/Symfony/Component/Form/FormTypeInterface.stub b/stubs/Symfony/Component/Form/FormTypeInterface.stub index 8536656a..0ebc96e8 100644 --- a/stubs/Symfony/Component/Form/FormTypeInterface.stub +++ b/stubs/Symfony/Component/Form/FormTypeInterface.stub @@ -4,24 +4,25 @@ namespace Symfony\Component\Form; /** * @template TData + * @template TOptions of array */ interface FormTypeInterface { /** * @param FormBuilderInterface $builder - * @param array $options + * @param TOptions $options */ public function buildForm(FormBuilderInterface $builder, array $options): void; /** * @param FormInterface $form - * @param array $options + * @param TOptions $options */ public function buildView(FormView $view, FormInterface $form, array $options): void; /** * @param FormInterface $form - * @param array $options + * @param TOptions $options */ public function finishView(FormView $view, FormInterface $form, array $options): void; } diff --git a/tests/Type/Symfony/CallMethodsRuleTest.php b/tests/Type/Symfony/CallMethodsRuleTest.php new file mode 100644 index 00000000..70a4ef12 --- /dev/null +++ b/tests/Type/Symfony/CallMethodsRuleTest.php @@ -0,0 +1,33 @@ + + */ +class CallMethodsRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return self::getContainer()->getByType(CallMethodsRule::class); + } + + public function testExtension(): void + { + $this->analyse([__DIR__ . '/data/form_data_type.php'], []); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/../../../extension.neon', + __DIR__ . '/../../../vendor/phpstan/phpstan-strict-rules/rules.neon', + ]; + } + +} diff --git a/tests/Type/Symfony/ExtensionTest.php b/tests/Type/Symfony/ExtensionTest.php index a076caac..59bffa42 100644 --- a/tests/Type/Symfony/ExtensionTest.php +++ b/tests/Type/Symfony/ExtensionTest.php @@ -58,6 +58,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/FormInterface_getErrors.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/cache.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/form_data_type.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/form_options.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/extension/with-configuration/WithConfigurationExtension.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/extension/without-configuration/WithoutConfigurationExtension.php'); diff --git a/tests/Type/Symfony/data/form_options.php b/tests/Type/Symfony/data/form_options.php new file mode 100644 index 00000000..e6201659 --- /dev/null +++ b/tests/Type/Symfony/data/form_options.php @@ -0,0 +1,102 @@ + + */ +class DataClassType extends AbstractType +{ + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + assertType('string', $options['required']); + assertType('int', $options['optional']); + + $builder + ->add('foo', NumberType::class) + ->add('bar', TextType::class) + ; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver + ->setDefaults([ + 'data_class' => DataClass::class, + 'optional' => 0, + ]) + ->setRequired('required') + ->setAllowedTypes('required', 'string') + ->setAllowedTypes('optional', 'int') + ; + } + +} + +class FormFactoryAwareClass +{ + + /** @var FormFactoryInterface */ + private $formFactory; + + public function __construct(FormFactoryInterface $formFactory) + { + $this->formFactory = $formFactory; + } + + public function doSomething(): void + { + $form = $this->formFactory->create(DataClassType::class, new DataClass()); + assertType('Symfony\Component\Form\FormInterface', $form); + } + + public function doSomethingWithOption(): void + { + $form = $this->formFactory->create(DataClassType::class, new DataClass(), ['required' => 'foo']); + assertType('Symfony\Component\Form\FormInterface', $form); + } + + public function doSomethingWithInvalidOption(): void + { + $form = $this->formFactory->create(DataClassType::class, new DataClass(), ['required' => 42]); + assertType('Symfony\Component\Form\FormInterface', $form); + } + +} + +class FormController extends AbstractController +{ + + public function doSomething(): void + { + $form = $this->createForm(DataClassType::class, new DataClass()); + assertType('Symfony\Component\Form\FormInterface', $form); + } + + public function doSomethingWithOption(): void + { + $form = $this->createForm(DataClassType::class, new DataClass(), ['required' => 'foo']); + assertType('Symfony\Component\Form\FormInterface', $form); + } + + public function doSomethingWithInvalidOption(): void + { + $form = $this->createForm(DataClassType::class, new DataClass(), ['required' => 42]); + assertType('Symfony\Component\Form\FormInterface', $form); + } + +}