Как в Symfony2 делать вложенные формы?

Необходимо дать возможнсть пользователям при создании счета добавлять позиции. Счет - entity Invoice
namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;


/**
 * Invoice
 *
 * @ORM\Table(name="invoices")
 * @ORM\Entity(repositoryClass="AppBundle\Entity\Repository\InvoiceRepository")
 * @ORM\EntityListeners({ "AppBundle\Entity\Listener\InvoiceListener" })
 *
 * @UniqueEntity(fields={"number"}, message="Такой номер уже существует")
 */
class Invoice
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * Разбивка счета
     *
     * @ORM\OneToMany(targetEntity="InvoiceLine", mappedBy="invoice", cascade={"persist", "remove"})
     *
     * @var InvoiceLine
     */
    protected $lines;

    public function addInvoiceLine(InvoiceLine $line) {
//        $line->addInvoice($this);
        $this->lines->add($line);
    }
}

Позиции счета InvoiceLine
namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

use Symfony\Component\Validator\Constraints as Assert;

/**
 * InvoiceLine
 *
 * @ORM\Table(name="invoices_lines")
 * @ORM\Entity(repositoryClass="AppBundle\Entity\Repository\InvoiceLineRepository")
 */
class InvoiceLine
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * Счет
     *
     * @ORM\ManyToOne(targetEntity="Invoice", inversedBy="lines")
     * @ORM\JoinColumn(name="invoice_id", referencedColumnName="id", nullable=false, onDelete="CASCADE")
     *
     * @Assert\NotBlank(message="Поле обязательно для заполнения")
     *
     * @var \AppBundle\Entity\Invoice
     */
    protected $invoice;

    /**
     * @ORM\Column(type="text", nullable=false)
     *
     * @Assert\NotBlank(message="Поле обязательно для заполнения")
     */
    protected $description;

    /**
     * @ORM\Column(type="decimal", precision = 20, nullable=false)
     *
     * @Assert\NotBlank(message="Поле обязательно для заполнения")
     */
    protected $cost;


Форма счета:
<?php

namespace AppBundle\Form\Type;

use AppBundle\Entity\InvoiceLine;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class InvoiceType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
          ->add('number')
          ->add('createDate')
          ->add('status', 'choice', array(
              'choices' => array(
                '0' => 'Не оплачен',
                '1' => 'Оплачен',
                '2' => 'Отменен',
              )
            ))
          ->add('client')
          ->add('lines', 'collection', array(
                'type' => new InvoiceLineType(),
                'allow_add' => true,
//                'delete_empty' => true,
//                'prototype' => true,
//                'allow_delete' => true,
                'by_reference' => false,
            ));
    }

    /**
     * @param OptionsResolverInterface $resolver
     */
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(
          array(
            'data_class' => 'AppBundle\Entity\Invoice'
          )
        );
    }

    /**
     * @return string
     */
    public function getName()
    {
        return 'appbundle_invoice';
    }
}

Форма позиции:
<?php

namespace AppBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class InvoiceLineType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('description')
            ->add('cost')
        ;
    }
    
    /**
     * @param OptionsResolverInterface $resolver
     */
    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'AppBundle\Entity\InvoiceLine'
        ));
    }

    /**
     * @return string
     */
    public function getName()
    {
        return 'invoiceline';
    }
}

Контроллер
namespace AppBundle\Controller;

use AppBundle\Entity\InvoiceLine;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;

use AppBundle\Entity\Invoice;
use AppBundle\Form\Type\InvoiceType;

use JMS\DiExtraBundle\Annotation as DI;

/**
 * @Route("/admin/invoice")
 */
class InvoiceController extends Controller
{

    /**
     * @DI\Inject("doctrine.orm.entity_manager")
     *
     * @var \Doctrine\ORM\EntityManager
     */
    private $em;

    /**
     * @Route("/add", name="invoice_add")
     * @Template()
     *
     * @var Request $request
     * @return array
     */
    public function invoiceAddAction(Request $request)
    {
        $invoice = new Invoice();
        $form   = $this->createForm(new InvoiceType(), $invoice);
        $form->handleRequest($request);

        if ($form->isValid()) {
            $this->em->persist($invoice);
            $this->em->flush();

            $this->addFlash('success', 'Счет добавлен');

            return $this->redirect($this->generateUrl('invoice_list'));
        }

        return array( 'form' => $form->createView() );
    }
}

вывод формы:
{% extends is_ajax ? 'AppBundle::layout_ajax.html.twig' : 'AppBundle::layout.html.twig' %}

{% block content %}

    <form class="form-horizontal" method="post" action="{{ path('invoice_edit', {'id': client.id}) }}">
        {{ form_errors(form) }}
        {{ form_widget(form) }}
        <button type="submit" class="btn btn-primary">Сохранить</button>
    </form>

{% endblock content %}


В результате получаю форму счета с кнопкой добавить line при нажатии на которую появляется вложенная форма позиции(за добавление html формы позиции отвечает mopabootstrapbundle). При внесении данных - ошибка
An exception occurred while executing 'INSERT INTO invoices_lines (description, cost, invoice_id) VALUES (?, ?, ?)' with params ["32", 234, null]:

SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'invoice_id' cannot be null

Почему возникает ошибка - у позиции не хватет id счета. Как сделать привязку? При работе опирался на эту инструкцию.
  • Вопрос задан
  • 3779 просмотров
Решения вопроса 1
@neolink
public function addLine(InvoiceLine $line) {
        $line->setInvoice($this);
        $this->lines->add($line);
}
Ответ написан
Пригласить эксперта
Ответы на вопрос 2
@shoomyst
dumb
Вероятно, нужно какое-то hidden поле в InvoiceLineType, которое будет содержать id счета
Ответ написан
pavel_salauyou
@pavel_salauyou
Symfony2 & Angular разработчик
// $line->addInvoice($this); если раскоментить, тоже самое будет? И в место контроллёра Form Type отображается.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы