Symfony3:ArrayCollection 只添加了最后一项

Symfony3 : ArrayCollection has only the last item added

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

用户必须在产品表单中填写名称和select相应的语言。多亏了“添加”按钮,他可以添加任意多个名字。

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

所以,ProductType是我的主要形式ProductNameType是我的"collection"形式.

例如,当用户创建具有 2 个名称的新产品(一个是法语,另一个是英语)时,该产品将保存在我的数据库中,并且还会创建 2 个名称并将其保存在另一个 table 中。

至此,一切正常。我的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 找到我的问题)。

如果我在表单提交后在 addAction() 中打印 $form->getData()$product->getNames(),我得到了所有数据,对我来说一切正常。

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
      ));
   }
}

问题可能出在您的 ProductNames 实体上。您已将 code 标记为主键(使用 @ORM\Id),一个 Product 将有多个 ProductNames,它们都不能将 code 作为主键,因为主键需要是唯一的。我建议通过向 langauge 添加 @ORM\Id 注释来使用复合主键。

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;

    // ...
}

您必须 update/re-create 您的数据库才能使复合键生效。

希望对您有所帮助。