У меня есть объект Product. Мой продукт может иметь несколько имен на разных языках. Имя на французском, имя на английском и т. Д. Я не хочу использовать автоматический перевод.Symfony3: ArrayCollection имеет только последний добавленный элемент
Пользователь должен будет записать имена в форме продукта и выбрать соответствующий язык. Он может добавить столько имен, сколько захочет, благодаря кнопке «Добавить».
Все языки создаются пользователем admin (в другой форме). Таким образом, язык также является сущностью, которая имеет имя (например: английский) и код (например: EN).
Итак, ProductType
моей основной формы и моей «коллекция» форма.
Когда пользователь создает новый продукт с двумя именами, например (один на французском, а другой на английском), продукт сохраняется в моей базе данных, а также создаются и сохраняются 2 имена и имена других таблиц.
На данный момент все работает хорошо. Мой addAction()
хорош, продукт и соответствующие им имена сохраняются в базе данных.
Но, У меня есть проблема на моемeditAction()
, когда я показываю свою форму заранее заполненной. Только последнее добавленное название продукта присутствует в моей коллекции ...
Я не понимаю, что я делаю неправильно. Имена находятся в моей базе данных, продукт тоже, так почему я получаю только фамилию в ArrayCollection?
Entity 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);
}
}
Entity 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()
после формы submit, я получаю все свои данные, все в порядке для меня.
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
));
}
}
Вы правы! На самом деле, мне нужно создать интерфейс с Symfony, который использует уже существующую базу данных. И в этой базе данных таблица «modele_lib» (= ProductNames) имеет двойной первичный ключ: код продукта + код языка. Я забыл написать это в своих аннотациях, спасибо, я собираюсь проверить это, чтобы проверить, исправляет ли я мою проблему с ArrayCollection. – Felurian
Большое спасибо, ваш гений! Сейчас это работает ;-) – Felurian