I
I
Ivan Pashin2016-08-28 18:00:37
Doctrine ORM
Ivan Pashin, 2016-08-28 18:00:37

How to dynamically add related models in Zend Framework 2?

I used the following example for implementation:
I have an Entity for a page and for tags for pages:
MyModule\Entity\Page
MyModule\Entity\Tag
N tags can be specified for one page.
The add page page fails to dynamically add tags for the page. Tell me what I'm doing wrong.
Here is a listing of the main files:
Page Model:

namespace MyModule\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

/**
 * Page
 *
 * @ORM\Table(name="page", indexes={@ORM\Index(name="fk_page_category", columns={"category_id"})})
 * @ORM\Entity
 */
class Page
{
    /**
     * @var integer
     * @ORM\Column(name="id", type="integer", nullable=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

    /**
     * @var string
     * @ORM\Column(name="name", type="string", length=255, nullable=false)
     */
    private $name;

    /**
     * @var \MyModule\Entity\Category
     * @ORM\ManyToOne(targetEntity="MyModule\Entity\Category")
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(name="category_id", referencedColumnName="id")
     * })
     */
    private $category;

    /**
     * @ORM\OneToMany(targetEntity="MyModule\Entity\Tag", mappedBy="page", cascade={"persist"})
     */
    protected $tags;

    /**
     * Never forget to initialize all your collections !
     */
    public function __construct()
    {
        $this->tags = new ArrayCollection();
    }

    /**
     * @param Collection $tags
     */
    public function addTags(Collection $tags)
    {
        foreach ($tags as $tag) {
            $tag->setPage($this);
            $this->tags->add($tag);
        }
    }

    /**
     * @param Collection $tags
     */
    public function removeTags(Collection $tags)
    {
        foreach ($tags as $tag) {
            $tag->setPage(null);
            $this->tags->removeElement($tag);
        }
    }

    /**
     * @return Collection
     */
    public function getTags()
    {
        return $this->tasg;
    }

    /**
     * Get category_id.
     * @return string
     */
    public function getCategory()
    {
        return $this->category;
    }
    /**
     * Set category_id.
     * @param Category $category
     * @return Category
     */
    public function setCategory($category)
    {
        $this->category = $category;
        return $this;
    }
}

Tag Model:
namespace MyModule\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Tag
 * @ORM\Table(name="tag", indexes={@ORM\Index(name="fk_tag_page", columns={"page_id"})})
 * @ORM\Entity
 */
class Tag
{

    private $id;

    private $name;

    /**
     * @var \MyModule\Entity\Page
     * @ORM\ManyToOne(targetEntity="MyModule\Entity\Page")
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(name="page_id", referencedColumnName="id")
     * })
     */
    private $page;

    /**
     * Allow null to remove association
     * @param Page $page
     */
    public function setPage(Page $page = null)
    {
        $this->page = $page;
    }

    /**
     * @return Page
     */
    public function getPage()
    {
        return $this->page;
    }
}

MyModule\Form\PageFieldset:
namespace MyModule\Form;

use MyModule\Entity\Page;
use Doctrine\Common\Persistence\ObjectManager;
use DoctrineModule\Stdlib\Hydrator\DoctrineObject as DoctrineHydrator;
use Zend\Form\Fieldset;
use Zend\InputFilter\InputFilterProviderInterface;

class PageFieldset extends Fieldset implements InputFilterProviderInterface
{
    public function __construct(ObjectManager $objectManager)
    {
        parent::__construct('page');

        $this->setHydrator(new DoctrineHydrator($objectManager))
            ->setObject(new Page());

        $this->add(array(
            'type' => 'Zend\Form\Element\Text',
            'name' => 'name'
        ));

        $tagsFieldset = new TagFieldset($objectManager);
        $this->add(array(
            'type'    => 'Zend\Form\Element\Collection',
            'name'    => 'tags',
            'options' => array(
                'count'           => 2,
                'target_element' => $tagFieldset
            )
        ));
    }

    public function getInputFilterSpecification()
    {
        return array(
            'name' => array(
                'required' => true
            ),
        );
    }
}

MyModule\Form\TagFieldset:
namespace MyModule\Form;

use MyModule\Entity\Tag;
use Doctrine\Common\Persistence\ObjectManager;
use DoctrineModule\Stdlib\Hydrator\DoctrineObject as DoctrineHydrator;
use Zend\Form\Fieldset;
use Zend\InputFilter\InputFilterProviderInterface;

class TagFieldset extends Fieldset implements InputFilterProviderInterface
{
    public function __construct(ObjectManager $objectManager)
    {
        parent::__construct('tag');

        $this->setHydrator(new DoctrineHydrator($objectManager))
            ->setObject(new Tag());

        $this->add(array(
            'type' => 'Zend\Form\Element\Hidden',
            'name' => 'id'
        ));

        $this->add(array(
            'type'    => 'Zend\Form\Element\Text',
            'name'    => 'name',
            'options' => array(
                'label' => 'Tag'
            )
        ));
    }

