Friday, September 07, 2012

Symfony2 custom query in entity field type and transform array to collection

This blog covers following questions:
how to
1, custom query in entitity field type
2, add custom field into entity form type
3, how to pass data to formBuilder
4, how to transform array to collection
5, how to set default choices in controller
6, how to get form data from request

questions are all related, to answer all the questions, lets setup a scenario.

we have category, product, tag and product_tags

the relationships are:
each category may have many products
each product only belongs to one category
each category may have many tags
each tag only belongs to one category
products may have many tags belonging to current category

So we have entities: Category, Product, Tag, ProductTags

When we do product crud, we want to be able to manage product tags in product form.

Then all problems popup.

1, add tags list in product form

in ProductType class

add tags field
        $category = $this->category;

        $builder->add('tags', 'entity', array(
                      'property_path' => false,
                      'multiple' => true,
                      'expanded' => true,
                      'class' => 'Bundle:Tag',
                      'query_builder' => function(Bundle\Repository\TagRepository $er) use ($category) {
                        return $er->createQueryBuilder('u')              
                          ->where('u.category = '. $category->getId());
                      }                    
                     ));


pay attention to:

'property_path' => false,  this means the field is not part of the product entity, without this, you'll get fatal error of no getter, no setter.

use ($category) , this is how you pass current category to the query_builder.

very well, you got the field, but where this $category from??? how do you pass data to formBuilder?

answer is

in your ProductType class, add following:


  protected $category;     //category object

 
    public function __construct($category)
    {
      $this->category = $category;
    }


in your controller:


        $category = $em->getRepository('Bundle:Category')->find($id);
     
        $entity = new Product();
       
        $form   = $this->createForm(new ProductType($category), $entity);

Now, on your new product form, you've got the tags list, next is handling the form submitted data.

$data = $request->request->get('product');


        if ($form->isValid()) {
     
         
            $em = $this->getDoctrine()->getEntityManager();
            $em->persist($entity);
            $em->flush();

            //save product tags
            if(!empty($data['tag']))
            {
                foreach ($data['tag'] as $value) {

                    $tag = $em->getRepository('Bundle:Tag')->find($value);
                   
                    $obj = new ProductTags();
                    $obj->setProduct($entity);
                    $obj->setTag($tag);
                    $em = $this->getDoctrine()->getEntityManager();
                    $em->persist($obj);
                    $em->flush();              
                }
            }



Almost there, when you edit the product, you need setup the default value for the tag list, here is how to do it:

this is how your transform array to collection

in your controller

use Doctrine\Common\Collections\ArrayCollection;

in your edit action


        $entities = $em->getRepository('Bundle:ProductTags')->findByProduct($product_entity);

        $choices = new ArrayCollection($entities);

        $editForm->get('tag')->setData($choices);

making sure pass category object when you create the product form, that's all.


More than 3 requests, I'll translate this to Chinese.
超过3个请求,我就会把这篇文章翻译成中文。

1 comment:

Damien Jarry said...

Great post :)

It works great with a many-to-one relation ; but with a many-to-many / many-to-many relation, with a intermediate table between article and tag, it persist only article and tag in the database, not the relation between them :/