Reputation: 1310
I would want a form for my entity « X ». This entity own a relationship OneToMany with many entities of type « X ». It's a relationship parent <-> children.
When I create my form simply, it works.
class XType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add("name", "text", array("label" => "Nom"))
->add("children", "collection", array(
"type" => new XType(),
"by_reference" => false));
}
}
Then, I would like add easily new entity in my collection with the option « allow_add », and used the prototype to add in javascript. This is my form with the « allow_add » option.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add("name", "text", array("label" => "Nom"))
->add("children", "collection", array(
"type" => new XType(),
"allow_add" => true,
"by_reference" => false));
}
When I execute with or without called the prototype, I have an webserver error. It's XDebug which kick my request because the recursive call is too big. There are cascade call.
What's the best solution to resolve my problem ?
Upvotes: 0
Views: 3193
Reputation: 2596
I ran into the same issue (my web server crashes, as well, due to too many recursive calls). My quick workaround was to simply create a dummy (cloned) type that doesn't contain the recursive field (this works for me since I was interested only into the 1st level children).
So, if this scenario is applicable to you, as well, you could change your code as follows:
class XType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add("name", "text", array("label" => "Nom"))
->add("children", "collection", array(
"type" => new XTypeWithoutChildren(),
"by_reference" => false));
}
}
class XTypeWithoutChildren extends XType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add("name", "text", array("label" => "Nom"));
}
}
Upvotes: 1
Reputation: 2167
I can't really say where your problem is, but should be somewhere around javascript and the collection prototype. Also, when you allow items to be added, you usually allow to allow_delete too.
Take a look at my example:
$builder->add('book', 'collection', array(
'type' => new BookType(),
'allow_add' => true,
'allow_delete' => true,
'prototype_name' => '__prototype__',
'by_reference' => false,
'error_bubbling' => false
);
To the template where you render form, include this:
{% block javascript %}
{{ parent() }}
{% javascripts
'@ProjectSomeBundle/Resources/public/js/form/collection.js'
%}
<script type="text/javascript" src="{{ asset_url }}"></script>
{% endjavascripts %}
{% endblock %}
And the collection.js file holds this:
$(function($) {
$addButton = $('button.add');
$collection = $addButton.parent().children().first();
$addButton.click(function () {
var prototype = $($collection.data('prototype').replace(/__prototype__/g, function() { return (new Date()).getTime(); }));
prototype.appendTo($collection.children('ul'));
});
$('body').on('click', 'button.remove', function () {
$(this).parent().remove();
});
});
Also, you should be using those customised collection widgets, which you pass to twig. Nothing fancy, it will be rendered as unordered list with items, to make items easily accessible.
config.yml:
twig:
form:
resources:
- 'ProjectSomeBundle:Form:fields.html.twig'
fields.html.twig:
{% extends 'form_div_layout.html.twig' %}
{% block collection_widget %}
{% import "ProjectSomeBundle:Macro:macro.html.twig" as macro %}
{% spaceless %}
<div class="collection">
{% if prototype is defined %}
{% set attr = attr|merge({'data-prototype': block('prototype_widget') }) %}
{% endif %}
<div {{ block('widget_container_attributes') }}>
<ul>
{% for row in form %}
{{ macro.collection_item_widget(row) }}
{% endfor %}
</ul>
{{ form_rest(form) }}
</div>
<div class="clear"></div>
<button type="button" class="add">{% trans %}Add{% endtrans %}</button>
</div>
<div class="clear"></div>
{% endspaceless %}
{% endblock collection_widget %}
{% block prototype_widget %}
{% spaceless %}
{{ macro.collection_item_widget(prototype) }}
{% endspaceless %}
{% endblock prototype_widget %}
You can notice it uses macro, so here it is:
macro.html.twig
{% macro collection_item_widget(fields) %}
<li>
{% set fieldNum = 1 %}
{% for field in fields %}
<div class="field_{{ fieldNum }}">
{{ form_label(field) }}
{{ form_errors(field) }}
{{ form_widget(field) }}
</div>
{% set fieldNum = fieldNum + 1 %}
{% endfor %}
<button type="button" class="remove">{% trans %}Delete{% endtrans %}</button>
<div class="clear"></div>
</li>
{% endmacro %}
This is a full example, I hope you find it useful and works for you.
Upvotes: 2