From d5960d8acef6e2b3ba5c81e580c0c7f6f806e4f4 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 24 Dec 2024 14:11:55 +0100 Subject: [PATCH] Add stub for form options --- phpstan-baseline.neon | 5 + .../Controller/AbstractController.stub | 5 +- .../Symfony/Component/Form/AbstractType.stub | 9 +- .../Component/Form/FormFactoryInterface.stub | 12 ++- .../Form/FormTypeExtensionInterface.stub | 11 +- .../Component/Form/FormTypeInterface.stub | 7 +- tests/Type/Symfony/CallMethodsRuleTest.php | 33 ++++++ tests/Type/Symfony/ExtensionTest.php | 1 + tests/Type/Symfony/data/form_options.php | 102 ++++++++++++++++++ 9 files changed, 166 insertions(+), 19 deletions(-) create mode 100644 tests/Type/Symfony/CallMethodsRuleTest.php create mode 100644 tests/Type/Symfony/data/form_options.php diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 0f6edd5c..66b7273f 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -24,3 +24,8 @@ parameters: message: "#^Accessing PHPStan\\\\Rules\\\\Comparison\\\\ImpossibleCheckTypeMethodCallRule\\:\\:class is not covered by backward compatibility promise\\. The class might change in a minor PHPStan version\\.$#" count: 1 path: tests/Type/Symfony/ImpossibleCheckTypeMethodCallRuleTest.php + + - + message: "#^Accessing PHPStan\\\\Rules\\\\Methods\\\\CallMethodsRule\\:\\:class is not covered by backward compatibility promise\\. The class might change in a minor PHPStan version\\.$#" + count: 1 + path: tests/Type/Symfony/CallMethodsRuleTest.php diff --git a/stubs/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.stub b/stubs/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.stub index 075dce6d..788e85ab 100644 --- a/stubs/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.stub +++ b/stubs/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.stub @@ -9,12 +9,13 @@ use Symfony\Contracts\Service\ServiceSubscriberInterface; abstract class AbstractController implements ServiceSubscriberInterface { /** - * @template TFormType of FormTypeInterface + * @template TFormType of FormTypeInterface * @template TData + * @template TOptions of array * * @param class-string $type * @param TData $data - * @param array $options + * @param TOptions $options * * @phpstan-return ($data is null ? FormInterface : 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..279f33c8 100644 --- a/stubs/Symfony/Component/Form/FormFactoryInterface.stub +++ b/stubs/Symfony/Component/Form/FormFactoryInterface.stub @@ -7,12 +7,13 @@ use Symfony\Component\Form\Extension\Core\Type\FormType; interface FormFactoryInterface { /** - * @template TFormType of FormTypeInterface + * @template TFormType of FormTypeInterface * @template TData + * @template TOptions of array * * @param class-string $type * @param TData $data - * @param array $options + * @param TOptions $options * * @phpstan-return ($data is null ? FormInterface : FormInterface) * @@ -21,12 +22,13 @@ interface FormFactoryInterface public function create(string $type = FormType::class, $data = null, array $options = []): FormInterface; /** - * @template TFormType of FormTypeInterface + * @template TFormType of FormTypeInterface * @template TData + * @template TOptions of array * - * @param class-string $type + * @param class-string $type * @param TData $data - * @param array $options + * @param TOptions $options * * @phpstan-return ($data is null ? FormInterface : FormInterface) * 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..bfabedcd --- /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_options.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..1672740e --- /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); + } + +}