Skip to content

Commit

Permalink
Support specifying type of data_class in forms
Browse files Browse the repository at this point in the history
  • Loading branch information
siketyan committed Apr 14, 2023
1 parent 1da7bf4 commit f45b9ce
Show file tree
Hide file tree
Showing 9 changed files with 159 additions and 2 deletions.
7 changes: 7 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ parameters:
consoleApplicationLoader: null
featureToggles:
skipCheckGenericClasses:
- Symfony\Component\Form\AbstractType
- Symfony\Component\Form\FormInterface
- Symfony\Component\Form\FormTypeExtensionInterface
- Symfony\Component\Form\FormTypeInterface
- Symfony\Component\OptionsResolver\Options
- Symfony\Component\Security\Core\Authorization\Voter\Voter
- Symfony\Component\Security\Core\User\PasswordUpgraderInterface
Expand All @@ -36,13 +40,15 @@ parameters:
- stubs/Symfony/Component/EventDispatcher/EventDispatcherInterface.stub
- stubs/Symfony/Component/EventDispatcher/EventSubscriberInterface.stub
- stubs/Symfony/Component/EventDispatcher/GenericEvent.stub
- stubs/Symfony/Component/Form/AbstractType.stub
- stubs/Symfony/Component/Form/ChoiceList/Loader/ChoiceLoaderInterface.stub
- stubs/Symfony/Component/Form/Exception/ExceptionInterface.stub
- stubs/Symfony/Component/Form/Exception/RuntimeException.stub
- stubs/Symfony/Component/Form/Exception/TransformationFailedException.stub
- stubs/Symfony/Component/Form/DataTransformerInterface.stub
- stubs/Symfony/Component/Form/FormBuilderInterface.stub
- stubs/Symfony/Component/Form/FormInterface.stub
- stubs/Symfony/Component/Form/FormFactoryInterface.stub
- stubs/Symfony/Component/Form/FormTypeExtensionInterface.stub
- stubs/Symfony/Component/Form/FormTypeInterface.stub
- stubs/Symfony/Component/Form/FormView.stub
Expand All @@ -52,6 +58,7 @@ parameters:
- stubs/Symfony/Component/HttpFoundation/Session.stub
- stubs/Symfony/Component/Messenger/StampInterface.stub
- stubs/Symfony/Component/Messenger/Envelope.stub
- stubs/Symfony/Component/OptionsResolver/Exception/InvalidOptionsException.stub
- stubs/Symfony/Component/OptionsResolver/Options.stub
- stubs/Symfony/Component/Process/Process.stub
- stubs/Symfony/Component/PropertyAccess/PropertyPathInterface.stub
Expand Down
12 changes: 12 additions & 0 deletions stubs/Symfony/Component/Form/AbstractType.stub
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace Symfony\Component\Form;

/**
* @template TData
*
* @implements FormTypeInterface<TData>
*/
abstract class AbstractType implements FormTypeInterface
{
}
36 changes: 36 additions & 0 deletions stubs/Symfony/Component/Form/FormFactoryInterface.stub
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace Symfony\Component\Form;

use Symfony\Component\Form\Extension\Core\Type\FormType;

interface FormFactoryInterface
{
/**
* @template TFormType of FormTypeInterface<TData>
* @template TData
*
* @param class-string<TFormType> $type
* @param TData $data
* @param array<string, mixed> $options
*
* @phpstan-return ($data is null ? FormInterface<null|TData> : FormInterface<TData>)
*
* @throws \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
*/
public function create(string $type = FormType::class, $data = null, array $options = []): FormInterface;

/**
* @template TFormType of FormTypeInterface<TData>
* @template TData
*
* @param class-string<TFormType> $type
* @param TData $data
* @param array<string, mixed> $options
*
* @phpstan-return ($data is null ? FormInterface<null|TData> : FormInterface<TData>)
*
* @throws \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
*/
public function createNamed(string $name, string $type = FormType::class, $data = null, array $options = []): FormInterface;
}
16 changes: 14 additions & 2 deletions stubs/Symfony/Component/Form/FormInterface.stub
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,22 @@
namespace Symfony\Component\Form;

/**
* @extends \ArrayAccess<string, \Symfony\Component\Form\FormInterface>
* @extends \Traversable<string, \Symfony\Component\Form\FormInterface>
* @template TData
*
* @extends \ArrayAccess<string, \Symfony\Component\Form\FormInterface<mixed>>
* @extends \Traversable<string, \Symfony\Component\Form\FormInterface<mixed>>
*/
interface FormInterface extends \ArrayAccess, \Traversable, \Countable
{
/**
* @param TData $modelData
*
* @return $this
*/
public function setData($modelData): FormInterface;

/**
* @return TData
*/
public function getData();
}
5 changes: 5 additions & 0 deletions stubs/Symfony/Component/Form/FormTypeExtensionInterface.stub
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

namespace Symfony\Component\Form;

/**
* @template TData
*/
interface FormTypeExtensionInterface
{
/**
Expand All @@ -10,11 +13,13 @@ interface FormTypeExtensionInterface
public function buildForm(FormBuilderInterface $builder, array $options): void;

/**
* @phpstan-param FormInterface<TData> $form
* @param array<string, mixed> $options
*/
public function buildView(FormView $view, FormInterface $form, array $options): void;

/**
* @phpstan-param FormInterface<TData> $form
* @param array<string, mixed> $options
*/
public function finishView(FormView $view, FormInterface $form, array $options): void;
Expand Down
5 changes: 5 additions & 0 deletions stubs/Symfony/Component/Form/FormTypeInterface.stub
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

namespace Symfony\Component\Form;

/**
* @template TData
*/
interface FormTypeInterface
{
/**
Expand All @@ -10,11 +13,13 @@ interface FormTypeInterface
public function buildForm(FormBuilderInterface $builder, array $options): void;

/**
* @phpstan-param FormInterface<TData> $form
* @param array<string, mixed> $options
*/
public function buildView(FormView $view, FormInterface $form, array $options): void;

/**
* @phpstan-param FormInterface<TData> $form
* @param array<string, mixed> $options
*/
public function finishView(FormView $view, FormInterface $form, array $options): void;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace Symfony\Component\OptionsResolver\Exception;

class InvalidOptionsException extends \InvalidArgumentException
{
}
1 change: 1 addition & 0 deletions tests/Type/Symfony/ExtensionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,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');
}

/**
Expand Down
72 changes: 72 additions & 0 deletions tests/Type/Symfony/data/form_data_type.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php declare(strict_types = 1);

namespace GenericFormDataType;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use function PHPStan\Testing\assertType;

class DataClass
{

/** @var int */
public $foo;

/** @var string */
public $bar;

}

/**
* @extends AbstractType<DataClass>
*/
class DataClassType extends AbstractType
{

public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('foo', NumberType::class)
->add('bar', TextType::class)
;
}

public function configureOptions(OptionsResolver $resolver): void
{
$resolver
->setDefaults([
'data_class' => DataClass::class,
])
;
}

}

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('GenericFormDataType\DataClass', $form->getData());
}

public function doSomethingNullable(): void
{
$form = $this->formFactory->create(DataClassType::class);
assertType('GenericFormDataType\DataClass|null', $form->getData());
}

}

0 comments on commit f45b9ce

Please sign in to comment.