简体   繁体   English

保存嵌入式表格(集合)Symfony2

[英]saving embedded form (collection) Symfony2

I am making an invoicing system with an embedded form. 我正在制作具有嵌入式表单的发票系统。 A form where you can add a date, choose a customer company from a dropdown list (-> customer is an entity) and add details to the invoice item (-> details is also an entity, with properties like price, amount,...) - with javascript. 您可以在其中添加日期,从下拉列表中选择客户公司(->客户是实体)并向发票项目添加详细信息(->详细信息也是实体,具有价格,金额等属性的表单)。 。)-使用javascript。 This works just fine, but at saving the form I get an error. 这很好,但是在保存表单时出现错误。 I have 3 entities: InvoicingCustomer, InvoiceItem, InvoiceItemDetail. 我有3个实体:InvoicingCustomer,InvoiceItem,InvoiceItemDetail。 (Sorry; this is going to be a long post) (对不起;这将是一个很长的帖子)

InvoicingCustomer.php (with properties like street, address,...) = InvoicingCustomer.php (具有街道,地址等属性)=

/**
* @ORM\Table(name="invoicing_customer")
* @ORM\Entity
*/
class InvoicingCustomer
{
   /**
   * @ORM\OneToMany(targetEntity="Invoicing\InvoicingBundle\Entity\InvoiceItem", mappedBy="customer")
   */
   private $invoice;

   public function __construct()
   { $this->invoice = new ArrayCollection();}

   public function getInvoice()
   { return $this->invoice; }

   public function getAllInvoices()
   {
      $invoices = $this->getInvoice()->toArray();
      return $invoices;
   }

   /**
   * @var integer
   * @ORM\Column(name="id", type="integer")
   * @ORM\Id
   * @ORM\GeneratedValue(strategy="IDENTITY")
   */
   private $id;

  /**
   * @var string
   * @ORM\Column(name="company_name", type="string", length=50, nullable=false)
   * @Assert\NotBlank()
   */
   private $companyName;
   //idem for next properties:
   private $firstName;
   private $lastName;
   private $street;
   private $number;
   private $postalCode;
   private $city;
}

And off course the getters and setters. 当然,吸气剂和吸气剂。

InvoiceItem.php = InvoiceItem.php =

/**
* @ORM\Table(name="invoicing_invoice_item")
* @ORM\Entity
*/
class InvoiceItem
{
  /**
   * @ORM\OneToMany(targetEntity="Invoicing\InvoicingBundle\Entity\InvoiceItemDetail", mappedBy="item_nr", cascade={"ALL"}, fetch="EAGER", orphanRemoval=true)
   */
   private $item_detail;

   public function __construct()
   { $this->item_detail = new ArrayCollection(); }

   /**
    * @return mixed
    */
   public function getItemDetail()
   { return $this->item_detail;  }

   /**
   * @param mixed $item_detail
   */
   public function setItemDetail(Collection $item_detail)
   {
      foreach ($item_detail as $v)
      {
        if (is_null($v->getId()))
        {
            $v->getId($this);
        }
      }
      $this->item_detail = $item_detail;
   }

   public function addDetail(InvoiceItemDetail $detail){
     $detail->$this->setItemDetail($this);
     $this->detail[] = $detail;
     return $this;
   }

   public function removeDetail(InvoiceItemDetail $detail){
      //
   }

  /**
   * @var integer
   * @ORM\Column(name="id", type="integer")
   * @ORM\Id
   * @ORM\GeneratedValue(strategy="IDENTITY")
   */
   private $id;

  /**
   * @var \DateTime
   * @ORM\Column(name="date", type="date", nullable=false)
   */
   private $date;

  /**
   * @ORM\ManyToOne(targetEntity="Invoicing\CustomerBundle\Entity\InvoicingCustomer", inversedBy="invoice")
   * @ORM\JoinColumn(onDelete="CASCADE", nullable=false)
   * @Assert\Type(type="Invoicing\CustomerBundle\Entity\InvoicingCustomer")
   * @Assert\Valid()
   *
   */
   private $customer;

  // here also getters and setters
}

InvoiceItemDetail.php = InvoiceItemDetail.php =

