Jotaeme
Jotaeme

Reputation: 365

How to Embed a Collection of Forms in Symfony?

I am having some issues when trying to create a form that embeds a collection of another entity.

I want to create a Recipe and for that purpose I want the user to be able to insert several Tags for that Recipe.

I've followed the current Symfony documentation and I am actually able to insert both entities Recipe and Tag, but for Tag entity, only id is actually inserted.

Here is what I've done so far:

RecipeType form:

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('name')
        ->add('recipe_tags', CollectionType::class, array(
            'entry_type' => TagsType::class,
            'allow_add' => true,
            'by_reference' => false,
        ));

}

Which adds the collationType TagsType form that it's coded as follows:

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

Then I create a tag object in the controller beforehand so the recipe entity has a default tag that will be updated after submit the form.

    $recipe = new Recipe();
    $tag = new Tags();
    $tag->setName('tag1');
    $recipe->addRecipeTag($tag);
    $form = $this->get('form.factory')->createNamed(null, RecipeType::class, 
    $recipe);

And finally, the Recipe Entity with the (I think so) proper information:

/**
 * @var \Doctrine\Common\Collections\Collection
 *
 * @ORM\ManyToMany(targetEntity="Tags", inversedBy="idRecipe", cascade=
 {"persist"})
 * @ORM\JoinTable(name="recipe_tags",
 *   joinColumns={
 *     @ORM\JoinColumn(name="id_recipe", referencedColumnName="id_recipe")
 *   },
 *   inverseJoinColumns={
 *     @ORM\JoinColumn(name="id_tag", referencedColumnName="id_tag")
 *   }
 * )
 * @Groups({"recipe_detail"})
 *
 */
private $recipe_tags;

I added the cascade persist annotation following the documentation as well. Here is the method for adding tags.

/**
 * Add recipeTag
 *
 * @param \AppBundle\Entity\Tags $recipeTag
 *
 * @return Recipe
 */
public function addRecipeTag(\AppBundle\Entity\Tags $recipeTag)
{
    $this->recipe_tags[] = $recipeTag;

    return $this;
}

And this is what I get inserted into my database (is the return after the POST request):

 {
"recipe": {
"idRecipe": 3671,
"name": "TagName#76232",
"description": null,
"duration": null,
"updatedAt": null,
"createdAt": null,
"active": null,
"image": null,
"portions": null,
"idCategory": null,
"createdBy": null,
"recipeTags": [
  {
    "idTag": 143,
    "name": null
  }
],
"recipeSteps": [],
"recipeIngredients": null

} }

As you can see, the recipe is corretly added, but no name is added for the tag I just created. Plus,the pivot table recipe_tags does not add data at all.

What am I doing wrong?

EDIT:

I think the problem was that I was using the same form name field for both entities. Now I've changed the field and a new error appears:

{
  "code": 400,
  "message": "Validation Failed",
  "errors": {
  "errors": [
    "This value is not valid."
   ],
  "children": {
    "name": [],
    "recipe_tags": {
      "children": [
        {
          "children": {
            "tag": []
        }
      }
    ]
  }
}

} }

And this is the parameters I am passing with the POST request:

{"name":"Recipe#2342"}
{"recipe_tags":"TagName#76232"}

Any ideas? Thank you.

Upvotes: 1

Views: 3532

Answers (2)

Jotaeme
Jotaeme

Reputation: 365

I finally solved the problem. I wasn't passing correctly the POST parameters. This is how it should be done in case the form is for an restful api:

{
    "recipe_form[name]":"recipeName",
    "recipe_form[recipe_tags][0][tag]":"tag1",
    "recipe_form[recipe_tags][1][tag]":"tag2",
    "recipe_form[recipe_tags][2][tag]":"tag3"
}

..and so on. I alse named the form which made me easier to understand how it works. Also it is not necessary to create tags and then pass them to the form, so I can create as many tags as a want.

This is the controller:

    $recipe = new Recipe();
    $form = $this->get('form.factory')->
                createNamed('recipe_form', RecipeType::class, $recipe);
    $form->handleRequest($request);

    if ($form->isValid() && $form->isSubmitted()) {
        $em = $this->getDoctrine()->getManager();
        $em->persist($recipe);
        $em->flush();
        return array("recipe" => $recipe);
    }

    return array(
        'form' => $form,
    );

And then, the formType classes:

public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
    ->add('name')
    ->add('recipe_tags', CollectionType::class, array(
        'entry_type' => TagsType::class,
        'allow_add' => true,
        'by_reference' => false,
    ));

 }

 public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder->add('tag');
}

Cheers.

Upvotes: 1

ste
ste

Reputation: 1539

With a ManyToMany you should simply add an EntityType field just with multiple=true in RecipeType and you are done, unless you want to also create new tags.

Upvotes: 1

Related Questions