Files
archived-form/Tests/AbstractRequestHandlerTestCase.php
Nicolas Grekas d6c6f6ac13 Merge branch '8.0' into 8.1
* 8.0: (26 commits)
  Fix merge
  [RateLimiter] Fix retryAfter when consuming exactly all remaining tokens in FixedWindow and TokenBucket
  [RateLimiter] Fix retryAfter value on last token consume (SlidingWindow)
  [RateLimiter] Fix reservations outside the second fixed window
  [Filesystem] makePathRelative with existing files, remove ending /
  [Config][Routing] Fix exclude option being ignored for non-glob and PSR-4 resources
  [Serializer][Validator] Fix propertyPath in ConstraintViolationListNormalizer with MetadataAwareNameConverter
  [Messenger][Amqp] Don't use retry routing key when sending to failure transport
  [Messenger] Fix re-sending failed messages to a different failure transport
  [DependencyInjection] Fix #[AsTaggedItem] discovery through multi-level decoration chains
  [Config] Fix ArrayShapeGenerator required keys with deep merging
  [Validator] Add a guard when `Parser::IGNORE_UNKNOWN_VARIABLES` is not defined
  [Validator] Correctly handle null `allowedVariables` in `ExpressionSyntaxValidator`
  [DependencyInjection] Fix PriorityTaggedServiceTrait not discovering #[AsTaggedItem] on decorated services
  [Mailer] Clarify the purpose of SentMessage's "message id" concept
  [ObjectMapper] fix nested mapping with class-level transform
  [TwigBridge] Fix Bootstrap 4 form error layout
  [Form] Fix merging POST params and files when collection entries have mismatched indices
  [Validator] Fix type error for non-array items when Unique::fields is set
  [HttpKernel] Fix default locale ignored when Accept-Language has no enabled-locale match
  ...
2026-02-25 18:08:26 +01:00

623 lines
19 KiB
PHP

