Issue 1:
Q
A

Bug report?
yes

Feature request?
no

BC Break report?
no

RFC?
no

Symfony version
2.7

Hi there!

This morning I faced an issue in EasyAdminBundle related to form themes and form.parent check in Twig.

Briefly, the problem happen if we have a simple form with a field named parent. The situation gets worse depending on whether the parent field has children or not.

First, let's see the FormView class, especially the parent property and \ArrayAccess methods:

symfony/src/Symfony/Component/Form/FormView.php

Lines 19 to 126
in
61b7534

class FormView implements \ArrayAccess, \IteratorAggregate, \Countable

{

/**

* The variables assigned to this view.

*/

public $vars = array(

'value' => null,

'attr' => array(),

);

/**

* The parent view.

*/

public $parent;

/**

* The child views.

*

* @var FormView[]

*/

public $children = array();

/**

* Is the form attached to this renderer rendered?

*

* Rendering happens when either the widget or the row method was called.

* Row implicitly includes widget, however certain rendering mechanisms

* have to skip widget rendering when a row is rendered.

*

* @var bool

*/

private $rendered = false;

private $methodRendered = false;

public function __construct(FormView $parent = null)

{

$this->parent = $parent;

}

/**

* Returns whether the view was already rendered.

*

* @return bool Whether this view's widget is rendered

*/

public function isRendered()

{

if (true === $this->rendered || 0 === count($this->children)) {

return $this->rendered;

}

foreach ($this->children as $child) {

if (!$child->isRendered()) {

return false;

}

}

return $this->rendered = true;

}

/**

* Marks the view as rendered.

*

* @return $this

*/

public function setRendered()

{

$this->rendered = true;

return $this;

}

/**

* @return bool

*/

public function isMethodRendered()

{

return $this->methodRendered;

}

public function setMethodRendered()

{

$this->methodRendered = true;

}

/**

* Returns a child by name (implements \ArrayAccess).

*

* @param string $name The child name

*

* @return self The child view

*/

public function offsetGet($name)

{

return $this->children[$name];

}

/**

* Returns whether the given child exists (implements \ArrayAccess).

*

* @param string $name The child name

*

* @return bool Whether the child view exists

*/

public function offsetExists($name)

{

return isset($this->children[$name]);

}

In PHP context there's no problem, the main form view has the $form->parent property and its children's fields ($form['parent']), each one accessible unmistakably by syntax. But, in Twig context, this access/check {% if form.parent is null/empty %} causes a naming collision. The parent attribute is resolved by Twig following the steps below:

https://twig.symfony.com/doc/2.x/templates.html#variables

For convenience's sake form.parent does the following things on the PHP layer:

check if form is an array and parent a valid element; <-- always wins in this particular case

if not, and if form is an object, check that parent is a valid property; <-- never reached

...

if not, and if form is an object, check that getParent is a valid method;

Therefore, the parent child field view is returned instead of the real parent view (which is null in this case)!

There's some place in the source where this kind of check is done and surely in many bundles and apps:

I'm not sure if there's a workaround for current projects/bundles rely on the Symfony form themes templates. I don't think it is a bug of Twig either, but a design error of the FormView class, which is specially designed to work in Twig context.

We may need a getter method for this parent property by now and check {% if form.getParent %} everywhere avoiding the first rule. Wdyt? thoughts?

Issue 2:
How can know if a form has a parent or not, in a view?

The fast answer is:

{{ form.parent }}

but if the form has a child called parent the above statement always return a result, even when they do not have a real parent.

then the only way to know if has a real parent is something like:

{% set hasParent = (form.parent is not null and (not form.offsetExists('parent') or form.parent != form.offsetGet('parent'))) %}

I'm missing something?

a method hasParent() is needed in the FormView to avoid the \ArrayAccess.