Skip to content

feat: Support specifying type of data_class in forms #337

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Apr 14, 2023

Conversation

siketyan
Copy link
Contributor

You can now specify what type of data_class the FormType have as the generic parameter of FormTypeInterface or AbstractType.

Example:

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

Also, you can validate mismatch between data_class in FormType and the initial data:

$form = $this->formFactory->create(DataClassType::class, new DataClass()); // OK
$form = $this->formFactory->create(DataClassType::class, new AnotherClass()); // ERROR!

Then you can get appropriate type of the data:

$data = $form->getData();
\PHPStan\dumpType($data): // Dumped type: DataClass

@siketyan siketyan force-pushed the feat/form-data-types branch from 7d257b4 to a18b756 Compare February 20, 2023 04:11
Copy link
Member

@ondrejmirtes ondrejmirtes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You've given a compelling list of arguments! Looking forward to merge this.

* @param class-string<TFormType> $type
* @param TData $data
* @param array<string, mixed> $options
*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong indentation

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 88da905

/**
* @extends AbstractType<DataClass>
*/
class DataClassType extends AbstractType
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what these tests are supposed to do, they're not referenced from anywhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's used from here: https://github.com/siketyan/phpstan-symfony/blob/a18b756621163b0040a3c3f5a58a2f24b3652f48/tests/Stubs/Symfony/Component/Form/FormFactoryAwareClass.php#L20.
Although it is for testing purpose to confirm the FormFactoryInterface accepts DataClass for DataClassType form type.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That’s not how testing works here 😊 Please see https://phpstan.org/developing-extensions/testing to know how to test it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about this? dcf9b4f

* @param TData $data
* @param array<string, mixed> $options
*
* @phpstan-return ($data is null ? FormInterface<null|TData> : FormInterface<TData>)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These complex generics should be tested with TypeInferenceTestCase. To make sure they behave like you expect them to.

namespace Symfony\Component\Form;

/**
* @template TData
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every time a new class is made generic, the class name should be added here:

featureToggles:
skipCheckGenericClasses:
- Symfony\Component\OptionsResolver\Options
- Symfony\Component\Security\Core\Authorization\Voter\Voter
- Symfony\Component\Security\Core\User\PasswordUpgraderInterface
(to make sure users are not bothered by "Function foo() has parameter $hw with generic class HelloWorld but does not specify its types: T" errors.

This list will be made empty with the next major version.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in da2cb98

*/
interface FormInterface extends \ArrayAccess, \Traversable, \Countable
{

/**
* @return TData
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. So it means psalm should improve his typing ^^

@ondrejmirtes ondrejmirtes merged commit f45b9ce into phpstan:1.2.x Apr 14, 2023
@ondrejmirtes
Copy link
Member

Thank you!

@blankse
Copy link
Contributor

blankse commented Apr 14, 2023

@siketyan @ondrejmirtes After update to 1.3.0 I get now following errors:

Parameter $form of method App\Form\Type\TypeaheadType::buildView() has invalid type Symfony\Component\Form\TData.

I haven't much experience with the generics. I am only on phpstan level 6 with disabled checkGenericClassInNonGenericObjectType and checkMissingIterableValueType.

The form types have no data class. So I tried to add following phpdocs to the classes, but it doesn't help:

/**
 * @extends AbstractType<null>
 */
/**
 * @extends AbstractType<mixed>
 */

Is there a bug on your side or what make I wrong?

@ondrejmirtes
Copy link
Member

Please show a piece of code for which PHPStan reports this error:

Parameter $form of method App\Form\Type\TypeaheadType::buildView() has invalid type Symfony\Component\Form\TData.

@blankse
Copy link
Contributor

blankse commented Apr 14, 2023

@ondrejmirtes

<?php

namespace App\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;

/**
 * @extends AbstractType<mixed>
 */
class TypeaheadType extends AbstractType
{
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'definitionType' => 'class',
            'definitionId' => null,
            'field' => null,
            'multiple' => false,
            'type' => '',
            'documentId' => null,
        ]);
    }

    public function buildView(FormView $view, FormInterface $form, array $options): void
    {
        if (!$options['definitionId'] || !$options['field']) {
            throw new \InvalidArgumentException('Typeahead: No definitionId or field is set');
        }

        $view->vars = array_replace_recursive($view->vars, [
            'definitionType' => $options['definitionType'],
            'definitionId' => $options['definitionId'],
            'field' => $options['field'],
            'multiple' => $options['multiple'],
            'documentId' => $options['documentId'],
            'attr' => [
                'class' => 'js-typeahead ' . $options['type'],
            ]
        ]);
    }

    public function getParent(): string
    {
        return TextType::class;
    }
}

@siketyan siketyan deleted the feat/form-data-types branch April 14, 2023 16:16
@ondrejmirtes
Copy link
Member

Fixed: 7e78605

There was a weird bug because all the methods are re-defined in AbstractType even if they're in the FormTypeInterface ancestor. So the stub file needs to repeat them too.

@siketyan
Copy link
Contributor Author

Thank you for the quick fix @ondrejmirtes and sorry for my mistake @blankse

@blankse
Copy link
Contributor

blankse commented Apr 14, 2023

@ondrejmirtes Thank you for the quick fix 👍🏼

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants