Skip to content

Commit adb9016

Browse files
filiplikavcannicolas-grekas
authored andcommitted
[Form] Missing Data Handling (checkbox)
1 parent 9f5238d commit adb9016

File tree

4 files changed

+192
-10
lines changed

4 files changed

+192
-10
lines changed

src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Component\Form\Exception\UnexpectedTypeException;
1515
use Symfony\Component\Form\FormError;
1616
use Symfony\Component\Form\FormInterface;
17+
use Symfony\Component\Form\MissingDataHandler;
1718
use Symfony\Component\Form\RequestHandlerInterface;
1819
use Symfony\Component\Form\Util\ServerParams;
1920
use Symfony\Component\HttpFoundation\File\File;
@@ -29,10 +30,12 @@
2930
class HttpFoundationRequestHandler implements RequestHandlerInterface
3031
{
3132
private $serverParams;
33+
private MissingDataHandler $missingDataHandler;
3234

3335
public function __construct(ServerParams $serverParams = null)
3436
{
3537
$this->serverParams = $serverParams ?? new ServerParams();
38+
$this->missingDataHandler = new MissingDataHandler();
3639
}
3740

3841
/**
@@ -57,13 +60,13 @@ public function handleRequest(FormInterface $form, $request = null)
5760
if ('' === $name) {
5861
$data = $request->query->all();
5962
} else {
60-
// Don't submit GET requests if the form's name does not exist
61-
// in the request
62-
if (!$request->query->has($name)) {
63+
$missingData = $this->missingDataHandler->missingData;
64+
65+
if ($missingData === $data = $request->query->get($name) ?? $missingData) {
66+
// Don't submit GET requests if the form's name does not exist
67+
// in the request
6368
return;
6469
}
65-
66-
$data = $request->query->get($name);
6770
}
6871
} else {
6972
// Mark the form with an error if the uploaded size was too large
@@ -90,6 +93,15 @@ public function handleRequest(FormInterface $form, $request = null)
9093
$params = $request->request->get($name, $default);
9194
$files = $request->files->get($name, $default);
9295
} else {
96+
$params = $this->missingDataHandler->missingData;
97+
$files = null;
98+
}
99+
100+
if ('PATCH' !== $method) {
101+
$params = $this->missingDataHandler->handle($form, $params);
102+
}
103+
104+
if ($this->missingDataHandler->missingData === $params) {
93105
// Don't submit the form if it is not present in the request
94106
return;
95107
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form;
13+
14+
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
15+
use Symfony\Component\Form\FormInterface;
16+
use Symfony\Component\Form\ResolvedFormTypeInterface;
17+
18+
class MissingDataHandler
19+
{
20+
public readonly \stdClass $missingData;
21+
22+
public function __construct()
23+
{
24+
$this->missingData = new \stdClass();
25+
}
26+
27+
public function handle(FormInterface $form, mixed $data): mixed
28+
{
29+
$processedData = $this->handleMissingData($form, $data);
30+
31+
return $processedData === $this->missingData ? $data : $processedData;
32+
}
33+
34+
private function handleMissingData(FormInterface $form, mixed $data): mixed
35+
{
36+
if ($form->getConfig()->getType() instanceof ResolvedFormTypeInterface && $form->getConfig()->getType()->getInnerType() instanceof CheckboxType) {
37+
$falseValues = $form->getConfig()->getOption('false_values');
38+
39+
if ($data === $this->missingData) {
40+
return $falseValues[0];
41+
}
42+
43+
if (\in_array($data, $falseValues)) {
44+
return $data;
45+
}
46+
}
47+
48+
if (null === $data || $this->missingData === $data) {
49+
$data = $form->getConfig()->getCompound() ? [] : $data;
50+
}
51+
52+
if (\is_array($data)) {
53+
$children = $form->getConfig()->getCompound() ? $form->all() : [$form];
54+
55+
foreach ($children as $child) {
56+
$value = $this->handleMissingData($child, \array_key_exists($child->getName(), $data) ? $data[$child->getName()] : $this->missingData);
57+
58+
if ($this->missingData !== $value) {
59+
$data[$child->getName()] = $value;
60+
}
61+
}
62+
63+
return $data ?: $this->missingData;
64+
}
65+
66+
return $data;
67+
}
68+
}

src/Symfony/Component/Form/NativeRequestHandler.php

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\Form;
1313

1414
use Symfony\Component\Form\Exception\UnexpectedTypeException;
15+
use Symfony\Component\Form\MissingDataHandler;
1516
use Symfony\Component\Form\Util\ServerParams;
1617

1718
/**
@@ -22,6 +23,7 @@
2223
class NativeRequestHandler implements RequestHandlerInterface
2324
{
2425
private $serverParams;
26+
private MissingDataHandler $missingDataHandler;
2527

2628
/**
2729
* The allowed keys of the $_FILES array.
@@ -37,6 +39,7 @@ class NativeRequestHandler implements RequestHandlerInterface
3739
public function __construct(ServerParams $params = null)
3840
{
3941
$this->serverParams = $params ?? new ServerParams();
42+
$this->missingDataHandler = new MissingDataHandler();
4043
}
4144

4245
/**
@@ -63,13 +66,13 @@ public function handleRequest(FormInterface $form, $request = null)
6366
if ('' === $name) {
6467
$data = $_GET;
6568
} else {
66-
// Don't submit GET requests if the form's name does not exist
67-
// in the request
68-
if (!isset($_GET[$name])) {
69+
$missingData = $this->missingDataHandler->missingData;
70+
71+
if ($missingData === $data = $this->missingDataHandler->handle($form, $_GET[$name] ?? $missingData)) {
72+
// Don't submit GET requests if the form's name does not exist
73+
// in the request
6974
return;
7075
}
71-
72-
$data = $_GET[$name];
7376
}
7477
} else {
7578
// Mark the form with an error if the uploaded size was too large
@@ -101,6 +104,15 @@ public function handleRequest(FormInterface $form, $request = null)
101104
$params = \array_key_exists($name, $_POST) ? $_POST[$name] : $default;
102105
$files = \array_key_exists($name, $fixedFiles) ? $fixedFiles[$name] : $default;
103106
} else {
107+
$params = $this->missingDataHandler->missingData;
108+
$files = null;
109+
}
110+
111+
if ('PATCH' !== $method) {
112+
$params = $this->missingDataHandler->handle($form, $params);
113+
}
114+
115+
if ($this->missingDataHandler->missingData === $params) {
104116
// Don't submit the form if it is not present in the request
105117
return;
106118
}

src/Symfony/Component/Form/Tests/AbstractRequestHandlerTest.php

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\EventDispatcher\EventDispatcher;
1616
use Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper;
17+
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
18+
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
19+
use Symfony\Component\Form\Extension\Core\Type\FormType;
1720
use Symfony\Component\Form\Form;
1821
use Symfony\Component\Form\FormBuilder;
1922
use Symfony\Component\Form\FormError;
@@ -64,6 +67,93 @@ public function getNormalizedIniPostMaxSize(): string
6467
$this->request = null;
6568
}
6669

70+
/**
71+
* @dataProvider methodExceptPatchProvider
72+
*/
73+
public function testSubmitCheckboxInCollectionFormWithEmptyData($method)
74+
{
75+
$form = $this->factory->create(CollectionType::class, [true, false, true], [
76+
'entry_type' => CheckboxType::class,
77+
'method' => $method,
78+
]);
79+
80+
$this->setRequestData($method, [
81+
]);
82+
83+
$this->requestHandler->handleRequest($form, $this->request);
84+
85+
$this->assertEqualsCanonicalizing([false, false, false], $form->getData());
86+
}
87+
88+
/**
89+
* @dataProvider methodExceptPatchProvider
90+
*/
91+
public function testSubmitCheckboxInCollectionFormWithPartialData($method)
92+
{
93+
$form = $this->factory->create(CollectionType::class, [true, false, true], [
94+
'entry_type' => CheckboxType::class,
95+
'method' => $method,
96+
]);
97+
98+
$this->setRequestData($method, [
99+
'collection' => [
100+
1 => true,
101+
],
102+
]);
103+
104+
$this->requestHandler->handleRequest($form, $this->request);
105+
106+
$this->assertEqualsCanonicalizing([false, true, false], $form->getData());
107+
}
108+
109+
/**
110+
* @dataProvider methodExceptPatchProvider
111+
*/
112+
public function testSubmitCheckboxFormWithEmptyData($method)
113+
{
114+
$form = $this->factory->create(FormType::class, ['subform' => ['checkbox' => true]], [
115+
'method' => $method,
116+
])
117+
->add('subform', FormType::class, [
118+
'compound' => true,
119+
]);
120+
121+
$form->get('subform')
122+
->add('checkbox', CheckboxType::class);
123+
124+
$this->setRequestData($method, []);
125+
126+
$this->requestHandler->handleRequest($form, $this->request);
127+
128+
$this->assertEquals(['subform' => ['checkbox' => false]], $form->getData());
129+
}
130+
131+
/**
132+
* @dataProvider methodExceptPatchProvider
133+
*/
134+
public function testSubmitSimpleCheckboxFormWithEmptyData($method)
135+
{
136+
$form = $this->factory->createNamed('checkbox', CheckboxType::class, true, [
137+
'method' => $method,
138+
]);
139+
140+
$this->setRequestData($method, []);
141+
142+
$this->requestHandler->handleRequest($form, $this->request);
143+
144+
$this->assertFalse($form->getData());
145+
}
146+
147+
public function methodExceptPatchProvider()
148+
{
149+
return [
150+
['POST'],
151+
['PUT'],
152+
['DELETE'],
153+
['GET'],
154+
];
155+
}
156+
67157
public function methodExceptGetProvider()
68158
{
69159
return [

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy