2016-05-09 6 views
0

У меня есть объект 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 
    )); 
    } 
} 

ответ

0

Проблема может быть связана с вашей организацией ProductNames. Вы отметили code в качестве первичного ключа (с @ORM\Id), a Продукт будет иметь несколько 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; 

    // ... 
} 

Вам необходимо будет обновить/воссоздать вашу базу данных, чтобы составной ключ вступил в силу.

Надеюсь, это поможет.

+0

Вы правы! На самом деле, мне нужно создать интерфейс с Symfony, который использует уже существующую базу данных. И в этой базе данных таблица «modele_lib» (= ProductNames) имеет двойной первичный ключ: код продукта + код языка. Я забыл написать это в своих аннотациях, спасибо, я собираюсь проверить это, чтобы проверить, исправляет ли я мою проблему с ArrayCollection. – Felurian

+0

Большое спасибо, ваш гений! Сейчас это работает ;-) – Felurian

Смежные вопросы