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 您的数据库才能使复合键生效。
希望对您有所帮助。
我有一个实体产品。我的产品可以有多个不同语言的名称。法文名,英文名等。我不想使用自动翻译。
用户必须在产品表单中填写名称和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 您的数据库才能使复合键生效。
希望对您有所帮助。