/**
 * @ORM\Table(name="invoicing_invoice_itemdetail")
 * @ORM\Entity
 */
class InvoiceItemDetail
{
   /**
    * @var integer
    * @ORM\Column(name="id", type="integer")
    * @ORM\Id
    * @ORM\GeneratedValue(strategy="IDENTITY")
    */
    private $id;

   /**
    * @ORM\Column(name="description", type="text", length=200, nullable=false)
    */
   private $description;

   /**
    * @var string
    * @ORM\Column(name="price", type="decimal", precision=10, scale=0, nullable=false)
    */
    private $price;

    /**
    * @var integer
    * @ORM\Column(name="amount", type="decimal", precision=10, scale=0, nullable=false)
    */
    private $amount;

   /**
    * @ORM\ManyToOne(targetEntity="Invoicing\InvoicingBundle\Entity\InvoiceItem", inversedBy="item_detail" )
    * @ORM\JoinColumn(onDelete="CASCADE", nullable=false, name="item_nr_id", referencedColumnName="id")
    * @Assert\Type(type="Invoicing\InvoicingBundle\Entity\InvoiceItem")
    * @Assert\Valid()
    */
    private $item_nr;

// + getters and setters
}

Then, I got the types. 然后,我得到了类型。 InvoiceItemType.php = InvoiceItemType.php =

class InvoiceItemType extends AbstractType
 {
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
       $builder
           ->add('date', 'date', array(
               'format' => 'dd-MM-yyyy',
               'empty_value' => array('year' => 'Year', 'month' => 'Month', 'day' => 'Day'),
               'years' => range(date('Y') -1, date('Y')),
           ))
           ->add('customer', null, array(
              'empty_value' => 'Choose a company',
              'label' => 'Company',
              'required' => true,
           ))
           ->add('item_detail', 'collection', array(
               'type' => new InvoiceItemDetailType(),
               'allow_add' => true,
               'constraints' => new NotBlank(),
               'by_reference' => false,
           ));
    }
   public function getName()
   { return 'invoiceitem'; }

   public function setDefaultOptions(OptionsResolverInterface $resolver)
   {
      $resolver->setDefaults(array(
          'data_class' => 'Invoicing\InvoicingBundle\Entity\InvoiceItem',
      ));
   }
}

InvoicingCustomerType.php = InvoicingCustomerType.php =

class InvoicingCustomerType extends AbstractType
 {
   public function buildForm(FormBuilderInterface $builder, array $options)
   {
      $builder->add('companyName', 'text');
   }

   public function setDefaultOptions(OptionsResolverInterface $resolver)
   {
      $resolver->setDefaults(array(
          'data_class' => 'Invoicing\CustomerBundle\Entity\InvoicingCustomer',
      ));
   }
   public function getName()
   { return 'customer'; }
}

InvoiceItemDetailType.php = InvoiceItemDetailType.php =

class InvoiceItemDetailType extends AbstractType
 {
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
       $builder
           ->add('description', 'text')
           ->add('price', 'number', array(
               'label' => 'Price - €',
            ))
          ->add('amount', 'number');
     }

     public function setDefaultOptions(OptionsResolverInterface $resolver)
     {
         $resolver->setDefaults(array(
            'data_class' => 'Invoicing\InvoicingBundle\Entity\InvoiceItemDetail',
         ));
     }
     public function getName()
     { return 'detail'; }
 } 

In my controller I have this ( InvoiceItemController.php ): 在我的控制器中,我有这个( InvoiceItemController.php ):

/** InvoiceItem controller */
 class InvoiceItemController extends Controller
 {
    /**
     * Creates a new invoiceitem entity.
     */
    public function createAction(Request $request)
    {
       $entity = new InvoiceItem();

       $form = $this->createCreateForm($entity);
       $form->handleRequest($request);

       if ($form->isValid()) {
           $em = $this->getDoctrine()->getManager();

           // hack to work around handleRequest not using class methods to populate data
           foreach($entity->getItemDetail() as $detail){
              foreach($detail as $i){
                // if i didn't made a second loop, I get an error: "object could not be converted to string..."
                  $i->this->setItemNr($entity);
                  $em->persist($i);
               }
           }
           $em->persist($entity);
           $em->flush();

           return $this->redirect($this->generateUrl('invoiceitem_show', array('id' => $entity->getId())));
        }

       return $this->render('InvoicingBundle:InvoiceItem:new.html.twig', array(
           'entity' => $entity,
           'form'   => $form->createView(),
       ));
    }
}

