Issue 1:
Hi,

I do not know if this is a bug or a feature... I am still investigating this, but if you have some guidance I would appreciate.

For a reason I do not know (yet), sometimes, an empty array is passed to the method Symfony\Component\Form\Extension\Core\DataMapper\RadioListMapper::mapFormsToData (a var_dump gives array (size=0) empty) when nothing is checked by the user (this form is not required).

Until 2.8.5, everything was great. But from 2.8.5, the class Symfony\Component\Form\Extension\Core\DataMapper\RadioListMapper has changed in commit ea5375c, an and Symfony\Component\Form\Exception\UnexpectedTypeException is thrown.

Logically, when I change the method mapFormsToData to allow empty arrays, the method is processed without any error :

public function mapFormsToData($radios, &$choice)
{
// the condition was if (null !== $choice && !is_string($choice))
if (
null !== $choice && !is_string($choice)
AND
!(is_array($choice) && count($choice) === 0)
) {
throw new UnexpectedTypeException($choice, 'null, string or empty array');
}

$choice = null;

// as we have an empty array, this loop is not executed, the $choice remains on `null` and everything is fine...
foreach ($radios as $radio) {
if ($radio->getData()) {
if ('placeholder' === $radio->getName()) {
return;
}

$choice = $radio->getConfig()->getOption('value');

return;
}
}
}

I do not know if we have to be so strong in the condition, or if this class should also accept empty array.

So, is this a bug or a feature ?

Some context

As I said before, I do not know why we have an empty array. This array comes from a CustomType which produce a field "Choice with other" (the user may choose amongst different values, or pick another value and fill a text). This Type may allow multiple choices ('multiple' => true) or not, and is not required. When multiple is true, we do not encounter problems. When multiple is false, the RadioListMapper is executed and the exception is thrown. This Type is attached to a DataTransformer which transform the values into an array and store that as an array.

Some code :

class ChoiceWithOtherType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
//add an 'other' entry in choices array
$options['choices']['_other'] = $this->otherValueLabel;
//ChoiceWithOther must always be expanded
$options['expanded'] = true;

$builder
->add('_other', 'text', array('required' => false))
->add('_choices', 'choice', $options)
;
}
}

When coming out of the DataTransformer, the data is this :

array (size=2)
'_other' => string '' (length=0)
'_choices' => null

Something else...

Something else which might be interesting is this block in Form::submit line 616-623.

if (count($this->children) > 0) {
// Use InheritDataAwareIterator to process children of
// descendants that inherit this form's data.
// These descendants will not be submitted normally (see the check
// for $this->config->getInheritData() above)
$childrenIterator = new InheritDataAwareIterator($this->children);
$childrenIterator = new \RecursiveIteratorIterator($childrenIterator);
$this->config->getDataMapper()->mapFormsToData($childrenIterator, $viewData);
}

When $this->config->getDataMapper()->mapFormsToData($childrenIterator, $viewData); is executed, $viewData contains an empty array, and this throw and Exception. But var_dump($this->getViewData())), return string '' (length=0). I wonder if the $viewData would have some transformation from an empty string to an empty array...

But, if I replace $this->config->getDataMapper()->mapFormsToData($childrenIterator, $viewData) by $this->config->getDataMapper()->mapFormsToData($childrenIterator, $this->getViewData()), the validation does not pass any more.

If you think this might be a bug, could you tell me how I should improve this bug report ? (I hadn't time to reproduce the bug).

Issue 2:
Symfony version(s) affected: 4.2.0, 4.1.* (and probably more, I din't test it)

Description

When rendering a FormType containing a CollectionType, and that CollectionType also contains an other CollectionType, There is an error when trying to render this form in PHP Engine Only.

Rendering the same form on Twig is working correctly.

How to reproduce

<?php
// Form/Type/TestType.php
namespace App\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;

class TestType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('multiple', CollectionType::class, [
'entry_type' => TestSubType::class,
])
->add('submit', SubmitType::class)
;
}
}

<?php
// Form/Type/TestSubType.php
namespace App\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;

class TestSubType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('testSub', TextType::class, [
'label' => 'test Sub',
])
->add('multipleSub', CollectionType::class, [
'entry_type' => TestSubSubType::class,
])
;
}
}

<?php
// Form/Type/TestSubSubType.php
namespace App\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;

class TestSubSubType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
;
}
}

In controller, create the form and send it to a PHP view:

NB : the error occurs only when multiple have a least 1 line

$data = [
'name' => 'name 1',
'multiple' => [
[
'testSub' => 'Name 2',
'multipleSub' => [
[
'name' => 'name 3',
],
],
],
],
];

$form = $this->createForm(TestType::class, $data);

return $this->render('front/index.html.php', [
'form' => $form->createView(),
]);

In index.html.php, simply render the form:

<?php echo $view['form']->form($form); ?>

Possible Solution

I figured out where the problem comes from, but I'm lacking background oho Symfony Form to fix it correctly, regarding the performance.

In Symfony\Component\Form\AbstractRendererEngine, we have those lines:

// The next two if statements contain slightly duplicated code. This is by intention
// and tries to avoid execution of unnecessary checks in order to increase performance.
if (isset($this->resources[$cacheKey][$parentBlockName])) {
// It may happen that the parent block is already loaded, but its level is not.
// In this case, the parent block must have been loaded by loadResourceForBlock(),
// which does not check the hierarchy of the block. Subsequently the block must have
// been found directly on the parent level.
if (!isset($this->resourceHierarchyLevels[$cacheKey][$parentBlockName])) {
$this->resourceHierarchyLevels[$cacheKey][$parentBlockName] = $parentLevel;
}

// Cache the shortcuts for further accesses
$this->resources[$cacheKey][$blockName] = $this->resources[$cacheKey][$parentBlockName];
$this->resourceHierarchyLevels[$cacheKey][$blockName] = $this->resourceHierarchyLevels[$cacheKey][$parentBlockName];

return true;
}

When commenting them, everything works fine.

An other solution is to modify the if check in this way:

if (isset($this->resources[$cacheKey][$parentBlockName]) && false !== $this->resources[$cacheKey][$parentBlockName]) {

Additional context

What looks very weird to me is that these lines was introduced 7 years ago by @webmozart .

I can't think that this bug was here and nobody had trouble with it before me.