Symfony2 Form Collection not saved into the databa

2019-09-18 16:06发布

I am building an application with an Article entity. Every article can have more Attribute (and one attribute can have many articles reffering to it).

To manage the connection I am using link entity AttributeLink, so the connection is one-to-many-to-one. The link entity has additional field to store the attribute value.

I have built a form with collection that works well. In fact, the solution worked fine and saved all the values correctly. When I went to style the form to look better, the solution stopped working (and that is quite strange to me, because I did no changes to the logic, just tuned up the twig).

Now the Article is saved to the database, but no new row is created in the linked entity database table.

I went over and over and over my solution, checking it against documentation and all seems to me to be right in the correct place. I am probably missing something small.

Based on the changes I have done and on the behaviour (nothing is saved in the databse table) the only problem I can think of is, that the collection does not pass the data correctly for persistance.

Please advice.

ArticleController.php

$form = $this->createFormBuilder($article)
            ->add('name', TextType::class, array('label' => 'Název'))
            ->add('date', DateType::class, array(
                'label' => false, 
                'format' => 'dd.MM.yyyy', 
                'data' => new \DateTime(),
                'widget' => 'single_text',
                'html5' => false,
                'attr' => ['class' => 'form-control_form_input input_half']
                ))
            ->add('quantity_stored', IntegerType::class, array('label' => 'Úvodní množství', 'data' => 0))
            ->add('category', EntityType::class, array('label' => 'Kategorie', 'choice_label' => 'name', 'expanded' => true, 'multiple' => true, 'class' => 'ArticleBundle:Category', 'choices' => $category))
            ->add('state', EntityType::class, array('label' => 'Stav', 'choice_label' => 'name', 'class' => 'ArticleBundle:State'))
            ->add('warehouse', EntityType::class, array('label' => 'Sklad', 'choice_label' => 'name', 'class' => 'CompanyBundle:Warehouse'))
            ->add('user', EntityType::class, array('label' => 'Zodpovědný uživatel', 'choice_label' => 'email', 'class' =>'LoginBundle:User'))
            ->add('attribute', CollectionType::class, array(
                    'entry_type' => AttributeLinkType::class, 
                    'label' => false,
                    'allow_add' => true,
                    'allow_delete' => true,
                    'by_reference' => false,
                    'entry_options' => array('attr' => array('company_id' => $this->session->get('active_company')))
                ))
            ->add('description', TextareaType::class, array('label' => 'Popis', 'required' => false, 'empty_data'  => ' '))
            ->add('code', HiddenType::class, array('data' => $code))
            ->add('company', EntityType::class, array('class' => 'CompanyBundle:Company', 'choice_label' => 'id', 'choices' => array($company)))
            ->add('save', SubmitType::class, array('label' => 'Uložit'))
            ->getForm();

AttributeLinkType.php

class AttributeLinkType extends AbstractType
{
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $this->company_id = $options['attr']['company_id'];

        $builder
            ->add('attribute', EntityType::class, 
                    array(
                        'label' => false, 
                        'class' => 'ArticleBundle:Attribute', 
                        'query_builder' => function (EntityRepository $er) {
                            return $er->createQueryBuilder('a')
                            ->join('a.company', 'c')
                            ->where('c.id = '.$this->company_id)
                            ->orderBy('a.name', 'ASC');
                        }
                    )
                )            
            ->add('value', TextType::class, array('label' => false));
    }

    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => AttributeLink::class,
        ));
    }
}

Article.php

     class Article
    {            
        /**
         * Many Article has Many Attribute via link entity
         * 
         * @ORM\OneToMany(targetEntity="AttributeLink", mappedBy="article", cascade={"persist"})
         */
        private $attribute;

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

        public function __toString() {
            return $this->name;
        }

        /**
         * Add attribute
         *
         * @param \AuthenticatedUser\ContentManagement\ArticleBundle\Entity\AttributeLink $attribute
         * @return Article
         */
        public function addAttribute(\AuthenticatedUser\ContentManagement\ArticleBundle\Entity\AttributeLink $attribute)
        {   
            $attribute->setArticle($this);

    $this->attribute->add($attribute);

            $this->attribute[] = $attribute;

            return $this;
        }

        /**
         * Remove attribute
         *
         * @param \AuthenticatedUser\ContentManagement\ArticleBundle\Entity\AttributeLink $attribute
         */
        public function removeAttribute(\AuthenticatedUser\ContentManagement\ArticleBundle\Entity\AttributeLink $attribute)
        {        
            $this->attribute->removeElement($attribute);
        }

        /**
         * Get attribute
         *
         * @return \Doctrine\Common\Collections\Collection 
         */
        public function getAttribute()
        {
            return $this->attribute;
        }
    }

