Skip to content

Commit

Permalink
Merge pull request #248 from gsteel/payload-shapes
Browse files Browse the repository at this point in the history
Introduce Form Template to improve inference for processed payloads
  • Loading branch information
Slamdunk authored Nov 28, 2023
2 parents 2ec86da + e9cad9b commit bc4828e
Show file tree
Hide file tree
Showing 34 changed files with 224 additions and 234 deletions.
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

0 comments on commit bc4828e

Please sign in to comment.