Symfony3:ArrayCollection只添加了最后一项

时间:2016-05-09 13:27:28

标签: database forms entity symfony arraycollection

我有一个实体产品。我的产品可以有多个不同语言的名称。法语名称,英语名称等。我不想使用自动翻译。

用户必须在“产品”表单中写下名称并选择相应的语言。由于添加按钮,他可以根据需要添加许多名称。

所有语言都是由管理员用户创建的(另一种形式)。因此,语言也是一个具有名称(例如:英语)和代码(例如:EN)的实体。

因此,ProductType是我的主要表单ProductNameType是我的"集合"形式

当用户创建一个包含两个名称的新产品(一个是法语,另一个是英文)时,产品会保存在我的数据库中,另外两个名称也会创建并保存在另一个表中。

此时,一切运作良好。我的addAction()很好,产品和相应的名称都保存在数据库中。

但是,当我显示预先填写的表单时,我的 editAction()出现了问题。 我的收藏品阵列中只有最后添加的产品名称...

我不明白我做错了什么。名称在我的数据库中,也是产品,为什么我只得到ArrayCollection中的姓氏?

实体 Product.php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;

/**
 * @ORM\Table(name="modele")
 * @ORM\Entity(repositoryClass="ProductRepository")
 * @UniqueEntity(fields="code", message="Product code already exists")
 */
class Product
{
    /**
     * @ORM\Column(name="Modele_Code", type="string", length=15)
     * @ORM\Id
     * @Assert\NotBlank()
     * @Assert\Length(max=15, maxMessage="The code cannot be longer than {{ limit }} characters")
     */
    private $code;

    /**
     * @ORM\OneToMany(targetEntity="ProductNames", mappedBy="product", cascade={"persist", "remove"})
     */
    private $names;

    /**
     * Constructor
     */
    public function __construct()
    {
        $this->names = new ArrayCollection();
    }

    /**
     * Set code
     *
     * @param string $code
     *
     * @return Product
     */
    public function setCode($code)
    {
        $this->code = $code;

        return $this;
    }

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

    /**
     * Get names
     *
     * @return ArrayCollection
     */
    public function getNames()
    {
      return $this->names;
    }

    /**
     * Add names
     *
     * @param ProductNames $names
     *
     * @return Product
     */
    public function addName(ProductNames $names)
    {
        $names->setCode($this->getCode());
        $names->setProduct($this);

        if (!$this->getNames()->contains($names)) {
            $this->names->add($names);
        }

        return $this;
    }

    /**
     * Remove names
     *
     * @param ProductNames $names
     */
    public function removeName(ProductNames $names)
    {
        $this->names->removeElement($names);
    }
}

实体 ProductNames.php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;

/**
 * @ORM\Table(name="modele_lib")
 * @ORM\Entity(repositoryClass="ModelTextsRepository")
 * @UniqueEntity(fields={"code","language"}, message="A name in this language already exists for this product")
 */
class ProductNames
{
    /**
     * @ORM\Column(name="Modele_Code", type="string", length=15)
     * @ORM\Id
     */
    private $code;

    /**
     * @ORM\ManyToOne(targetEntity="Product", inversedBy="names")
     * @ORM\JoinColumn(name="Modele_Code", referencedColumnName="Modele_Code")
     */
    private $product;

    /**
     * @ORM\Column(name="Langue_Code", type="string", length=2)
     */
    private $language;

    /**
     * @ORM\Column(name="Modele_Libelle", type="string", length=50)
     * @Assert\NotBlank()
     */
    private $name;

    /**
     * Set code
     *
     * @param string $code
     *
     * @return ProductNames
     */
    public function setCode($code)
    {
        $this->code = $code;

        return $this;
    }

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

     /**
      * Set product
      *
      * @param Product $product
      *
      * @return ProductNames
      */
    public function setProduct(Model $product)
    {
        $this->product = $product;

        return $this;
    }

    /**
     * Get product
     *
     * @return Product
     */
    public function getProduct()
    {
        return $this->product;
    }

    /**
     * Set language
     *
     * @param string $language
     *
     * @return ProductNames
     */
    public function setLanguage($language)
    {
        $this->language = $language;

        return $this;
    }

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

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

        return $this;
    }

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

表格 ProductType.php

namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
// use Doctrine\ORM\EntityRepository;

class ProductType extends AbstractType
{

