Skip to content
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

Introduce Form Template to improve inference for processed payloads #248

Merged
merged 10 commits into from
Nov 28, 2023
89 changes: 13 additions & 76 deletions psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@
<code>$this</code>
<code>$this</code>
<code>$this</code>
<code>$this</code>
<code>$this</code>
<code>$this</code>
<code>$this</code>
<code>$this</code>
<code>$this</code>
<code>$this</code>
<code>$this</code>
</LessSpecificReturnStatement>
</file>
<file src="src/Element/Captcha.php">
Expand Down Expand Up @@ -158,6 +166,8 @@
<code>$this</code>
<code>$this</code>
<code>$this</code>
<code>$this</code>
<code>$this</code>
</LessSpecificReturnStatement>
<MoreSpecificImplementedParamType>
<code>$object</code>
Expand Down Expand Up @@ -187,6 +197,9 @@
<code>$this</code>
<code>$this</code>
<code>$this</code>
<code>$this</code>
<code>$this</code>
<code>$this</code>
</LessSpecificReturnStatement>
<NoValue>
<code>$cKey</code>
Expand Down Expand Up @@ -900,11 +913,6 @@
])]]></code>
</DeprecatedClass>
</file>
<file src="test/TestAsset/AddressFieldset.php">
<LessSpecificImplementedReturnType>
<code>array</code>
</LessSpecificImplementedReturnType>
</file>
<file src="test/TestAsset/Annotation/ComplexEntity.php">
<DeprecatedClass>
<code>Annotation\AllowEmpty()</code>
Expand All @@ -921,26 +929,6 @@
<code>Annotation\ContinueIfEmpty(true)</code>
</DeprecatedClass>
</file>
<file src="test/TestAsset/BasicFieldset.php">
<LessSpecificImplementedReturnType>
<code>array</code>
</LessSpecificImplementedReturnType>
</file>
<file src="test/TestAsset/CategoryFieldset.php">
<LessSpecificImplementedReturnType>
<code>array</code>
</LessSpecificImplementedReturnType>
</file>
<file src="test/TestAsset/CityFieldset.php">
<LessSpecificImplementedReturnType>
<code>array</code>
</LessSpecificImplementedReturnType>
</file>
<file src="test/TestAsset/CountryFieldset.php">
<LessSpecificImplementedReturnType>
<code>array</code>
</LessSpecificImplementedReturnType>
</file>
<file src="test/TestAsset/CustomCollection.php">
<PropertyNotSetInConstructor>
<code>CustomCollection</code>
Expand All @@ -958,16 +946,6 @@
<code><![CDATA[$this->view]]></code>
</PossiblyNullArgument>
</file>
<file src="test/TestAsset/ElementWithFilter.php">
<LessSpecificImplementedReturnType>
<code>array</code>
</LessSpecificImplementedReturnType>
</file>
<file src="test/TestAsset/ElementWithStringToArrayFilter.php">
<LessSpecificImplementedReturnType>
<code>array</code>
</LessSpecificImplementedReturnType>
</file>
<file src="test/TestAsset/FieldsetWithDependency.php">
<MissingParamType>
<code>$name</code>
Expand All @@ -977,15 +955,7 @@
<code>$dependency</code>
</PropertyNotSetInConstructor>
</file>
<file src="test/TestAsset/FieldsetWithInputFilter.php">
<LessSpecificImplementedReturnType>
<code>array[]</code>
</LessSpecificImplementedReturnType>
</file>
<file src="test/TestAsset/FileInputFilterProviderFieldset.php">
<LessSpecificImplementedReturnType>
<code>array[]</code>
</LessSpecificImplementedReturnType>
<MissingParamType>
<code>$name</code>
<code>$options</code>
Expand All @@ -998,56 +968,23 @@
</MissingParamType>
</file>
<file src="test/TestAsset/InputFilterProvider.php">
<LessSpecificImplementedReturnType>
<code>array[]</code>
</LessSpecificImplementedReturnType>
<MissingParamType>
<code>$name</code>
<code>$options</code>
</MissingParamType>
</file>
<file src="test/TestAsset/InputFilterProviderFieldset.php">
<LessSpecificImplementedReturnType>
<code>array[]</code>
</LessSpecificImplementedReturnType>
<MissingParamType>
<code>$name</code>
<code>$options</code>
</MissingParamType>
</file>
<file src="test/TestAsset/InputFilterProviderWithFieldset.php">
<LessSpecificImplementedReturnType>
<code>array[]</code>
</LessSpecificImplementedReturnType>
<MissingParamType>
<code>$name</code>
<code>$options</code>
</MissingParamType>
</file>
<file src="test/TestAsset/MyFieldset.php">
<LessSpecificImplementedReturnType>
<code>array[]</code>
</LessSpecificImplementedReturnType>
</file>
<file src="test/TestAsset/NestedFieldset.php">
<LessSpecificImplementedReturnType>
<code>array</code>
</LessSpecificImplementedReturnType>
</file>
<file src="test/TestAsset/OrphansFieldset.php">
<LessSpecificImplementedReturnType>
<code>array[]</code>
</LessSpecificImplementedReturnType>
<MissingParamType>
<code>$name</code>
<code>$options</code>
</MissingParamType>
</file>
<file src="test/TestAsset/ProductFieldset.php">
<LessSpecificImplementedReturnType>
<code>array</code>
</LessSpecificImplementedReturnType>
</file>
<file src="test/TestAsset/ValueStoringFieldset.php">
<PropertyNotSetInConstructor>
<code>$storedValue</code>
Expand Down
9 changes: 8 additions & 1 deletion src/Form.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
use function is_object;
use function sprintf;