AttributeLink.php

class AttributeLink
{
/**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * Attribute value
     * 
     * @ORM\Column(type="string", length=100)
     */
    protected $value;

    /**
     * Many Article has Many Attribute via link entity
     * 
     * @ORM\ManyToOne(targetEntity="Article", inversedBy="attribute")
     * @ORM\JoinColumn(name="article_id", referencedColumnName="id")
     */
    private $article;

    /**
     * Many Article has Many Attribute via link entity
     * 
     * @ORM\ManyToOne(targetEntity="Attribute", inversedBy="article")
     * @ORM\JoinColumn(name="attribute_id", referencedColumnName="id")
     */
    private $attribute;

    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set value
     *
     * @param string $value
     * @return AttributeLink
     */
    public function setValue($value)
    {
        $this->value = $value;

        return $this;
    }

    /**
     * Get value
     *
     * @return string 
     */
    public function getValue()
    {
        return $this->value;
    }

    /**
     * Set article
     *
     * @param \AuthenticatedUser\ContentManagement\ArticleBundle\Entity\Article $article
     * @return AttributeLink
     */
    public function setArticle(\AuthenticatedUser\ContentManagement\ArticleBundle\Entity\Article $article = null)
    {        
        $this->article = $article;

        return $this;
    }

    /**
     * Get article
     *
     * @return \AuthenticatedUser\ContentManagement\ArticleBundle\Entity\Article 
     */
    public function getArticle()
    {
        return $this->article;
    }

    /**
     * Set attribute
     *
     * @param \AuthenticatedUser\ContentManagement\ArticleBundle\Entity\Attribute $attribute
     * @return AttributeLink
     */
    public function setAttribute(\AuthenticatedUser\ContentManagement\ArticleBundle\Entity\Attribute $attribute = null)
    {                
        $this->attribute = $attribute;

        return $this;
    }

    /**
     * Get attribute
     *
     * @return \AuthenticatedUser\ContentManagement\ArticleBundle\Entity\Attribute 
     */
    public function getAttribute()
    {
        return $this->attribute;
    }
}

Attribute.php

class Attribute
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;


    /**
     * Attribute name
     * 
     * @ORM\Column(type="string", length=100)
     */
    protected $name;

    /**
     * Many Attributes have One Company
     * 
     * @ORM\ManyToOne(targetEntity="AuthenticatedUser\System\CompanyBundle\Entity\Company", inversedBy="article_attribute")
     * @ORM\JoinColumn(name="company_id", referencedColumnName="id")
     */
    private $company;

    /**
     * Many Article has Many Attribute via link entity
     * 
     * @ORM\OneToMany(targetEntity="AttributeLink", mappedBy="attribute", cascade={"persist"})
     */
    private $article;

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

    public function __toString() {
        return $this->name;
    }

    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set name
     *
     * @param string $name
     * @return Attribute
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string 
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Set company
     *
     * @param \AuthenticatedUser\System\CompanyBundle\Entity\Company $company
     * @return Attribute
     */
    public function setCompany(\AuthenticatedUser\System\CompanyBundle\Entity\Company $company = null)
    {
        $this->company = $company;

        return $this;
    }

    /**
     * Get company
     *
     * @return \AuthenticatedUser\System\CompanyBundle\Entity\Company 
     */
    public function getCompany()
    {
        return $this->company;
    }

    /**
     * Add article
     *
     * @param \AuthenticatedUser\ContentManagement\ArticleBundle\Entity\AttributeLink $article
     * @return Attribute
     */
    public function addArticle(\AuthenticatedUser\ContentManagement\ArticleBundle\Entity\AttributeLink $article)
    {                
        $this->article[] = $article;

        return $this;
    }

    /**
     * Remove article
     *
     * @param \AuthenticatedUser\ContentManagement\ArticleBundle\Entity\AttributeLink $article
     */
    public function removeArticle(\AuthenticatedUser\ContentManagement\ArticleBundle\Entity\AttributeLink $article)
    {
        $this->article->removeElement($article);
    }

    /**
     * Get article
     *
     * @return \Doctrine\Common\Collections\Collection 
     */
    public function getArticle()
    {
        return $this->article;
    }
}

page_content.html.twig collection section

<div id="attribute_outer" class="col-xs-12 col-sm-12 col-lg-6 col-md-12 masonry-container page_list form_attributes_box">
        <div id="attribute_inner" class="card-box_attributes">
            <label class="form_attributes_list_label">Vlastnosti produktu</label>
            <div class="attributes" data-prototype="
                 {% filter escape %}
                    {{ include('ArticleBundle:Article/New:attribute.html.twig', { 'form': form.attribute.vars.prototype}) }}
                 {% endfilter %}   
                 ">
            </div>
        </div>
    </div>

