Skip to content

Commit f45b9ce

Browse files
authored
Support specifying type of data_class in forms
1 parent 1da7bf4 commit f45b9ce

File tree

9 files changed

+159
-2
lines changed

9 files changed

+159
-2
lines changed

extension.neon

+7
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ parameters:
1313
consoleApplicationLoader: null
1414
featureToggles:
1515
skipCheckGenericClasses:
16+
- Symfony\Component\Form\AbstractType
17+
- Symfony\Component\Form\FormInterface
18+
- Symfony\Component\Form\FormTypeExtensionInterface
19+
- Symfony\Component\Form\FormTypeInterface
1620
- Symfony\Component\OptionsResolver\Options
1721
- Symfony\Component\Security\Core\Authorization\Voter\Voter
1822
- Symfony\Component\Security\Core\User\PasswordUpgraderInterface
@@ -36,13 +40,15 @@ parameters:
3640
- stubs/Symfony/Component/EventDispatcher/EventDispatcherInterface.stub
3741
- stubs/Symfony/Component/EventDispatcher/EventSubscriberInterface.stub
3842
- stubs/Symfony/Component/EventDispatcher/GenericEvent.stub
43+
- stubs/Symfony/Component/Form/AbstractType.stub
3944
- stubs/Symfony/Component/Form/ChoiceList/Loader/ChoiceLoaderInterface.stub
4045
- stubs/Symfony/Component/Form/Exception/ExceptionInterface.stub
4146
- stubs/Symfony/Component/Form/Exception/RuntimeException.stub
4247
- stubs/Symfony/Component/Form/Exception/TransformationFailedException.stub
4348
- stubs/Symfony/Component/Form/DataTransformerInterface.stub
4449
- stubs/Symfony/Component/Form/FormBuilderInterface.stub
4550
- stubs/Symfony/Component/Form/FormInterface.stub
51+
- stubs/Symfony/Component/Form/FormFactoryInterface.stub
4652
- stubs/Symfony/Component/Form/FormTypeExtensionInterface.stub
4753
- stubs/Symfony/Component/Form/FormTypeInterface.stub
4854
- stubs/Symfony/Component/Form/FormView.stub
@@ -52,6 +58,7 @@ parameters:
5258
- stubs/Symfony/Component/HttpFoundation/Session.stub
5359
- stubs/Symfony/Component/Messenger/StampInterface.stub
5460
- stubs/Symfony/Component/Messenger/Envelope.stub
61+
- stubs/Symfony/Component/OptionsResolver/Exception/InvalidOptionsException.stub
5562
- stubs/Symfony/Component/OptionsResolver/Options.stub
5663
- stubs/Symfony/Component/Process/Process.stub
5764
- stubs/Symfony/Component/PropertyAccess/PropertyPathInterface.stub
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
namespace Symfony\Component\Form;
4+
5+
/**
6+
* @template TData
7+
*
8+
* @implements FormTypeInterface<TData>
9+
*/
10+
abstract class AbstractType implements FormTypeInterface
11+
{
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace Symfony\Component\Form;
4+
5+
use Symfony\Component\Form\Extension\Core\Type\FormType;
6+
7+
interface FormFactoryInterface
8+
{
9+
/**
10+
* @template TFormType of FormTypeInterface<TData>
11+
* @template TData
12+
*
13+
* @param class-string<TFormType> $type
14+
* @param TData $data
15+
* @param array<string, mixed> $options
16+
*
17+
* @phpstan-return ($data is null ? FormInterface<null|TData> : FormInterface<TData>)
18+
*
19+
* @throws \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
20+
*/
21+
public function create(string $type = FormType::class, $data = null, array $options = []): FormInterface;
22+
23+
/**
24+
* @template TFormType of FormTypeInterface<TData>
25+
* @template TData
26+
*
27+
* @param class-string<TFormType> $type
28+
* @param TData $data
29+
* @param array<string, mixed> $options
30+
*
31+
* @phpstan-return ($data is null ? FormInterface<null|TData> : FormInterface<TData>)
32+
*
33+
* @throws \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
34+
*/
35+
public function createNamed(string $name, string $type = FormType::class, $data = null, array $options = []): FormInterface;
36+
}

stubs/Symfony/Component/Form/FormInterface.stub

+14-2
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,22 @@
33
namespace Symfony\Component\Form;
44

55
/**
6-
* @extends \ArrayAccess<string, \Symfony\Component\Form\FormInterface>
7-
* @extends \Traversable<string, \Symfony\Component\Form\FormInterface>
6+
* @template TData
7+
*
8+
* @extends \ArrayAccess<string, \Symfony\Component\Form\FormInterface<mixed>>
9+
* @extends \Traversable<string, \Symfony\Component\Form\FormInterface<mixed>>
810
*/
911
interface FormInterface extends \ArrayAccess, \Traversable, \Countable
1012
{
13+
/**
14+
* @param TData $modelData
15+
*
16+
* @return $this
17+
*/
18+
public function setData($modelData): FormInterface;
1119

20+
/**
21+
* @return TData
22+
*/
23+
public function getData();
1224
}

stubs/Symfony/Component/Form/FormTypeExtensionInterface.stub

+5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
namespace Symfony\Component\Form;
44

5+
/**
6+
* @template TData
7+
*/
58
interface FormTypeExtensionInterface
69
{
710
/**
@@ -10,11 +13,13 @@ interface FormTypeExtensionInterface
1013
public function buildForm(FormBuilderInterface $builder, array $options): void;
1114

1215
/**
16+
* @phpstan-param FormInterface<TData> $form
1317
* @param array<string, mixed> $options
1418
*/
1519
public function buildView(FormView $view, FormInterface $form, array $options): void;
1620

1721
/**
22+
* @phpstan-param FormInterface<TData> $form
1823
* @param array<string, mixed> $options
1924
*/
2025
public function finishView(FormView $view, FormInterface $form, array $options): void;

stubs/Symfony/Component/Form/FormTypeInterface.stub

+5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
namespace Symfony\Component\Form;
44

5+
/**
6+
* @template TData
7+
*/
58
interface FormTypeInterface
69
{
710
/**
@@ -10,11 +13,13 @@ interface FormTypeInterface
1013
public function buildForm(FormBuilderInterface $builder, array $options): void;
1114

1215
/**
16+
* @phpstan-param FormInterface<TData> $form
1317
* @param array<string, mixed> $options
1418
*/
1519
public function buildView(FormView $view, FormInterface $form, array $options): void;
1620

1721
/**
22+
* @phpstan-param FormInterface<TData> $form
1823
* @param array<string, mixed> $options
1924
*/
2025
public function finishView(FormView $view, FormInterface $form, array $options): void;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace Symfony\Component\OptionsResolver\Exception;
4+
5+
class InvalidOptionsException extends \InvalidArgumentException
6+
{
7+
}

tests/Type/Symfony/ExtensionTest.php

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ public function dataFileAsserts(): iterable
5656

5757
yield from $this->gatherAssertTypes(__DIR__ . '/data/FormInterface_getErrors.php');
5858
yield from $this->gatherAssertTypes(__DIR__ . '/data/cache.php');
59+
yield from $this->gatherAssertTypes(__DIR__ . '/data/form_data_type.php');
5960
}
6061

6162
/**
+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace GenericFormDataType;
4+
5+
use Symfony\Component\Form\AbstractType;
6+
use Symfony\Component\Form\Extension\Core\Type\NumberType;
7+
use Symfony\Component\Form\Extension\Core\Type\TextType;
8+
use Symfony\Component\Form\FormBuilderInterface;
9+
use Symfony\Component\Form\FormFactoryInterface;
10+
use Symfony\Component\OptionsResolver\OptionsResolver;
11+
use function PHPStan\Testing\assertType;
12+
13+
class DataClass
14+
{
15+
16+
/** @var int */
17+
public $foo;
18+
19+
/** @var string */
20+
public $bar;
21+
22+
}
23+
24+
/**
25+
* @extends AbstractType<DataClass>
26+
*/
27+
class DataClassType extends AbstractType
28+
{
29+
30+
public function buildForm(FormBuilderInterface $builder, array $options): void
31+
{
32+
$builder
33+
->add('foo', NumberType::class)
34+
->add('bar', TextType::class)
35+
;
36+
}
37+
38+
public function configureOptions(OptionsResolver $resolver): void
39+
{
40+
$resolver
41+
->setDefaults([
42+
'data_class' => DataClass::class,
43+
])
44+
;
45+
}
46+
47+
}
48+
49+
class FormFactoryAwareClass
50+
{
51+
52+
/** @var FormFactoryInterface */
53+
private $formFactory;
54+
55+
public function __construct(FormFactoryInterface $formFactory)
56+
{
57+
$this->formFactory = $formFactory;
58+
}
59+
60+
public function doSomething(): void
61+
{
62+
$form = $this->formFactory->create(DataClassType::class, new DataClass());
63+
assertType('GenericFormDataType\DataClass', $form->getData());
64+
}
65+
66+
public function doSomethingNullable(): void
67+
{
68+
$form = $this->formFactory->create(DataClassType::class);
69+
assertType('GenericFormDataType\DataClass|null', $form->getData());
70+
}
71+
72+
}

0 commit comments

Comments
 (0)