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

Filter data nested forms #68

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
14 changes: 14 additions & 0 deletions docs/guide/en/displaying-fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ use Yiisoft\FormModel\FormModel;

/** @var FormModel $formModel */
$field = Field::text($formModel, 'login');

/** Display fields nested forms */
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/** Display fields nested forms */
/** Display nested form */

Does that display whole form?

Copy link
Author

Choose a reason for hiding this comment

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

Whole?

nested form field or nested form 🤷‍♂️

$nestedField = Field::text($formModel->nestedForm, 'text');
/** or dot-notation */
$nestedField = Field::text($formModel, 'nestedForm.text');
/** or array notation */
$nestedField = Field::text($formModel, 'nestedForm[text]');
```

or factory (`\Yiisoft\FormModel\FieldFactory`):
Expand All @@ -20,6 +27,13 @@ use Yiisoft\FormModel\FormModel;
/** @var FormModel $formModel */
$factory = new FieldFactory();
$factory->text($formModel, 'login');

/** Display nested form field */
$nestedField = $factory->text($formModel->nestedForm, 'text');
/** or dot-notation */
$nestedField = $factory->text($formModel, 'nestedForm.text');
/** or array notation */
$nestedField = $factory->text($formModel, 'nestedForm[text]');
```

If you want to customize other properties, such as label, hint, etc., use dedicated methods:
Expand Down
34 changes: 33 additions & 1 deletion src/FormHydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@
if (!isset($data[$scope]) || !is_array($data[$scope])) {
return false;
}
$hydrateData = $data[$scope];

$filteredData = $this->filterDataNestedForms($model,$data);
$hydrateData = array_merge_recursive($data[$model->getFormName()], $filteredData);

Check failure on line 73 in src/FormHydrator.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.1-ubuntu-latest

MixedArgument

