Bendy
Bendy

Reputation: 3576

Generating symfony forms with an array instead of entity?

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).

Upvotes: 0

Views: 2067

Answers (1)

Bendy
Bendy

Reputation: 3576

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

    /* ... */
}

Upvotes: 1

Related Questions