    public function getInputFilterSpecification()
    {
        return array(
            'id' => array(
                'required' => false
            ),

            'name' => array(
                'required' => true
            )
        );
    }
}

MyModule\Form\PageForm:
namespace MyModule\Form;

use Doctrine\Common\Persistence\ObjectManager;
use DoctrineModule\Stdlib\Hydrator\DoctrineObject as DoctrineHydrator;
use Zend\Form\Form;

class PageForm extends Form
{

    protected $objectManager;

    /**
     * Set the object manager
     *
     * @param ObjectManager $objectManager
     */
    public function setObjectManager(ObjectManager $objectManager)
    {
        $this->objectManager = $objectManager;
    }

    /**
     * Get the object manager
     *
     * @return ObjectManager
     */
    public function getObjectManager()
    {
        return $this->objectManager;
    }

    public function __construct(ObjectManager $objectManager)
    {
        parent::__construct('page-form');

        $this->setObjectManager($objectManager);

        // The form will hydrate an object of type "Page"
        $this->setHydrator(new DoctrineHydrator($objectManager));

        // Add the user fieldset, and set it as the base fieldset
        $pageFieldset = new PageFieldset($objectManager);
        $pageFieldset->setUseAsBaseFieldset(true);
        $this->add($pageFieldset);

        $this->createElements();
    }

    public function createElements()
    {
        $this->setAttribute('method', 'post');

        $this->add(array(
            'name' => 'security',
            'type' => 'Zend\Form\Element\Csrf',
        ));

        $this->add(array(
            'type' => 'DoctrineModule\Form\Element\ObjectSelect',
            'name' => 'category',
            'options' => array(
                'label' => 'Category',
                'empty_option' => 'Select platform ...',
                'object_manager' => $this->getObjectManager(),
                'target_class' => 'MyModule\Entity\Category',
                'property' => 'name'
            ),
            'attributes' => array(
                'required' => 'required'
            )
        ));

        $this->add(array(
            'name' => 'name',
            'type' => 'Text',
            'options' => array(
                'label' => 'Name'
            ),
            'attributes' => array(
                'required' => 'required'
            )
        ));

        $this->add(array(
            'name' => 'submit',
            'type' => 'Submit',
            'attributes' => array(
                'value' => 'Save'
            )
        ));
    }
}

And finally the add.twig view:
{% extends 'layout/layout.twig' %}

{% block content %}

<h1>Add Page</h1>
<div>

    {{ form().prepare() }}
    {{ form().openTag(form)|raw }}

    {% for element in form %}

        {% set attributes = element.getAttributes() %}

        {% if type != 'hidden' and type != 'submit' %}
            <div class="field">
        {% endif %}

        {% if attributes.type is defined %}
            {% set type = attributes.type %}
        {% else %}
            {% set type = '' %}
        {% endif %}

        {% if type != 'hidden' and type != 'submit' %}
            <label for="{{ element.getName() }}">{{ element.getLabel() }}: </label>
            {% if attributes.required is defined %}
                <abbr title="Обязательное поле">*</abbr>
            {% endif %}
        {% endif %}


        {% if type == 'text' %}
            {{ formInput(form.get( element.name ))|raw }}
        {% elseif type == 'textarea' %}
            {{ formTextarea(form.get( element.name ))|raw }}
        {% elseif type == 'select' %}
            {{ formSelect(form.get( element.name ))|raw }}
        {% elseif type != 'hidden' and type != 'submit' %}
            {{ formRow().setInputErrorClass('error').render(form.get( element.name ))|raw }}
        {% endif %}

        {% if type != 'hidden' and type != 'submit' %}
            </div>
        {% endif %}

    {% endfor %}

    {{ formCollection(form.get( 'page' ).get( 'tags' ))|raw }}

    {{ formRow(form.get('submit'))|raw }}
    {{ form().closeTag(form)|raw }}

</div>

{% endblock content %}

Tell me what needs to be corrected so that when creating a Page, you can dynamically add as many Tag as you like?

Answer the question

In order to leave comments, you need to log in

2 answer(s)
I
Ivan Pashin, 2016-08-29
@IvanFantoM

Great illustration of adding entities dynamically:
https://github.com/arvind2110/ZF2-Collection-Form

S
Sergey, 2016-08-28
Protko @Fesor

let's think. Does the page need to know about its tags? Yes, it would be cool, but what if the tags need to be added to something else? Or, for example, cases when some tags need to be created and some just need to be connected.
So, we remove the tag connection from the Page and leave the unidirectional association to the pages of the tags. Some service (Tagger) makes tags for us with methods ala assignTags($page, $tags) and getTagsOf($page)
Yes, this is more difficult, but you can safely reuse in the future, it's easier to maintain and generally kosher.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question