<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Tests;
use PHPUnit\Framework\Attributes\DataProvider;
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\Extension\Core\Type\TextType;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormFactory;
use Symfony\Component\Form\FormRegistry;
use Symfony\Component\Form\Forms;
use Symfony\Component\Form\RequestHandlerInterface;
use Symfony\Component\Form\ResolvedFormTypeFactory;
use Symfony\Component\Form\Tests\Extension\Type\ItemFileType;
use Symfony\Component\Form\Util\ServerParams;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
abstract class AbstractRequestHandlerTestCase extends TestCase
{
protected RequestHandlerInterface $requestHandler;
protected FormFactory $factory;
protected mixed $request = null;
protected ServerParams $serverParams;
protected function setUp(): void
{
$this->serverParams = new class extends ServerParams {
public ?int $contentLength = null;
public string $postMaxSize = '';
public function getContentLength(): ?int
{
return $this->contentLength;
}
public function getNormalizedIniPostMaxSize(): string
{
return $this->postMaxSize;
}
};
$this->requestHandler = $this->getRequestHandler();
$this->factory = Forms::createFormFactoryBuilder()->getFormFactory();
$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 static function methodExceptPatchProvider(): array
{
return [
['POST'],
['PUT'],
['DELETE'],
['GET'],
];
}
public static function methodExceptGetProvider(): array
{
return [
['POST'],
['PUT'],
['DELETE'],
['PATCH'],
];
}
public static function methodProvider(): array
{
return array_merge([
['GET'],
], self::methodExceptGetProvider());
}
#[DataProvider('methodProvider')]
public function testSubmitIfNameInRequest($method)
{
$form = $this->createForm('param1', $method);
$this->setRequestData($method, [
'param1' => 'DATA',
]);
$this->requestHandler->handleRequest($form, $this->request);
$this->assertTrue($form->isSubmitted());
$this->assertSame('DATA', $form->getData());
}
#[DataProvider('methodProvider')]
public function testDoNotSubmitIfWrongRequestMethod($method)
{
$form = $this->createForm('param1', $method);
$otherMethod = 'POST' === $method ? 'PUT' : 'POST';
$this->setRequestData($otherMethod, [
'param1' => 'DATA',
]);
$this->requestHandler->handleRequest($form, $this->request);
$this->assertFalse($form->isSubmitted());
}
#[DataProvider('methodExceptGetProvider')]
public function testDoNoSubmitSimpleFormIfNameNotInRequestAndNotGetRequest($method)
{
$form = $this->createForm('param1', $method, false);
$this->setRequestData($method, [
'paramx' => [],
]);
$this->requestHandler->handleRequest($form, $this->request);
$this->assertFalse($form->isSubmitted());
}
#[DataProvider('methodExceptGetProvider')]
public function testDoNotSubmitCompoundFormIfNameNotInRequestAndNotGetRequest($method)
{
$form = $this->createForm('param1', $method, true);
$this->setRequestData($method, [
'paramx' => [],
]);
$this->requestHandler->handleRequest($form, $this->request);
$this->assertFalse($form->isSubmitted());
}
public function testDoNotSubmitIfNameNotInRequestAndGetRequest()
{
$form = $this->createForm('param1', 'GET');
$this->setRequestData('GET', [
'paramx' => [],
]);
$this->requestHandler->handleRequest($form, $this->request);
$this->assertFalse($form->isSubmitted());
}
#[DataProvider('methodProvider')]
public function testSubmitFormWithEmptyNameIfAtLeastOneFieldInRequest($method)
{
$form = $this->createForm('', $method, true);
$form->add($this->createForm('param1'));
$form->add($this->createForm('param2'));
$this->setRequestData($method, $requestData = [
'param1' => 'submitted value',
'paramx' => 'submitted value',
]);
$this->requestHandler->handleRequest($form, $this->request);
$this->assertTrue($form->isSubmitted());
$this->assertTrue($form->get('param1')->isSubmitted());
$this->assertSame('submitted value', $form->get('param1')->getData());
if ('PATCH' === $method) {
$this->assertFalse($form->get('param2')->isSubmitted());
} else {
$this->assertTrue($form->get('param2')->isSubmitted());
}
$this->assertNull($form->get('param2')->getData());
}
#[DataProvider('methodProvider')]
public function testDoNotSubmitFormWithEmptyNameIfNoFieldInRequest($method)
{
$form = $this->createForm('', $method, true);
$form->add($this->createForm('param1'));
$form->add($this->createForm('param2'));
$this->setRequestData($method, [
'paramx' => 'submitted value',
]);
$this->requestHandler->handleRequest($form, $this->request);
$this->assertFalse($form->isSubmitted());
}
#[DataProvider('methodExceptGetProvider')]
public function testMergeParamsAndFiles($method)
{
$form = $this->createForm('param1', $method, true);
$form->add($this->createForm('field1'));
$form->add($this->createBuilder('field2', false, ['allow_file_upload' => true])->getForm());
$file = $this->getUploadedFile();
$this->setRequestData($method, [
'param1' => [
'field1' => 'DATA',
],
], [
'param1' => [
'field2' => $file,
],
]);
$this->requestHandler->handleRequest($form, $this->request);
$this->assertTrue($form->isSubmitted());
$this->assertSame('DATA', $form->get('field1')->getData());
$this->assertSame($file, $form->get('field2')->getData());
}
public function testIntegerChildren()
{
$form = $this->createForm('root', 'POST', true);
$form->add('0', TextType::class);
$form->add('1', TextType::class);
$this->setRequestData('POST', [
'root' => [
'1' => 'bar',
],
]);
$this->requestHandler->handleRequest($form, $this->request);
$this->assertNull($form->get('0')->getData());
$this->assertSame('bar', $form->get('1')->getData());
}
#[DataProvider('methodExceptGetProvider')]
public function testMergeParamsAndFilesMultiple($method)
{
$form = $this->createForm('param1', $method, true);
$form->add($this->createBuilder('field1', false, ['allow_file_upload' => true, 'multiple' => true])->getForm());
$file1 = $this->getUploadedFile();
$file2 = $this->getUploadedFile();
$this->setRequestData($method, [
'param1' => [
'field1' => [
'foo',
'bar',
'baz',
],
],
], [
'param1' => [
'field1' => [
$file1,
$file2,
],
],
]);
$this->requestHandler->handleRequest($form, $this->request);
$data = $form->get('field1')->getData();
$this->assertTrue($form->isSubmitted());
$this->assertIsArray($data);
$this->assertCount(5, $data);
$this->assertSame(['foo', 'bar', 'baz', $file1, $file2], $data);
}
#[DataProvider('methodExceptGetProvider')]
public function testParamTakesPrecedenceOverFile($method)
{
$form = $this->createForm('param1', $method);
$file = $this->getUploadedFile();
$this->setRequestData($method, [
'param1' => 'DATA',
], [
'param1' => $file,
]);
$this->requestHandler->handleRequest($form, $this->request);
$this->assertTrue($form->isSubmitted());
$this->assertSame('DATA', $form->getData());
}
public function testMergeZeroIndexedCollection()
{
$form = $this->createForm('root', 'POST', true);
$form->add('items', CollectionType::class, [
'entry_type' => ItemFileType::class,
'allow_add' => true,
]);
$file = $this->getUploadedFile();
$this->setRequestData('POST', [
'root' => [
'items' => [
0 => [
'item' => 'test',
],
],
],
], [
'root' => [
'items' => [
0 => [
'file' => $file,
],
],
],
]);
$this->requestHandler->handleRequest($form, $this->request);
$itemsForm = $form->get('items');
$this->assertTrue($form->isSubmitted());
$this->assertTrue($form->isValid());
$this->assertTrue($itemsForm->has('0'));
$this->assertFalse($itemsForm->has('1'));
$this->assertEquals('test', $itemsForm->get('0')->get('item')->getData());
$this->assertNotNull($itemsForm->get('0')->get('file')->getData());
}
public function testMergePartialDataFromCollection()
{
$form = $this->createForm('root', 'POST', true);
$form->add('items', CollectionType::class, [
'entry_type' => ItemFileType::class,
'allow_add' => true,
]);
$file = $this->getUploadedFile();
$file2 = $this->getUploadedFile();
$this->setRequestData('POST', [
'root' => [
'items' => [
1 => [
'item' => 'test',
],
],
],
], [
'root' => [
'items' => [
0 => [
'file' => $file,
],
1 => [
'file' => $file2,
],
],
],
]);
$this->requestHandler->handleRequest($form, $this->request);
$itemsForm = $form->get('items');
$data = $itemsForm->getData();
$this->assertTrue($form->isSubmitted());
$this->assertTrue($form->isValid());
$this->assertCount(2, $data);
$this->assertArrayHasKey(0, $data);
$this->assertArrayHasKey(1, $data);
$this->assertNull($itemsForm->get('0')->get('item')->getData());
$this->assertNotNull($itemsForm->get('0')->get('file')->getData());
$this->assertEquals('test', $itemsForm->get('1')->get('item')->getData());
$this->assertNotNull($itemsForm->get('1')->get('file')->getData());
}
#[DataProvider('methodExceptGetProvider')]
public function testSubmitFileIfNoParam($method)
{
$form = $this->createBuilder('param1', false, ['allow_file_upload' => true])
->setMethod($method)
->getForm();
$file = $this->getUploadedFile();
$this->setRequestData($method, [
'param1' => null,
], [
'param1' => $file,
]);
$this->requestHandler->handleRequest($form, $this->request);
$this->assertTrue($form->isSubmitted());
$this->assertSame($file, $form->getData());
}
#[DataProvider('methodExceptGetProvider')]
public function testSubmitMultipleFiles($method)
{
$form = $this->createBuilder('param1', false, ['allow_file_upload' => true])
->setMethod($method)
->getForm();
$file = $this->getUploadedFile();
$this->setRequestData($method, [
'param1' => null,
], [
'param2' => $this->getUploadedFile('2'),
'param1' => $file,
'param3' => $this->getUploadedFile('3'),
]);
$this->requestHandler->handleRequest($form, $this->request);
$this->assertTrue($form->isSubmitted());
$this->assertSame($file, $form->getData());
}
#[DataProvider('methodExceptGetProvider')]
public function testSubmitFileWithNamelessForm($method)
{
$form = $this->createForm('', $method, true);
$fileForm = $this->createBuilder('document', false, ['allow_file_upload' => true])->getForm();
$form->add($fileForm);
$file = $this->getUploadedFile();
$this->setRequestData($method, [
'document' => null,
], [
'document' => $file,
]);
$this->requestHandler->handleRequest($form, $this->request);
$this->assertTrue($form->isSubmitted());
$this->assertSame($file, $fileForm->getData());
}
#[DataProvider('getPostMaxSizeFixtures')]
public function testAddFormErrorIfPostMaxSizeExceeded(?int $contentLength, string $iniMax, bool $shouldFail, array $errorParams = [])
{
$this->serverParams->contentLength = $contentLength;
$this->serverParams->postMaxSize = $iniMax;
$options = ['post_max_size_message' => 'Max {{ max }}!'];
$form = $this->factory->createNamed('name', 'Symfony\Component\Form\Extension\Core\Type\TextType', null, $options);
$this->setRequestData('POST', [], []);
$this->requestHandler->handleRequest($form, $this->request);
if ($shouldFail) {
$error = new FormError($options['post_max_size_message'], null, $errorParams);
$error->setOrigin($form);
$this->assertEquals([$error], iterator_to_array($form->getErrors()));
$this->assertTrue($form->isSubmitted());
} else {
$this->assertCount(0, $form->getErrors());
$this->assertFalse($form->isSubmitted());
}
}
public static function getPostMaxSizeFixtures()
{
return [
[1024 ** 3 + 1, '1G', true, ['{{ max }}' => '1G']],
[1024 ** 3, '1G', false],
[1024 ** 2 + 1, '1M', true, ['{{ max }}' => '1M']],
[1024 ** 2, '1M', false],
[1024 + 1, '1K', true, ['{{ max }}' => '1K']],
[1024, '1K', false],
[null, '1K', false],
[1024, '', false],
[1024, '0', false],
];
}
public function testUploadedFilesAreAccepted()
{
$this->assertTrue($this->requestHandler->isFileUpload($this->getUploadedFile()));
}
public function testInvalidFilesAreRejected()
{
$this->assertFalse($this->requestHandler->isFileUpload($this->getInvalidFile()));
}
#[DataProvider('uploadFileErrorCodes')]
public function testFailedFileUploadIsTurnedIntoFormError($errorCode, $expectedErrorCode)
{
$this->assertSame($expectedErrorCode, $this->requestHandler->getUploadFileError($this->getFailedUploadedFile($errorCode)));
}
public static function uploadFileErrorCodes()
{
return [
'no error' => [\UPLOAD_ERR_OK, null],
'upload_max_filesize ini directive' => [\UPLOAD_ERR_INI_SIZE, \UPLOAD_ERR_INI_SIZE],
'MAX_FILE_SIZE from form' => [\UPLOAD_ERR_FORM_SIZE, \UPLOAD_ERR_FORM_SIZE],
'partially uploaded' => [\UPLOAD_ERR_PARTIAL, \UPLOAD_ERR_PARTIAL],
'no file upload' => [\UPLOAD_ERR_NO_FILE, \UPLOAD_ERR_NO_FILE],
'missing temporary directory' => [\UPLOAD_ERR_NO_TMP_DIR, \UPLOAD_ERR_NO_TMP_DIR],
'write failure' => [\UPLOAD_ERR_CANT_WRITE, \UPLOAD_ERR_CANT_WRITE],
'stopped by extension' => [\UPLOAD_ERR_EXTENSION, \UPLOAD_ERR_EXTENSION],
];
}
abstract protected function setRequestData($method, $data, $files = []);
abstract protected function getRequestHandler();
abstract protected function getUploadedFile($suffix = '');
abstract protected function getInvalidFile();
abstract protected function getFailedUploadedFile($errorCode);
protected function createForm($name, $method = null, $compound = false)
{
$config = $this->createBuilder($name, $compound);
if (null !== $method) {
$config->setMethod($method);
}
return new Form($config);
}
protected function createBuilder($name, $compound = false, array $options = [])
{
$builder = new FormBuilder($name, null, new EventDispatcher(), new FormFactory(new FormRegistry([], new ResolvedFormTypeFactory())), $options);
$builder->setCompound($compound);
if ($compound) {
$builder->setDataMapper(new DataMapper());
}
return $builder;
}
}