phpdoctrine-ormzend-framework2zend-formzend-form-element

Populating Relationship data in Zend Framework 2 Forms via Annotation Builder


Currently, I have article and tag tables. I am trying to auto populate the "Tag" form element as a select box on the article form. What is the best way to go about setting the Value Options of the tags select box from a database table and then also have the article bind the tag data automatically during the "bind" method call?

Article.php

<?php
// Article class
class Article {
    /**
     * 
     * @var \Doctrine\Common\Collections\Collection|Tag[]
     * 
     * @ORM\ManyToMany(targetEntity="Tag", inversedBy="articles")
     * @Orm\JoinTable(name="rel_article_tag", joinColumns={@ORM\JoinColumn(name="article_id", referencedColumnName="article_id")}, inverseJoinColumns={@ORM\JoinColumn(name="tag_id", referencedColumnName="tag_id")})
     * 
     * @Form\Required(false)
     * @Form\Type("Zend\Form\Element\Select")
     * @Form\Options({"label":"Tags: ")
     * @Form\Attributes({"id":"tags", "data-placeholder":"Choose tags...", "multiple" : "multiple", "class" : "chosen-select"})
     */
    private $tags;

    public function __construct()
    {
        $this->tags = new ArrayCollection();
    }

    public function getTags()
    {
        return $this->tags;
    }

    public function addTags($tags)
    {
        $this->tags = $tags;
    }

    public function removeTags()
    {
        $this->tags = new ArrayCollection();
    }
}

ArticleController.php

class ArticleController{
    public function editAction()
    {
        $builder = new AnnotationBuilder();
        $form = $builder->createForm(new TblArticle());

        $id = 1;
        $article = $em->find('Admin\Entity\TblArticle', $id);
        $form->bind($article);
    }
}

WHAT I'VE DONE

Within ArticleController::editAction(), I've dynamically added the value options to the tags element on the form.

class ArticleController
{
    public function editAction()
    {
        $builder = new AnnotationBuilder();
        $form = $builder->createForm(new TblArticle());

        // add tag options to form
        $sm = $this->getServiceLocator();
        $em = $sm->get('Doctrine\ORM\EntityManager');
        $tags = $em->getRepository('Admin\Entity\LuTag')->findAll();
        $tagOptions = [null => ''];
        foreach ($tags as $tag) {
            $tagOptions[$tag->getTagId()] = $tag->getName();
        }
        $form->get('tags')->setValueOptions($tagOptions);
        // end add tag options to form


        $id = 1;
        $article = $em->find('Admin\Entity\TblArticle', $id);
        $form->bind($article);

        if ($article->getTags()) {
            $tagIds = array();
            foreach ($article->getTags() as $tag) {
                $tagIds[] = $tag->getTagId();
            }
            $form->get('tags')->setValue($tagIds);
        }
    }
}

This seems like an excessive amount of code in my controller, I know it's not right, but I'm not sure how to better do this. Possibly using a FormBuilder that sets the value options for the Tag elements?

Thanks.


Solution

  • Check out this tutorial: https://samsonasik.wordpress.com/2014/05/22/zend-framework-2-using-doctrinemoduleformelementobjectselect-and-custom-repository/

    Basically you need to specify a repository class in your Tag-Entities Entity Annotation like this:

    @ORM\Entity(repositoryClass="Admin\Entity\LuTag")
    

    Then you can use Doctrines DoctrineModule\Form\Element\ObjectSelect Type which will be able to provide the feature you requested:

    Article.php (Note the @Form\Type Annotation and the additional @Form\Options entries)

    ...
    /**
     * 
     * @var \Doctrine\Common\Collections\Collection|Tag[]
     * 
     * @ORM\ManyToMany(targetEntity="Tag", inversedBy="articles")
     * @Orm\JoinTable(name="rel_article_tag", joinColumns={@ORM\JoinColumn(name="article_id", referencedColumnName="article_id")}, inverseJoinColumns={@ORM\JoinColumn(name="tag_id", referencedColumnName="tag_id")})
     * 
     * @Form\Required(false)
     * @Form\Type("DoctrineModule\Form\Element\ObjectSelect")*
     * @Form\Options({"label":"Tags: ", "target_class": "Admin\Entity\LuTag", "property": "name"})
     * @Form\Attributes({"id":"tags", "data-placeholder":"Choose tags...", "multiple" : "multiple", "class" : "chosen-select"})
     */
    private $tags;
    

    Also check out https://github.com/doctrine/DoctrineModule/blob/master/docs/form-element.md for more information about ObjectSelect

    At last you will need to build your form using

    DoctrineORMModule\Form\Annotation\AnnotationBuilder
    

    instead of Zends AnnotationBuilder in order to resolve the object_manager dependencies.

    /* using the service manager like this within a controller method is 
    bad practice. Inject the EntityManager using a Controller Factory! */
    $sm = $this->getServiceLocator();
    $em = $sm->get('Doctrine\ORM\EntityManager');
    
    $builder = new DoctrineORMModule\Form\Annotation\AnnotationBuilder($em);
    $form = $builder->createForm(TblArticle::class);
    

    This should do the trick.