diff --git a/src/Symfony/Component/Form/ButtonBuilder.php b/src/Symfony/Component/Form/ButtonBuilder.php index bfba1d6cd0970..a4e92f50d6927 100644 --- a/src/Symfony/Component/Form/ButtonBuilder.php +++ b/src/Symfony/Component/Form/ButtonBuilder.php @@ -29,7 +29,7 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface protected $locked = false; private bool $disabled = false; - private ResolvedFormTypeInterface $type; + private ?ResolvedFormTypeInterface $type = null; private string $name; private array $attributes = []; private array $options; @@ -453,7 +453,7 @@ public function getCompound(): bool /** * Returns the form type used to construct the button. */ - public function getType(): ResolvedFormTypeInterface + public function getType(): ?ResolvedFormTypeInterface { return $this->type; } diff --git a/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php b/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php index 3b1aaebf02648..1471d19930b71 100644 --- a/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php +++ b/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php @@ -14,6 +14,7 @@ use Symfony\Component\Form\Exception\UnexpectedTypeException; use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\MissingDataHandler; use Symfony\Component\Form\RequestHandlerInterface; use Symfony\Component\Form\Util\ServerParams; use Symfony\Component\HttpFoundation\File\File; @@ -29,10 +30,12 @@ class HttpFoundationRequestHandler implements RequestHandlerInterface { private ServerParams $serverParams; + private MissingDataHandler $missingDataHandler; public function __construct(ServerParams $serverParams = null) { $this->serverParams = $serverParams ?? new ServerParams(); + $this->missingDataHandler = new MissingDataHandler(); } public function handleRequest(FormInterface $form, mixed $request = null) @@ -54,13 +57,16 @@ public function handleRequest(FormInterface $form, mixed $request = null) if ('' === $name) { $data = $request->query->all(); } else { - // Don't submit GET requests if the form's name does not exist - // in the request - if (!$request->query->has($name)) { + $missingData = $this->missingDataHandler->missingData; + $queryData = $request->query->all()[$name] ?? $missingData; + + $data = $this->missingDataHandler->handle($form, $queryData); + + if ($missingData === $data) { + // Don't submit GET requests if the form's name does not exist + // in the request return; } - - $data = $request->query->all()[$name]; } } else { // Mark the form with an error if the uploaded size was too large @@ -87,6 +93,15 @@ public function handleRequest(FormInterface $form, mixed $request = null) $params = $request->request->all()[$name] ?? $default; $files = $request->files->get($name, $default); } else { + $params = $this->missingDataHandler->missingData; + $files = null; + } + + if ('PATCH' !== $method) { + $params = $this->missingDataHandler->handle($form, $params); + } + + if ($this->missingDataHandler->missingData === $params) { // Don't submit the form if it is not present in the request return; } diff --git a/src/Symfony/Component/Form/FormConfigBuilder.php b/src/Symfony/Component/Form/FormConfigBuilder.php index e18db7ed8e320..7baab9a1ed5c7 100644 --- a/src/Symfony/Component/Form/FormConfigBuilder.php +++ b/src/Symfony/Component/Form/FormConfigBuilder.php @@ -41,7 +41,7 @@ class FormConfigBuilder implements FormConfigBuilderInterface private bool $byReference = true; private bool $inheritData = false; private bool $compound = false; - private ResolvedFormTypeInterface $type; + private ?ResolvedFormTypeInterface $type = null; private array $viewTransformers = []; private array $modelTransformers = []; private ?DataMapperInterface $dataMapper = null; @@ -197,7 +197,7 @@ public function getCompound(): bool return $this->compound; } - public function getType(): ResolvedFormTypeInterface + public function getType(): ?ResolvedFormTypeInterface { return $this->type; } diff --git a/src/Symfony/Component/Form/FormConfigInterface.php b/src/Symfony/Component/Form/FormConfigInterface.php index 93d1998ec2620..defae6bf6ac3b 100644 --- a/src/Symfony/Component/Form/FormConfigInterface.php +++ b/src/Symfony/Component/Form/FormConfigInterface.php @@ -66,7 +66,7 @@ public function getCompound(): bool; /** * Returns the resolved form type used to construct the form. */ - public function getType(): ResolvedFormTypeInterface; + public function getType(): ?ResolvedFormTypeInterface; /** * Returns the view transformers of the form. diff --git a/src/Symfony/Component/Form/MissingDataHandler.php b/src/Symfony/Component/Form/MissingDataHandler.php new file mode 100644 index 0000000000000..a1a61258a4a46 --- /dev/null +++ b/src/Symfony/Component/Form/MissingDataHandler.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form; + +use Symfony\Component\Form\Extension\Core\Type\CheckboxType; + +class MissingDataHandler +{ + public readonly \stdClass $missingData; + + public function __construct() + { + $this->missingData = new \stdClass(); + } + + public function handle(FormInterface $form, mixed $data): mixed + { + $processedData = $this->handleMissingData($form, $data); + + return $processedData === $this->missingData ? $data : $processedData; + } + + private function handleMissingData(FormInterface $form, mixed $data): mixed + { + if ($form->getConfig()->getType() instanceof ResolvedFormTypeInterface && $form->getConfig()->getType()->getInnerType() instanceof CheckboxType) { + $falseValues = $form->getConfig()->getOption('false_values'); + + if ($data === $this->missingData) { + return $falseValues[0]; + } + + if (\in_array($data, $falseValues)) { + return $data; + } + } + + if (null === $data || $this->missingData === $data) { + $data = $form->getConfig()->getCompound() ? [] : $data; + } + + if (\is_array($data)) { + $children = $form->getConfig()->getCompound() ? $form->all() : [$form]; + + foreach ($children as $child) { + $value = $this->handleMissingData($child, \array_key_exists($child->getName(), $data) ? $data[$child->getName()] : $this->missingData); + + if ($this->missingData !== $value) { + $data[$child->getName()] = $value; + } + } + + return $data ?: $this->missingData; + } + + return $data; + } +} diff --git a/src/Symfony/Component/Form/NativeRequestHandler.php b/src/Symfony/Component/Form/NativeRequestHandler.php index cf193398c8318..e3632713623f9 100644 --- a/src/Symfony/Component/Form/NativeRequestHandler.php +++ b/src/Symfony/Component/Form/NativeRequestHandler.php @@ -22,6 +22,7 @@ class NativeRequestHandler implements RequestHandlerInterface { private ServerParams $serverParams; + private MissingDataHandler $missingDataHandler; /** * The allowed keys of the $_FILES array. @@ -37,6 +38,7 @@ class NativeRequestHandler implements RequestHandlerInterface public function __construct(ServerParams $params = null) { $this->serverParams = $params ?? new ServerParams(); + $this->missingDataHandler = new MissingDataHandler(); } /** @@ -61,13 +63,13 @@ public function handleRequest(FormInterface $form, mixed $request = null) if ('' === $name) { $data = $_GET; } else { - // Don't submit GET requests if the form's name does not exist - // in the request - if (!isset($_GET[$name])) { + $missingData = $this->missingDataHandler->missingData; + + if ($missingData === $data = $this->missingDataHandler->handle($form, $_GET[$name] ?? $missingData)) { + // Don't submit GET requests if the form's name does not exist + // in the request return; } - - $data = $_GET[$name]; } } else { // Mark the form with an error if the uploaded size was too large @@ -99,6 +101,15 @@ public function handleRequest(FormInterface $form, mixed $request = null) $params = \array_key_exists($name, $_POST) ? $_POST[$name] : $default; $files = \array_key_exists($name, $fixedFiles) ? $fixedFiles[$name] : $default; } else { + $params = $this->missingDataHandler->missingData; + $files = null; + } + + if ('PATCH' !== $method) { + $params = $this->missingDataHandler->handle($form, $params); + } + + if ($this->missingDataHandler->missingData === $params) { // Don't submit the form if it is not present in the request return; } diff --git a/src/Symfony/Component/Form/Tests/AbstractRequestHandlerTest.php b/src/Symfony/Component/Form/Tests/AbstractRequestHandlerTest.php index 61b8dc379148a..9fdb2696ad0ff 100644 --- a/src/Symfony/Component/Form/Tests/AbstractRequestHandlerTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractRequestHandlerTest.php @@ -14,6 +14,9 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\Form\Extension\Core\DataMapper\DataMapper; +use Symfony\Component\Form\Extension\Core\Type\CheckboxType; +use Symfony\Component\Form\Extension\Core\Type\CollectionType; +use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\Form; use Symfony\Component\Form\FormBuilder; use Symfony\Component\Form\FormError; @@ -65,6 +68,92 @@ public function getNormalizedIniPostMaxSize(): string $this->request = null; } + /** + * @dataProvider methodExceptPatchProvider + */ + public function testSubmitCheckboxInCollectionFormWithEmptyData($method) + { + $form = $this->factory->create(CollectionType::class, [true, false, true], [ + 'entry_type' => CheckboxType::class, + 'method' => $method, + ]); + + $this->setRequestData($method, []); + + $this->requestHandler->handleRequest($form, $this->request); + + $this->assertEqualsCanonicalizing([false, false, false], $form->getData()); + } + + /** + * @dataProvider methodExceptPatchProvider + */ + public function testSubmitCheckboxInCollectionFormWithPartialData($method) + { + $form = $this->factory->create(CollectionType::class, [true, false, true], [ + 'entry_type' => CheckboxType::class, + 'method' => $method, + ]); + + $this->setRequestData($method, [ + 'collection' => [ + 1 => true, + ], + ]); + + $this->requestHandler->handleRequest($form, $this->request); + + $this->assertEqualsCanonicalizing([false, true, false], $form->getData()); + } + + /** + * @dataProvider methodExceptPatchProvider + */ + public function testSubmitCheckboxFormWithEmptyData($method) + { + $form = $this->factory->create(FormType::class, ['subform' => ['checkbox' => true]], [ + 'method' => $method, + ]) + ->add('subform', FormType::class, [ + 'compound' => true, + ]); + + $form->get('subform') + ->add('checkbox', CheckboxType::class); + + $this->setRequestData($method, []); + + $this->requestHandler->handleRequest($form, $this->request); + + $this->assertEquals(['subform' => ['checkbox' => false]], $form->getData()); + } + + /** + * @dataProvider methodExceptPatchProvider + */ + public function testSubmitSimpleCheckboxFormWithEmptyData($method) + { + $form = $this->factory->createNamed('checkbox', CheckboxType::class, true, [ + 'method' => $method, + ]); + + $this->setRequestData($method, []); + + $this->requestHandler->handleRequest($form, $this->request); + + $this->assertFalse($form->getData()); + } + + public function methodExceptPatchProvider() + { + return [ + ['POST'], + ['PUT'], + ['DELETE'], + ['GET'], + ]; + } + public function methodExceptGetProvider() { return [ 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