attribute.html.twig

<div class="{{ form.attribute.vars.id }} form_attribute_row">
    {{ form_widget(form.attribute, { 'attr': {'class': 'form-control_form_input'} }) }}&nbsp;&nbsp;&nbsp;
    {{ form_widget(form.value, { 'attr': {'class': 'form-control_form_input'} }) }}&nbsp;&nbsp;&nbsp;
</div>

page_content_scripts.html.twig collections script

var $collectionHolder;

        // setup an "add an attribute" link
        var $addTagLink = $('<a href="#" class="btn btn-primary">Přidat vlastnost</a>');
        var $newLinkLi = $('<div></div>').append($addTagLink);
        var rmIndex = 0;

        jQuery(document).ready(function() {
            // Get the ul that holds the collection of tags
            $collectionHolder = $('div.attributes');

            // add a delete link to all of the existing tag form li elements
            $collectionHolder.find('div').each(function() {
                addTagFormDeleteLink($(this));
            });

            // add the "add a tag" anchor and li to the tags ul
            $collectionHolder.append($newLinkLi);

            // count the current form inputs we have (e.g. 2), use that as the new
            // index when inserting a new item (e.g. 2)
            $collectionHolder.data('index', $collectionHolder.find(':input').length);

            $addTagLink.on('click', function(e) {
                // prevent the link from creating a "#" on the URL
                e.preventDefault();

                // add a new tag form (see next code block)
                addTagForm($collectionHolder, $newLinkLi);

                $('#attribute_outer').animate({height: '+=46'}, 1);
                $('#attribute_inner').animate({height: '+=46'}, 1);
            });

            // add a new tag form (see next code block)
            addTagForm($collectionHolder, $newLinkLi);
        });

        function addTagForm($collectionHolder, $newLinkLi) {
            // Get the data-prototype explained earlier
            var prototype = $collectionHolder.data('prototype');

            // get the new index
            var index = $collectionHolder.data('index');

            // Replace '__name__' in the prototype's HTML to
            // instead be a number based on how many items we have
            var newForm = prototype.replace(/__name__/g, index);

            // increase the index with one for the next item
            $collectionHolder.data('index', index + 1);

            // Display the form in the page in an li, before the "Add a tag" link li
            var $newFormLi = $('<div class="col-xs-12 col-sm-12 col-lg-12 col-md-12"></div>').append(newForm);
            $newLinkLi.before($newFormLi);

            // add a delete link to the new form
            addTagFormDeleteLink($newFormLi);
        }

        function addTagFormDeleteLink($tagFormLi) {
            var $removeFormA = $('<button class="btn btn-danger btn-custom" style="margin-bottom: 4px;"><i class="ion-close-round"></i></button>');

            $($removeFormA).appendTo(".form_attribute_" + rmIndex + "_attribute");
            //$tagFormLi.append($removeFormA);

            rmIndex++;

            $removeFormA.on('click', function(e) {
                // prevent the link from creating a "#" on the URL
                e.preventDefault();

                // remove the li for the tag form
                $tagFormLi.remove();

                $('#attribute_outer').animate({height: '-=46'}, 500);
                $('#attribute_inner').animate({height: '-=46'}, 500);
            });
        }

generated html output

<div class="form_attribute_0_attribute form_attribute_row">
    <select id="form_attribute_0_attribute" name="form[attribute][0][attribute]" class="form-control_form_input"><option value="1">Barva</option><option value="2">Hmotnost</option></select>&nbsp;&nbsp;&nbsp;
    <input id="form_attribute_0_value" name="form[attribute][0][value]" required="required" class="form-control_form_input" type="text">&nbsp;&nbsp;&nbsp;
<button class="btn btn-danger btn-custom" style="margin-bottom: 4px;"><i class="ion-close-round"></i></button>

generated prototype

data-prototype='<div class="form_attribute___name___attribute form_attribute_row">
    <select id="form_attribute___name___attribute" name="form[attribute][__name__][attribute]" class="form-control_form_input"><option value="1">Barva</option><option value="2">Hmotnost</option></select>&nbsp;&nbsp;&nbsp;
    <input type="text" id="form_attribute___name___value" name="form[attribute][__name__][value]" required="required" class="form-control_form_input" />&nbsp;&nbsp;&nbsp;
</div>

' EDIT I enhanced my controller with the following missing lines: $attribute = new AttributeLink(); $article->addAttribute($attribute);

Now there are some additional attribute fields generated by the form and only last entry is saved ... loosing my mind here

0条回答
登录 后发表回答