-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Form] Fix form value merging involving file upload, collection & checkbox #54324
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
base: 5.4
Are you sure you want to change the base?
Changes from 11 commits
597b6a3
f943adb
fdc3e01
79fdbaa
0c9b3d7
f3b5efd
55deed0
aec4b56
21fbb71
60008f4
30c95b8
ac24610
1cf2c5f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -355,6 +355,53 @@ public function testMergeZeroIndexedCollection() | |
$this->assertNotNull($itemsForm->get('0')->get('file')); | ||
} | ||
|
||
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->assertEquals( 8000 9;test', $itemsForm->get('1')->get('item')->getData()); | ||
$this->assertNotNull($itemsForm->get('0')->get('file')); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. shouldn't this assert the form data is not null instead of asserting that the Form instance is not null ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. btw, |
||
} | ||
|
||
/** | ||
* @dataProvider methodExceptGetProvider | ||
*/ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
<?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\Util; | ||
|
||
use Symfony\Component\HttpFoundation\File\UploadedFile; | ||
|
||
/** | ||
* @author Priyadi Iman Nurcahyo <priyadi@rekalogika.com> | ||
* | ||
* @internal | ||
*/ | ||
priyadi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
class ParamFilesMerger | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of introducing this new internal class the code should be moved into private methods in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The class represents a node in the tree. It is used by itself to traverse the entire tree. The task will be very complicated without a dedicated class. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have sent a PR against your fork which inlines the methods from the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have merged your PR |
||
{ | ||
private array $path; | ||
private array $params; | ||
private array $files; | ||
|
||
/** | ||
* @param array $path The path to the current element, empty means the root | ||
*/ | ||
public function __construct(array $path, array $params, array $files) | ||
{ | ||
$this->path = $path; | ||
$this->params = $params; | ||
$this->files = $files; | ||
} | ||
|
||
public function getResult() | ||
{ | ||
$paramsValue = $this->getParamsValue(); | ||
$filesValue = $this->getFilesValue(); | ||
|
||
if (null === $paramsValue) { | ||
return $filesValue; | ||
} elseif (\is_array($paramsValue)) { | ||
priyadi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (self::isFileUpload($filesValue)) { | ||
return $filesValue; // if the array is a file upload field, it has the precedence | ||
} elseif (\is_array($filesValue)) { | ||
return $this->getResultArray($paramsValue, $filesValue); | ||
} | ||
|
||
return $paramsValue; // params has the precedence | ||
} else { // $paramsValue has a non-array value | ||
if (self::isFileUpload($filesValue)) { | ||
return $filesValue; // if the array is a file upload field, it has the precedence | ||
} | ||
|
||
return $paramsValue; | ||
} | ||
} | ||
|
||
/** | ||
* @param UploadedFile|array $value | ||
*/ | ||
private static function isFileUpload($value): bool | ||
{ | ||
if ($value instanceof UploadedFile) { | ||
return true; | ||
} | ||
|
||
if (!\is_array($value) || !\in_array(\count($value), [5, 6], true)) { | ||
return false; | ||
} | ||
|
||
if (\array_key_exists('full_path', $value)) { | ||
unset($value['full_path']); | ||
} | ||
|
||
$keys = array_keys($value); | ||
sort($keys); | ||
priyadi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
return ['error', 'name', 'size', 'tmp_name', 'type'] === $keys; | ||
} | ||
|
||
private static function doesNotContainNonFileUploadArray(array $array): bool | ||
{ | ||
foreach ($array as $value) { | ||
if (\is_array($value) && !self::isFileUpload($value)) { | ||
priyadi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return false; | ||
} | ||
} | ||
|
||
return true; | ||
} | ||
|
||
private function getResultArray(array $paramsValue, array $filesValue): array | ||
{ | ||
// if both are lists and both does not contains array, then merge them and return | ||
if ( | ||
array_is_list($paramsValue) | ||
&& self::doesNotContainNonFileUploadArray($paramsValue) | ||
&& array_is_list($filesValue) | ||
&& self::doesNotContainNonFileUploadArray($filesValue) | ||
) { | ||
return array_merge($paramsValue, $filesValue); | ||
} | ||
|
||
// heuristics to preserve order, the bigger array wins | ||
if (\count($filesValue) > \count($paramsValue)) { | ||
$keys = array_unique(array_merge(array_keys($filesValue), array_keys($paramsValue))); | ||
} else { | ||
$keys = array_unique(array_merge(array_keys($paramsValue), array_keys($filesValue))); | ||
} | ||
|
||
$result = []; | ||
|
||
foreach ($keys as $key) { | ||
$path = $this->path; | ||
$path[] = $key; | ||
|
||
$node = new self($path, $this->params, $this->files); | ||
|
||
$result[$key] = $node->getResult(); | ||
} | ||
|
||
return $result; | ||
} | ||
|
||
/** | ||
* Gets the value of the current element in the params according to the path. | ||
*/ | ||
private function getParamsValue() | ||
priyadi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
$params = $this->params; | ||
|
||
foreach ($this->path as $key) { | ||
if (null === $params = $params[$key] ?? null) { | ||
return null; | ||
} | ||
} | ||
|
||
return $params; | ||
} | ||
|
||
/** | ||
* Gets the value of the current element in the files according to the path. | ||
*/ | ||
private function getFilesValue() | ||
priyadi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
$files = $this->files; | ||
|
||
foreach ($this->path as $key) { | ||
if (null === $files = $files[$key] ?? null) { | ||
return null; | ||
} | ||
} | ||
|
||
return $files; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this misses assertions on
$itemsForm->get('0')->get('name')
and$itemsForm->get('1')->get('file')