src/FormHydrator.php:73:50: MixedArgument: Argument 1 of array_merge_recursive cannot be mixed, expecting array<array-key, mixed> (see https://psalm.dev/030)

Check failure on line 73 in src/FormHydrator.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.2-ubuntu-latest

MixedArgument

src/FormHydrator.php:73:50: MixedArgument: Argument 1 of array_merge_recursive cannot be mixed, expecting array<array-key, mixed> (see https://psalm.dev/030)

Check failure on line 73 in src/FormHydrator.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.3-ubuntu-latest

MixedArgument

src/FormHydrator.php:73:50: MixedArgument: Argument 1 of array_merge_recursive cannot be mixed, expecting array<array-key, mixed> (see https://psalm.dev/030)
}

$this->hydrator->hydrate(
Expand Down Expand Up @@ -251,4 +253,34 @@
}
return $result;
}

private function filterDataNestedForms(FormModelInterface $formModel, array &$data): array
{
$reflection = new \ReflectionClass($formModel);
$properties = $reflection->getProperties(\ReflectionProperty::IS_PUBLIC);

$filteredData = [];
foreach ($properties as $property) {
if ($property->isStatic()) {
continue;

Check warning on line 265 in src/FormHydrator.php

View check run for this annotation

Codecov / codecov/patch

src/FormHydrator.php#L265

Added line #L265 was not covered by tests
DAGpro marked this conversation as resolved.
Show resolved Hide resolved
}

if ($property->isReadOnly() && $property->isInitialized($formModel)) {
continue;

Check warning on line 269 in src/FormHydrator.php

View check run for this annotation

Codecov / codecov/patch

src/FormHydrator.php#L269

Added line #L269 was not covered by tests
}

$propertyValue = $property->getValue($formModel);
if ($propertyValue instanceof FormModel) {
$dataNestedForms = $this->filterDataNestedForms($property->getValue($formModel), $data);

Check failure on line 274 in src/FormHydrator.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.1-ubuntu-latest

MixedArgument

src/FormHydrator.php:274:65: MixedArgument: Argument 1 of Yiisoft\FormModel\FormHydrator::filterDataNestedForms cannot be mixed, expecting Yiisoft\FormModel\FormModelInterface (see https://psalm.dev/030)

Check failure on line 274 in src/FormHydrator.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.2-ubuntu-latest

MixedArgument

src/FormHydrator.php:274:65: MixedArgument: Argument 1 of Yiisoft\FormModel\FormHydrator::filterDataNestedForms cannot be mixed, expecting Yiisoft\FormModel\FormModelInterface (see https://psalm.dev/030)

Check failure on line 274 in src/FormHydrator.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.3-ubuntu-latest

MixedArgument

src/FormHydrator.php:274:65: MixedArgument: Argument 1 of Yiisoft\FormModel\FormHydrator::filterDataNestedForms cannot be mixed, expecting Yiisoft\FormModel\FormModelInterface (see https://psalm.dev/030)
DAGpro marked this conversation as resolved.
Show resolved Hide resolved
if (isset($data[$propertyValue->getFormName()])) {
$filteredData[$property->getName()] = array_merge($data[$propertyValue->getFormName()], $dataNestedForms);

Check failure on line 276 in src/FormHydrator.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.1-ubuntu-latest

MixedArgument

src/FormHydrator.php:276:71: MixedArgument: Argument 1 of array_merge cannot be mixed, expecting array<array-key, mixed> (see https://psalm.dev/030)

Check failure on line 276 in src/FormHydrator.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.2-ubuntu-latest

MixedArgument

src/FormHydrator.php:276:71: MixedArgument: Argument 1 of array_merge cannot be mixed, expecting array<array-key, mixed> (see https://psalm.dev/030)

Check failure on line 276 in src/FormHydrator.php

View workflow job for this annotation

GitHub Actions / psalm / PHP 8.3-ubuntu-latest

MixedArgument

src/FormHydrator.php:276:71: MixedArgument: Argument 1 of array_merge cannot be mixed, expecting array<array-key, mixed> (see https://psalm.dev/030)
unset($data[$propertyValue->getFormName()]);
} else if (!empty($dataNestedForms)) {
$filteredData[$property->getName()] = $dataNestedForms;
}
}
}

return $filteredData;
}
}
90 changes: 90 additions & 0 deletions tests/FormHydratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Psr\Http\Message\ServerRequestInterface;
use Yiisoft\FormModel\FormModel;
use Yiisoft\FormModel\Tests\Support\Form\CarForm;
use Yiisoft\FormModel\Tests\Support\Form\PopulateNestedForm\MainForm;
use Yiisoft\FormModel\Tests\Support\TestHelper;
use Yiisoft\Validator\Result;
use Yiisoft\Validator\Rule\Integer;
Expand Down Expand Up @@ -146,6 +147,95 @@ public function testPopulateFromPostAndValidate(bool $expected, ServerRequestInt
$this->assertSame($expected, $result);
}

public static function dataNestedPopulate(): array
{
$factory = new ServerRequestFactory();
$expected = [
'value' => 'test',
'firstForm' => 'firstTest',
'secondForm' => 3,
'secondForm.string' => 'string'
];
return [
'nested-array-data' => [
$expected,
$factory->createServerRequest('POST', '/')->withParsedBody([
'MainForm' => [
'value' => $expected['value'],
'firstForm' => [
'value' => $expected['firstForm'],
'secondForm' => [
'value' => $expected['secondForm'],
'string' => $expected['secondForm.string']
]
]
]
]),
],
'dot-notation-data' => [
$expected,
$factory->createServerRequest('POST', '/')->withParsedBody([
'MainForm' => [
'value' => $expected['value'],
'firstForm.value' => $expected['firstForm'],
'firstForm.secondForm.value' => $expected['secondForm'],
'firstForm.secondForm.string' => $expected['secondForm.string']
]
]),
],
'one-level-array-data' => [
$expected,
$factory->createServerRequest('POST', '/')->withParsedBody([
'MainForm' => ['value' => $expected['value']],
'FirstNestedForm' => ['value' => $expected['firstForm']],
'SecondNestedForm' => ['value' => $expected['secondForm'], 'string' => $expected['secondForm.string']],
]),
],
'mixed-one-level-and-dot-notation-data' => [
$expected,
$factory->createServerRequest('POST', '/')->withParsedBody([
'MainForm' => [
'value' => $expected['value'],
'firstForm.secondForm.string' => $expected['secondForm.string'],
],
'FirstNestedForm' => [
'value' => $expected['firstForm'],
'secondForm.value' => $expected['secondForm'],
]
])
],
'mixed-one-level-and-nested-array-data' => [
$expected,
$factory->createServerRequest('POST', '/')->withParsedBody([
'MainForm' => [
'value' => $expected['value'],
'firstForm' => [
'value' => $expected['firstForm'],
'secondForm' => [
'string' => $expected['secondForm.string'],
]
],
],
'SecondNestedForm' => [
'value' => $expected['secondForm'],
],
])
],
];
}

#[DataProvider('dataNestedPopulate')]
public function testPopulateNestedFormFromPost(array $expected, ServerRequestInterface $request): void
{
$form = new MainForm();

TestHelper::createFormHydrator()->populateFromPost($form, $request);
$this->assertSame($expected['value'], $form->value);
$this->assertSame($expected['firstForm'], $form->firstForm->value);
$this->assertSame($expected['secondForm'], $form->firstForm->secondForm->value);
$this->assertSame($expected['secondForm.string'], $form->firstForm->secondForm->string);
}

public function testPopulateFormWithRulesFromAttributesAndMethod(): void
{
$form = new class () extends FormModel implements RulesProviderInterface {
Expand Down
29 changes: 29 additions & 0 deletions tests/Support/Form/PopulateNestedForm/FirstNestedForm.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);


namespace Yiisoft\FormModel\Tests\Support\Form\PopulateNestedForm;


use Yiisoft\FormModel\FormModel;
use Yiisoft\Validator\Rule\Length;
use Yiisoft\Validator\Rule\Nested;
use Yiisoft\Validator\Rule\Required;
use Yiisoft\Validator\Rule\StringValue;

class FirstNestedForm extends FormModel {

#[Required]
#[StringValue]
#[Length(min: 3)]
public string $value = '';

#[Required]
#[Nested(SecondNestedForm::class)]
public SecondNestedForm $secondForm;

public function __construct() {
$this->secondForm = new SecondNestedForm();
}
}
27 changes: 27 additions & 0 deletions tests/Support/Form/PopulateNestedForm/MainForm.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace Yiisoft\FormModel\Tests\Support\Form\PopulateNestedForm;

use Yiisoft\FormModel\FormModel;
use Yiisoft\Validator\Rule\Length;
use Yiisoft\Validator\Rule\Nested;
use Yiisoft\Validator\Rule\Required;
use Yiisoft\Validator\Rule\StringValue;


class MainForm extends FormModel {

#[Nested(FirstNestedForm::class)]
public FirstNestedForm $firstForm;

#[Required]
#[StringValue]
#[Length(min: 3)]
public string $value = '';

public function __construct() {
$this->firstForm = new FirstNestedForm();
}
}
26 changes: 26 additions & 0 deletions tests/Support/Form/PopulateNestedForm/SecondNestedForm.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);


namespace Yiisoft\FormModel\Tests\Support\Form\PopulateNestedForm;

use Yiisoft\FormModel\FormModel;
use Yiisoft\Validator\Rule\Integer;
use Yiisoft\Validator\Rule\Length;
use Yiisoft\Validator\Rule\Required;
use Yiisoft\Validator\Rule\StringValue;

class SecondNestedForm extends FormModel {

#[Required]
#[Integer]
public int $value = 0;

#[Required]
#[StringValue]
#[Length(min: 4)]
public string $string = '';

public function __construct() {}
}
Loading