0

I am trying to generate a form which I think just needs two entities. However the only way I've managed to get it working is by using three. The form I have works but it is VERY slow as there are a lot of records in the database for each entity. Is there is a smarter way that I should be handling this?

What I am trying to achieve is a nested InvoiceType form for every customer entity in the database. The admin user should then be able to tick/untick which customers they want to generate a batch of invoices for. It should look something like this:

| Generate Invoice? | Customer   | Amount   | Date       | Notes     |
|-------------------|------------|----------|------------|-----------|
| ☑                |  Customer1 | 100.00   | 2016-05-08 |-----------|
| ☐                |  Customer2 | 105.55   | 2016-05-09 |-----------|

Currently, I have it working by using three entities. I created an Invoicebatch entity in order to identify which batch (if any) an Invoice belongs to. However, as I need to create a new Invoice object for every Customer in the database, I am loading (I think) each Customer object into the form object in my controller:

public function batchInvoicesAction(Request $request)
{
    $em = $this->getDoctrine()->getManager();
    $customers = $em->getRepository('AppBundle:Customer')->findAll();
    $batch = new InvoiceBatch();
    foreach ($customers as $customer) {
        $invoice = new Invoice();
        $invoice->setCustomerId($customer);
        $invoice->setSelected(True);
        $invoice->setCreatedate(new \Datetime());
        $invoice->setAmount($customer->getDefaultinvoiceamount());
        $invoice->setinvoicebatchid($batch);
        $batch->addInvoiceId($invoice);
    }
    $form = $this->createForm(InvoiceBatchType::class, $batch);
    $form->handleRequest($request);

    if ($form->isSubmitted() && ($form->isValid())) {
        $batchform = $form->getData();
        foreach ($batchform->getInvoiceids() as $invoiceform) {
            if ($invoiceform->getSelected() == False) {
                $batchform->removeInvoiceId($invoiceform);
            } else {
                $em->persist($invoiceform);
            }
        }
        $em->persist($batchform);
        $em->flush();
        return $this->redirectToRoute('view_monthly_invoices');
    }
    return $this->render('invoices/new.batch.invoice.html.twig')
}

My InvoiceBatchType is as below:

class InvoiceBatchType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('batchevent', TextType::class, array(
            ))
            ->add('invoice_ids', CollectionType::class, array(
                'entry_type' => InvoiceForBulkType::class,
            ))
        ;
    }

My InvoiceForBulkType is:

class InvoiceForBulkType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('selected', CheckboxType::class, array(
                'label'    => ' ',
                'required' => false,
            ))
            ->add('customer_id', EntityType::class, array(
                    'class' => 'AppBundle:Customer',
                    'choice_label' => 'FullName',
                    'label'=>'Customer',
                    'disabled'=>true,
                )
            )
            ->add('amount', TextType::class, array(
                'label' => 'Amount',
            ))

            ->add('invoicedate', DateType::class, array(
                'widget' => 'single_text',
                'data' => new \DateTime('now'),
                'format' => 'dd/MMM/yyyy',
                'label' => 'Date of invoice',
                'attr'=> array(
                    'class'=>'datepicker',
                )
            ))

            ->add('description', TextType::class, array(
                'required'=>false,
            ))
        ;
    }
}

I was thinking it may be possible to just load the Customer names in as an array, as I just want to display a customer name on each row (ie no dropdown or other form element). I've tried a few attempts at this but with no luck so I am thinking the $customer objects need to be included for background processes in Symfony I'm unaware of (I have a OneToMany association between Customer and Invoice).

6
  • Start by removing the "I Think" portion of the problem. Check the web debug toolbar to determine what queries are actually being executed. Commented May 9, 2016 at 16:00
  • Good point thanks @Cerad - Doctrine=7ms (90MB), controller=37,325ms (937MB), twig files=20,806ms (937MB), kernel response=79ms (937MB) Commented May 9, 2016 at 16:29
  • And di you confirm that thousands of queries are being performed? Commented May 9, 2016 at 18:08
  • There's just three queries to the database - the third is the one that gets the Customer objects that are eventually fed into the $form. At 7ms though that looks fine. Still being new to web development though, having a controller at nearly 1GB seems huge - is that within 'normal ranges' or am I doing something wrong there? Commented May 9, 2016 at 18:19
  • Has you check the 'entity' type and expanded=>true and multiple=>true? Commented May 9, 2016 at 18:35

1 Answer 1

1

I managed to get this working in the end - the problem was that I was not aware of the options for passing entities in Symfony forms. I've tried to summarise the theory/logic below in the hope it's of help to anyone else struggling to get their heads around Symfony forms:

Having read through the Symfony Form documentation I mistakenly thought that only EntityType and CollectionType could handle receiving an entity as input. Therefore, my InvoiceForBatchType was causing the problem - it was loading ALL Customers into every single Invoice.

The key concept I had been missing for Symfony forms is:

If you are passing an entity that you want to be able to access, then you do not use EntityType (unless you want to populate dropdown/radio/ticks). Instead, you create a new file to define a nested FormType

This was then an easy fix in my case by:

1. Updating InvoiceForBulkType to:
class InvoiceForBulkType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            /* ... */

            ->add('customer_id', CustomerForInvoiceType::class)

            /* ... */
        ;
    }
}
2. Creating CustomerForInvoiceType:
class CustomerForInvoice extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('fullname', TextType::class, array(
            ))
        ;
    }

    /* ... */
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.