-
-
Notifications
You must be signed in to change notification settings - Fork 9.7k
Description
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 sakeform.parent
does the following things on the PHP layer:
- check if
form
is an array andparent
a valid element; <-- always wins in this particular case- if not, and if
form
is an object, check thatparent
is a valid property; <-- never reached- ...
- if not, and if
form
is an object, check thatgetParent
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?