Symfony2表单集合allow_add和allow_delete null错误(Silex)
I've run into a problem when following the Symfony cookbook for form collections with add/remove. See: http://symfony.com/doc/current/cookbook/form/form_collections.html
Now, for some reason, if I dynamically add a form row but don't fill in any of its fields, I get the following error:
ContextErrorException: Catchable Fatal Error: Argument 1 passed to Project::addTask() must be an instance of Task, null given in D:\web_workspace\wedding\src\testapp.php line 82
I would like people to be able to have blank rows in the form which will just get ignored. For example, if you click "Add Task" a few times, but don't fill in the last row, the form should still be submitted, and the last row should be ignored.
I've created a very simple Silex demo that fits in just a couple of files. I'll highlight it here, but the full example is here, and can be run by just adding Silex via composer: https://gist.github.com/mattsnowboard/7065865
I have the following Models (just Project
which has a description and a collection of Task
s)
class Task
{
protected $name;
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
return $this;
}
}
class Project
{
protected $description;
protected $tasks;
public function __construct()
{
$this->tasks = array();
}
public function getDescription()
{
return $this->description;
}
public function setDescription($description)
{
$this->description = $description;
return $this;
}
public function getTasks()
{
return $this->tasks;
}
public function addTask(Task $task)
{
if (!is_null($task) && !in_array($task, $this->tasks)) {
$this->tasks[] = $task;
}
return $this;
}
public function removeTask(Task $task)
{
if (!is_null($task)) {
$this->tasks = array_diff($this->tasks, array($task));
}
return $this;
}
}
My forms are as follows
class TaskType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', 'text', array(
'required' => false
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => '\Task',
));
}
public function getName()
{
return 'task';
}
}
class ProjectType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('description', 'text', array())
->add('tasks', 'collection', array(
'type' => new TaskType(),
'allow_add' => true,
'by_reference' => false,
'allow_delete' => true,
'required' => false
))
->add('submit', 'submit')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => '\Project',
));
}
public function getName()
{
return 'project';
}
}
The controller adds some test data to the form and just prints the data when it is submitted:
$app->match('/', function (Request $request) use ($app) {
$project = new Project();
// dummy code
$task1 = new Task();
$task1->setName('A Task');
$project->addTask($task1);
// end dummy code
$form = $app['form.factory']->create(new ProjectType(), $project);
$form->handleRequest($request);
if ($form->isValid()) {
$data = $form->getData();
$debug = print_r($data, true);
echo $debug;
}
return $app['twig']->render('test.html.twig', array(
'form' => $form->createView(),
));
})
->method('GET|POST');
The view is mostly this, and the javascript sample from the cookbook
{{ form_start(form) }}
{{ form_row(form.description) }}
<h3>Tags</h3>
<ul class="tasks" data-prototype="{{ form_widget(form.tasks.vars.prototype)|e }}">
{# iterate over each existing tag and render its only field: name #}
{% for task in form.tasks %}
<li>{{ form_row(task.name) }}</li>
{% endfor %}
</ul>
{{ form_widget(form.submit) }}
{{ form_end(form) }}
Am I just missing something? Is this a bug? I feel like this example which is very close to the cookbook should work pretty easily...
Updated: Removing "allow_deleted"
doesn't actually fix this.
Sorry, I figured it out, the collection
type shouldn't have 'required' => false
$builder->add('description', 'text', array())
->add('tasks', 'collection', array(
'type' => new TaskType(),
'allow_add' => true,
'by_reference' => false,
'allow_delete' => true,
'required' => false
))
Should be
$builder->add('description', 'text', array())
->add('tasks', 'collection', array(
'type' => new TaskType(),
'allow_add' => true,
'by_reference' => false,
'allow_delete' => true
))