    public function buildForm(FormBuilderInterface $builder, array $options)
    {

        $recordId = $options['data']->getCode();    // product code

        // default options for names
        $namesOptions = array(
            'entry_type'   => ProductNamesType::class,
            'entry_options' => array('languages' => $options['languages']),
            'allow_add'     => true,
            'allow_delete'  => true,
            'prototype'     => true,
            'label'         => false,
            'by_reference' => false
        );

        // case edit product
        if (!empty($recordId)) {
            $namesOptions['entry_options']['edit'] = true;
        }

        $builder
            ->add('code',       TextType::class, array(
                'attr'              => array(
                    'size'          => 15,
                    'maxlength'     => 15,
                    'placeholder'   => 'Ex : LBSKIN'
                ),
            ))

            ->add('names',      CollectionType::class, $namesOptions)

            ->add('save',       SubmitType::class, array(
                'attr'          => array('class' => 'button-link save'),
                'label'         => 'Validate'
            )
        );

        // Edit case : add delete button
        if (!empty($recordId)) {
            $builder->add('delete', SubmitType::class, array(
                'attr'      => array('class' => 'button-link delete'),
                'label'     => 'Delete'
            ));
        }
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'AppBundle\Entity\Product',
            'languages'  => null
        ));
    }
}

表格 ProductNamesType.php

namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Ivory\CKEditorBundle\Form\Type\CKEditorType;
use Doctrine\ORM\EntityRepository;

class ProductNamesType extends AbstractType
{

    public function buildForm(FormBuilderInterface $builder, array $options)
    {

        // Language codes list
        $choices = array();
        foreach ($options['languages'] as $lang) {
            $code = $lang->getCode();
            $choices[$code] = $code;
        }

        $builder
            ->add('name',           TextType::class)

            ->add('language',       ChoiceType::class, array(
                'label'             => 'Language',
                'placeholder'       => '',
                'choices'           => $choices
            ))
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'AppBundle\Entity\ProductNames',
            'languages'  => null,
            'edit'       => false
        ));
    }

}

ProductController.php (请参阅editAction查找我的问题)。

如果我在表单提交后在$form->getData()中打印$product->getNames()addAction(),我会收到所有数据,一切都对我好。

namespace AppBundle\Controller;

use AppBundle\Form\ProductType;
use AppBundle\Entity\Product;
use AppBundle\Entity\ProductNames;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\Common\Collections\ArrayCollection;

class ProductController extends Controller
{

   /**
    * @Route("/products/add", name="product_add")
    */
    public function addAction(Request $request) {

      // build the form
      $em = $this->getDoctrine()->getManager();
      $languages = $em->getRepository('AppBundle:Language')->findAllOrderedByCode();


      $product = new Product();

      $form = $this->createForm(ProductType::class, $product, array(
            'languages' => $languages
        ));

      // handle the submit
      $form->handleRequest($request);
      if ($form->isSubmitted() && $form->isValid()) {

            // save the product
            $em->persist($product);

            foreach($product->getNames() as $names){
                $em->persist($names);
            }

            $em->flush();

            /*** here, everything is working ***/

            // success message
            $this->addFlash('notice', 'Product has been created successfully !');

            // redirection
            return $this->redirectToRoute('product');
      }

      // show form
      return $this->render('products/form.html.twig', array(
         'form' => $form->createView()
      ));
   }

   /**
    * @Route("/products/edit/{code}", name="product_edit")
    */
   public function editAction($code, Request $request) {

      // get product from database
      $em = $this->getDoctrine()->getManager();
      $product = $em->getRepository('AppBundle:Product')->find($code);
      $languages = $em->getRepository('AppBundle:Language')->findAllOrderedByCode();

      // product doesn't exist
      if (!$product) {
         throw $this->createNotFoundException('No product found for code '. $code);
      }

        $originalNames = new ArrayCollection();

        /*** My PROBLEM IS HERE ***/
        // $product->getNames() returns only one name : the last added
        foreach ($product->getNames() as $names) {
           $originalNames->add($names);
        }

        // My form shows only one "name block" with the last name added when the user created the product.

      // build the form with product data
      $form = $this->createForm(ProductType::class, $product, array(
            'languages' => $languages
        ));

      // form POST
      $form->handleRequest($request);
      if ($form->isSubmitted() && $form->isValid()) {

            // ...
      }

      // show form
      return $this->render('products/form.html.twig', array(
         'form'      => $form->createView(),
         'product_code' => $code
      ));
   }
}

1 个答案:

答案 0 :(得分:0)

问题可能在于您的ProductNames实体。您已将code标记为主键(使用@ORM\Id),产品会将多个 ProductNames 标记为code作为主键,因为主键需要是唯一的。我建议通过将@ORM\Id注释添加到langauge来使用复合主键。

class ProductNames
{
    /**
     * @ORM\Column(name="Modele_Code", type="string", length=15)
     * @ORM\Id
     */
    private $code;

    /**
     * @ORM\Column(name="Langue_Code", type="string", length=2)
     * @ORM\Id
     */
    private $language;

    // ...
}

您必须更新/重新创建数据库才能使复合键生效。

希望这有帮助。