P
P
Pavel Busel2014-12-28 13:55:18
symfony
Pavel Busel, 2014-12-28 13:55:18

How to make nested forms in Symfony2?

It is necessary to enable users to add positions when creating an account. Account - 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);
    }
}

Account line items 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;

Account form:
<?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';
    }
}

Position form:
<?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';
    }
}

Controller
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() );
    }
}

form output:
{% 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 %}

As a result, I get an account form with a button to add line, when clicked, a nested position form appears (mopabootstrapbundle is responsible for adding the html position form). Error while entering data
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

Why there is an error - the position does not have enough account id. How to make a binding? When working, I relied on this instruction .

Answer the question

In order to leave comments, you need to log in

3 answer(s)
N
neolink, 2014-12-28
@pro100Coder

public function addLine(InvoiceLine $line) {
        $line->setInvoice($this);
        $this->lines->add($line);
}

A
Alex, 2014-12-28
@shoomyst

You probably need some kind of hidden field in InvoiceLineType, which will contain the invoice id

P
Pavel Solovyov, 2014-12-28
@pavel_salauyou

// $line->addInvoice($this); If you uncomment, will it be the same? And in place of the controller Form Type is displayed.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question