In my twig it's just like: 在我的树枝上,就像:

{% block body -%}
   <h1>Invoice item creation</h1>
   {{ form(form) }}
 {% endblock %}

Everything in the form is displayed good (and with javascript I can add several details to one invoice item). 表格中的所有内容均显示良好(并且可以使用javascript将多个详细信息添加到一个发票项目中)。 But when I submit the form, symfony throws an error: 但是当我提交表单时,symfony会抛出错误:

An exception occurred while executing 'INSERT INTO invoicing_invoice_itemdetail (description, price, amount, item_nr_id) VALUES (?, ?, ?, ?)' with params ["test", 300, 1, null]: 
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'item_nr_id' cannot be null

I searched around on the docs of symfony ( http://symfony.com/doc/current/cookbook/form/form_collections.html ) and on stackoverflow (for example: Saving embedded collections ), but none of these give me the right solution. 我在symfony的文档( http://symfony.com/doc/current/cookbook/form/form_collections.html )和stackoverflow上进行了搜索(例如: 保存嵌入式集合 ),但是没有一个给我正确的解决方案。

I know this is a long post: I am sorry. 我知道这是一篇很长的文章:对不起。 But I don't know how to sort this problem out (+ I am new in learning symfony2 & new in asking questions here). 但是我不知道如何解决这个问题(+我是学习symfony2的新手,也是在这里问问题的新手)。

I believe your problem is in InvoiceItem entity. 我相信您的问题出在InvoiceItem实体。 Try to create method addItemDetail (or maybe addInvoiceItemDetail) instead addDetail. 尝试创建方法addItemDetail(或可能是addInvoiceItemDetail)而不是addDetail。 You can also delete method setItemDetail and maybe you will see good explanation what method is Symfony looking for. 您也可以删除setItemDetail方法,也许您会很好地解释Symfony在寻找什么方法。

public function addItemDetail(InvoiceItemDetail $detail){
    $detail->setItemNr($this);
    $this->item_detail[] = $detail;
    return $this;
}

And delete the hack from controller. 并从控制器中删除黑客。

 // hack to work around handleRequest not using class methods to populate data
 foreach($entity->getItemDetail() as $detail){
     foreach($detail as $i){
         // if i didn't made a second loop, I get an error: "object could not be converted to string..."
         $i->this->setItemNr($entity);
         $em->persist($i);
     }
 }

I hope it helps but it is a little hard to answer this question without live code. 我希望这会有所帮助,但是如果没有实时代码,很难回答这个问题。

The concept of Symfony forms is that for relations you can also specify the related entity in the form type. Symfony表单的概念是,对于关系,您还可以在表单类型中指定相关实体。 In your case, you didn't add InvoiceItemType to the Type ItemInvoiceDetail: I would expect the following code in your InvoiceDetailType: 就您而言,您没有将InvoiceItemType添加到Type ItemInvoiceDetail类型:我希望您的InvoiceDetailType中包含以下代码:

class InvoiceItemDetailType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
   $builder
       ->add('description', 'text')
       ->add('invoice_item', 'invoice_item', array('error_bubbling' => true, 'mapped' => true))
       ->add('price', 'number', array(
           'label' => 'Price - €',
        ))
      ->add('amount', 'number');
 }

 public function setDefaultOptions(OptionsResolverInterface $resolver)
 {
     $resolver->setDefaults(array(
        'data_class' => 'Invoicing\InvoicingBundle\Entity\InvoiceItemDetail',
     ));
 }
 public function getName()
 { return 'detail'; }

Notice i set the type of the form element as invoice_item. 注意,我将表单元素的类型设置为invoice_item。 you can achieve that by defining it as a service: 您可以通过将其定义为服务来实现:

   service_name:
        class: invoiceitemfullclasspath
        arguments: []            
        tags:
            - { name: form.type, alias: invoice_item }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM