我有一个实体Product。我的产品可以有不同语言的多个名称。一个法语名字,一个英语名字,等等。我不想用自动翻译。
用户必须在产品表单中填写名称并选择相应的语言。多亏了"添加"按钮,他可以添加任意多的名字。
所有语言都由管理员用户创建(以另一种形式)。因此,语言也是一个实体,它有一个名称(例如:英语)和一个代码(例如:EN)。
因此,ProductType
是我的主形式,ProductNameType
是我的"集合"形式
例如,当用户创建了一个有两个名称的新产品(一个是法语,另一个是英语)时,该产品会保存在我的数据库中,并且还会创建两个名称并保存在另一个表中。
在这一点上,一切都很顺利。我的addAction()
很好,产品和相应的名称都保存在数据库中。
但是,当我显示预先填写的表格时,我的editAction()
出现问题我的集合数组中只存在最后添加的产品名称
我不明白我做错了什么。这些名字在我的数据库中,产品也在,那么为什么我在ArrayCollection中只得到姓氏呢?
实体Product.php
namespace AppBundleEntity;
use DoctrineORMMapping as ORM;
use DoctrineCommonCollectionsArrayCollection;
use SymfonyComponentValidatorConstraints as Assert;
use SymfonyBridgeDoctrineValidatorConstraintsUniqueEntity;
/**
* @ORMTable(name="modele")
* @ORMEntity(repositoryClass="ProductRepository")
* @UniqueEntity(fields="code", message="Product code already exists")
*/
class Product
{
/**
* @ORMColumn(name="Modele_Code", type="string", length=15)
* @ORMId
* @AssertNotBlank()
* @AssertLength(max=15, maxMessage="The code cannot be longer than {{ limit }} characters")
*/
private $code;
/**
* @ORMOneToMany(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 AppBundleEntity;
use DoctrineORMMapping as ORM;
use SymfonyComponentValidatorConstraints as Assert;
use SymfonyBridgeDoctrineValidatorConstraintsUniqueEntity;
/**
* @ORMTable(name="modele_lib")
* @ORMEntity(repositoryClass="ModelTextsRepository")
* @UniqueEntity(fields={"code","language"}, message="A name in this language already exists for this product")
*/
class ProductNames
{
/**
* @ORMColumn(name="Modele_Code", type="string", length=15)
* @ORMId
*/
private $code;
/**
* @ORMManyToOne(targetEntity="Product", inversedBy="names")
* @ORMJoinColumn(name="Modele_Code", referencedColumnName="Modele_Code")
*/
private $product;
/**
* @ORMColumn(name="Langue_Code", type="string", length=2)
*/
private $language;
/**
* @ORMColumn(name="Modele_Libelle", type="string", length=50)
* @AssertNotBlank()
*/
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 AppBundleForm;
use SymfonyComponentFormAbstractType;
use SymfonyComponentFormFormBuilderInterface;
use SymfonyComponentOptionsResolverOptionsResolver;
use SymfonyComponentFormExtensionCoreTypeTextType;
use SymfonyComponentFormExtensionCoreTypeCollectionType;
use SymfonyComponentFormExtensionCoreTypeSubmitType;
// use DoctrineORMEntityRepository;
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' => 'AppBundleEntityProduct',
'languages' => null
));
}
}
表单产品名称Type.php
namespace AppBundleForm;
use SymfonyComponentFormAbstractType;
use SymfonyComponentFormFormBuilderInterface;
use SymfonyComponentOptionsResolverOptionsResolver;
use SymfonyComponentFormExtensionCoreTypeTextType;
use SymfonyComponentFormExtensionCoreTypeChoiceType;
use IvoryCKEditorBundleFormTypeCKEditorType;
use DoctrineORMEntityRepository;
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' => 'AppBundleEntityProductNames',
'languages' => null,
'edit' => false
));
}
}
ProductController.php(请参阅editAction
查找我的问题)。
如果我在表单提交后在addAction()
中打印$form->getData()
或$product->getNames()
,我会得到我所有的数据,一切对我来说都很好。
namespace AppBundleController;
use AppBundleFormProductType;
use AppBundleEntityProduct;
use AppBundleEntityProductNames;
use SensioBundleFrameworkExtraBundleConfigurationRoute;
use SymfonyBundleFrameworkBundleControllerController;
use SymfonyComponentHttpFoundationRequest;
use DoctrineCommonCollectionsArrayCollection;
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
标记为主键(使用@ORMId
),一个产品将具有多个ProductNames,并且它们都不能将code
作为主键,因为主键需要唯一。我建议通过在langauge中添加@ORMId
注释来使用复合主键。
class ProductNames
{
/**
* @ORMColumn(name="Modele_Code", type="string", length=15)
* @ORMId
*/
private $code;
/**
* @ORMColumn(name="Langue_Code", type="string", length=2)
* @ORMId
*/
private $language;
// ...
}
您必须更新/重新创建数据库,复合密钥才能生效。
希望这能有所帮助。