/**
* @template TFilteredValues
* @implements FormInterface<TFilteredValues>
*/
class Form extends Fieldset implements FormInterface
{
/** @var array<string, scalar|null> */
Expand Down Expand Up @@ -487,7 +491,7 @@ public function isValid(): bool
* By default, retrieves normalized values; pass one of the
* FormInterface::VALUES_* constants to shape the behavior.
*
* @return array|object
* @inheritDoc
* @throws Exception\DomainException
*/
public function getData(int $flag = FormInterface::VALUES_NORMALIZED)
Expand Down Expand Up @@ -582,6 +586,7 @@ protected function prepareValidationGroup(Fieldset $formOrFieldset, array $data,
/**
* Set the input filter used by this form
*
* @param InputFilterInterface<TFilteredValues> $inputFilter
* @return $this
*/
public function setInputFilter(InputFilterInterface $inputFilter)
Expand All @@ -599,6 +604,8 @@ public function setInputFilter(InputFilterInterface $inputFilter)

/**
* Retrieve input filter used by this form
*
* @return InputFilterInterface<TFilteredValues>
*/
public function getInputFilter(): InputFilterInterface
{
Expand Down
14 changes: 13 additions & 1 deletion src/FormInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

use Laminas\InputFilter\InputFilterInterface;

/**
* @template TFilteredValues
*/
interface FormInterface extends FieldsetInterface
{
public const BIND_ON_VALIDATE = 0x00;
Expand Down Expand Up @@ -42,12 +45,15 @@ public function setBindOnValidate(int $bindOnValidateFlag);
/**
* Set input filter
*
* @param InputFilterInterface<TFilteredValues> $inputFilter
* @return $this
*/
public function setInputFilter(InputFilterInterface $inputFilter);

/**
* Retrieve input filter
*
* @return InputFilterInterface<TFilteredValues>
*/
public function getInputFilter(): InputFilterInterface;

Expand All @@ -64,7 +70,13 @@ public function isValid(): bool;
* By default, retrieves normalized values; pass one of the VALUES_*
* constants to shape the behavior.
*
* @return array|object
* @param self::VALUES_* $flag
* @return TFilteredValues|object|array<string, mixed>
* @psalm-return (
* $flag is self::VALUES_NORMALIZED
* ? TFilteredValues|object
* : ($flag is self::VALUES_RAW ? array<string, mixed> : TFilteredValues)
* )
*/
public function getData(int $flag = FormInterface::VALUES_NORMALIZED);

Expand Down
20 changes: 20 additions & 0 deletions test/FieldsetTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -641,4 +641,24 @@ public function testSetHydratorByNameMethodShouldSetValidHydratorForForm(): void
// Test
self::assertSame($this->hydrator, $this->fieldset->getHydrator());
}

public function testTheValueOfAFieldsetIsNullWhenComposedInAForm(): void
{
$fieldset = new Fieldset('set');
$fieldset->add(new Element\Text('text'));
$fieldset->setUseAsBaseFieldset(true);

$form = new Form();
$form->add($fieldset);
$payload = [
'set' => [
'text' => 'Test Value',
],
];
$form->setData($payload);

self::assertTrue($form->isValid());
self::assertNull($fieldset->getValue());
self::assertSame($payload, $form->getData());
}
}
1 change: 1 addition & 0 deletions test/Integration/TestAsset/Form.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Laminas\Form\Form as BaseForm;
use Laminas\Form\FormElementManager;

/** @extends BaseForm<array<string, mixed>> */
final class Form extends BaseForm
{
/** @var null|FormElementManager */
Expand Down
28 changes: 28 additions & 0 deletions test/StaticAnalysis/Asset/ExampleForm.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace LaminasTest\Form\StaticAnalysis\Asset;

use Laminas\Form\Element\Number;
use Laminas\Form\Element\Text;
use Laminas\Form\Form;

/**
* @psalm-import-type ValidPayload from ExampleInputFilter
* @extends Form<ValidPayload>
*/
final class ExampleForm extends Form
{
public function init(): void
{
$this->add([
'name' => 'string',
'type' => Text::class,
]);
$this->add([
'name' => 'number',
'type' => Number::class,
]);
}
}
34 changes: 34 additions & 0 deletions test/StaticAnalysis/Asset/ExampleInputFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace LaminasTest\Form\StaticAnalysis\Asset;

use Laminas\Filter\ToInt;
use Laminas\InputFilter\InputFilter;

/**
* @psalm-type ValidPayload = array{
* string: non-empty-string,
* number: int,
* }
* @extends InputFilter<ValidPayload>
*/
final class ExampleInputFilter extends InputFilter
{
public function init(): void
{
$this->add([
'name' => 'string',
'required' => true,
]);

$this->add([
'name' => 'number',
'required' => true,
'filters' => [
['name' => ToInt::class],
],
]);
}
}
57 changes: 57 additions & 0 deletions test/StaticAnalysis/FormTemplates.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

declare(strict_types=1);

namespace LaminasTest\Form\StaticAnalysis;

use Laminas\Form\FormInterface;
use LaminasTest\Form\StaticAnalysis\Asset\ExampleForm;

use function assert;
use function is_array;

final class FormTemplates
{
/** @return non-empty-string */
public function getSingleValueFromComposedInputFilter(): string
{
$form = new ExampleForm();

return $form->getInputFilter()->getValues()['string'];
}

/** @return non-empty-string */
public function getDataWithExplicitArray(): string
{
$form = new ExampleForm();
$data = $form->getData(FormInterface::VALUES_AS_ARRAY);

return $data['string'];
}

/** @return non-empty-string */
public function getDataWithValuesNormalized(): string
{
$form = new ExampleForm();
$data = $form->getData(FormInterface::VALUES_NORMALIZED);
assert(is_array($data));

return $data['string'];
}

/** @return non-empty-string */
public function testThatFluidReturnTypesPreserveTemplatesForSetPreferFormInputFilter(): string
{
$form = new ExampleForm();
return $form->setPreferFormInputFilter(true)
->getData(FormInterface::VALUES_AS_ARRAY)['string'];
}

/** @return non-empty-string */
public function testThatFluidReturnTypesPreserveTemplatesForSetWrapElements(): string
{
$form = new ExampleForm();
return $form->setWrapElements(true)
->getData(FormInterface::VALUES_AS_ARRAY)['string'];
}
}
Loading