Skip to content

Commit

Permalink
Add stub for form options
Browse files Browse the repository at this point in the history
  • Loading branch information
VincentLanglet committed Jan 4, 2025
1 parent 1ef4dce commit d5960d8
Show file tree
Hide file tree
Showing 9 changed files with 166 additions and 19 deletions.
5 changes: 5 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ use Symfony\Contracts\Service\ServiceSubscriberInterface;
abstract class AbstractController implements ServiceSubscriberInterface
{
/**
* @template TFormType of FormTypeInterface<TData>
* @template TFormType of FormTypeInterface<TData, TOptions>
* @template TData
* @template TOptions of array<string, mixed>
*
* @param class-string<TFormType> $type
* @param TData $data
* @param array<string, mixed> $options
* @param TOptions $options
*
* @phpstan-return ($data is null ? FormInterface<null|TData> : FormInterface<TData>)
*/
Expand Down
9 changes: 5 additions & 4 deletions stubs/Symfony/Component/Form/AbstractType.stub
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,28 @@ namespace Symfony\Component\Form;

/**
* @template TData
* @template TOptions of array<string, mixed>
*
* @implements FormTypeInterface<TData>
* @implements FormTypeInterface<TData, TOptions>
*/
abstract class AbstractType implements FormTypeInterface
{

/**
* @param FormBuilderInterface<TData|null> $builder
* @param array<string, mixed> $options
* @param TOptions $options
*/
public function buildForm(FormBuilderInterface $builder, array $options): void;

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

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

Expand Down
12 changes: 7 additions & 5 deletions stubs/Symfony/Component/Form/FormFactoryInterface.stub
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ use Symfony\Component\Form\Extension\Core\Type\FormType;
interface FormFactoryInterface
{
/**
* @template TFormType of FormTypeInterface<TData>
* @template TFormType of FormTypeInterface<TData, TOptions>
* @template TData
* @template TOptions of array<string, mixed>
*
* @param class-string<TFormType> $type
* @param TData $data
* @param array<string, mixed> $options
* @param TOptions $options
*
* @phpstan-return ($data is null ? FormInterface<null|TData> : FormInterface<TData>)
*
Expand All @@ -21,12 +22,13 @@ interface FormFactoryInterface
public function create(string $type = FormType::class, $data = null, array $options = []): FormInterface;

/**
* @template TFormType of FormTypeInterface<TData>
* @template TFormType of FormTypeInterface<TData, TOptions>
* @template TData
* @template TOptions of array<string, mixed>
*
* @param class-string<TFormType> $type
* @param class-string<TFormType> $type
* @param TData $data
* @param array<string, mixed> $options
* @param TOptions $options
*
* @phpstan-return ($data is null ? FormInterface<null|TData> : FormInterface<TData>)
*
Expand Down
11 changes: 6 additions & 5 deletions stubs/Symfony/Component/Form/FormTypeExtensionInterface.stub
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,25 @@ namespace Symfony\Component\Form;

/**
* @template TData
* @template TOptions of array<string, mixed>
*/
interface FormTypeExtensionInterface
{
/**
* @param FormBuilderInterface<TData|null> $builder
* @param array<string, mixed> $options
* @param TOptions $options
*/
public function buildForm(FormBuilderInterface $builder, array $options): void;

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

/**
* @phpstan-param FormInterface<TData> $form
* @param array<string, mixed> $options
* @param FormInterface<TData> $form
* @param TOptions $options
*/
public function finishView(FormView $view, FormInterface $form, array $options): void;
}
7 changes: 4 additions & 3 deletions stubs/Symfony/Component/Form/FormTypeInterface.stub
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,25 @@ namespace Symfony\Component\Form;

/**
* @template TData
* @template TOptions of array<string, mixed>
*/
interface FormTypeInterface
{
/**
* @param FormBuilderInterface<TData|null> $builder
* @param array<string, mixed> $options
* @param TOptions $options
*/
public function buildForm(FormBuilderInterface $builder, array $options): void;

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

/**
* @param FormInterface<TData> $form
* @param array<string, mixed> $options
* @param TOptions $options
*/
public function finishView(FormView $view, FormInterface $form, array $options): void;
}
33 changes: 33 additions & 0 deletions tests/Type/Symfony/CallMethodsRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Symfony;

use PHPStan\Rules\Methods\CallMethodsRule;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;

/**
* @extends RuleTestCase<CallMethodsRule>
*/
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',
];
}

}
1 change: 1 addition & 0 deletions tests/Type/Symfony/ExtensionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
102 changes: 102 additions & 0 deletions tests/Type/Symfony/data/form_options.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php declare(strict_types = 1);

namespace GenericFormOptionsType;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
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
{
}

/**
* @extends AbstractType<DataClass, array{required: string, optional: int}>
*/
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<GenericFormOptionsType\DataClass>', $form);
}

public function doSomethingWithOption(): void
{
$form = $this->formFactory->create(DataClassType::class, new DataClass(), ['required' => 'foo']);
assertType('Symfony\Component\Form\FormInterface<GenericFormOptionsType\DataClass>', $form);
}

public function doSomethingWithInvalidOption(): void
{
$form = $this->formFactory->create(DataClassType::class, new DataClass(), ['required' => 42]);
assertType('Symfony\Component\Form\FormInterface<GenericFormOptionsType\DataClass>', $form);
}

}

class FormController extends AbstractController
{

public function doSomething(): void
{
$form = $this->createForm(DataClassType::class, new DataClass());
assertType('Symfony\Component\Form\FormInterface<GenericFormOptionsType\DataClass>', $form);
}

public function doSomethingWithOption(): void
{
$form = $this->createForm(DataClassType::class, new DataClass(), ['required' => 'foo']);
assertType('Symfony\Component\Form\FormInterface<GenericFormOptionsType\DataClass>', $form);
}

public function doSomethingWithInvalidOption(): void
{
$form = $this->createForm(DataClassType::class, new DataClass(), ['required' => 42]);
assertType('Symfony\Component\Form\FormInterface<GenericFormOptionsType\DataClass>', $form);
}

}

0 comments on commit d5960d8

Please sign in to comment.