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

时间:2015-03-03 16:44:19

标签: forms symfony doctrine-orm

我正在制作一个嵌入式表格的发票系统。一个表格,您可以在其中添加日期,从下拉列表中选择客户公司( - >客户是实体)并向发票项目添加详细信息( - >详细信息也是一个实体,具有价格,金额等属性, ...) - 用javascript。这工作得很好,但在保存表单时我收到错误。 我有3个实体:InvoicingCustomer,InvoiceItem,InvoiceItemDetail。 (对不起,这将是一个很长的帖子)

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;
}

当然还有吸气剂和制定者。

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 =

/**
 * @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
}

然后,我得到了类型。 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 =

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 =

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'; }
 } 

在我的控制器中我有这个( 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(),
       ));
    }
}

在我的树枝上它就像:

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

表单中的所有内容都显示良好(使用javascript我可以将一些详细信息添加到一个发票项目)。但是当我提交表单时,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

我搜索了symfony(http://symfony.com/doc/current/cookbook/form/form_collections.html)和stackoverflow(例如:Saving embedded collections)的文档,但这些都没有给我正确的解决方案。

我知道这是一篇很长的帖子:对不起。但是我不知道如何解决这个问题(+我是学习symfony2的新手,也是新问题)。

2 个答案:

答案 0 :(得分:1)

我相信您的问题出在InvoiceItem实体中。尝试创建方法addItemDetail(或者addInvoiceItemDetail)而不是addDetail。你也可以删除方法setItemDetail,也许你会看到很好的解释Symfony寻找的方法。

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

从控制器中删除黑客。

 // 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);
     }
 }

我希望它有所帮助,但如果没有实时代码,就很难回答这个问题。

答案 1 :(得分:0)

Symfony表单的概念是,对于关系,您还可以在表单类型中指定相关实体。 在您的情况下,您没有将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'; }

注意我将表单元素的类型设置为invoice_item。 您可以通过将其定义为服务来实现这一目标:

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