Kees Sonnema Kees Sonnema - 6 months ago 5643
Twig Question

One form for multiple addresses in Symfony - Twig

First I want to make clear that I'm not using Entities/Doctrine for my forms (or even). I'm using webservices to add/edit the data fields for the database

I'm working on a project where I need to edit company information with multiple addresses.
The first address is always the physical address so I grab that in the same loop as the company data.

However the second (and third, or fourth if exist) address is placed in a seperate database entry.
I'm able to edit the company data and the company address (physical address) with a single form (because the data is all in one set) Thus I only need one form for this, and don't have to use a loop for the address.




Now I want to edit the second address. I'm looping through the addresses in twig and print out all the other addresses for that company.
As I'm aware Symfony doesn't allow multiple instances of the same form, because this wil interfere with ID's etc.

The question is how I'm supposed to render the form only for a specific address. which I want to edit with the click of a button (using bootstrap collapse to hide the form if not editing)

My code looks like this:

Controller:

public function editcompany(Request $request, $klantnr)
{

[...]

$result = [
'gegevens' => [],
'addresses' => [],
];

foreach ($company as $row) {
$dataFields = [
[...] // The data fields of the company (including the physical address)
];
foreach ($dataFields as $dataField) {
if (empty($result['gegevens'][$dataField])) {
$result['gegevens'][$dataField] = $row[$dataField];
}
}
if($row['AdrGid'] != $result['gegevens']['AdrGid']) {
$result['addresses'][$row['AdrGid']] = [
[...] // The data fields for the other addresses
];
}
}

// The form creation of the company data form
$form = $this->createForm(EditcompanyForm::class);

// The form of the other addresses
$form_adres = $this->createForm(EditcompanyAdresForm::class, array(
[...]
));
$form->handleRequest($request);
$form_adres->handleRequest($request);


if($form->isSubmitted() && $form->isValid()) {
[...] // Handling the submitted data for the company data
}

// GEKOPPELDE ADRESSEN company
if($form_adres->isSubmitted() && $form_adres->isValid()) {

[...]

$data = array(
[...] // Setting the data for the other addresses
);

[...]

// Passing the data and other options to the webservices
$this->get('http')->companyRequest($data, $admincode, $service, $token);

$request->getSession()
->getFlashBag()
->add('success', 'Linked address successfully edited');

return $this->redirect($request->getUri());
}

return $this->render('pages/company/edit.html.twig', array(
'client' => $result,
'form_company' => $form->createView(),
'form_company_adres' => $form_adres->createView()
));
}


Formbuilder (AbstractType):

class EditcompanyAdresForm extends AbstractType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$factuuradres = $options['NawFactuurAdres'];
$lands = $options['lands'];

$data = array();
$builder
[...] // Add the necessary fields
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
[...] // setting defaults here
));
}
/**
* {@inheritdoc}
*/
public function getBlockPrefix()
{
return 'appbundle_form_editcompanyadresform';
}
}


Twig template (showing addresses:

{% for address in client.addresses %}
<div class="right-cont-grey">
[...] // showing addresses
</div>
{% endfor %}


Twig template (editing form):

{% for address in client.addresses %}
{{ form_start(form_company_adres, {' method': 'POST'}) }}
{{ form_row(form_company_adres._token) }}
<section class="collapse" id="adres_plus">
<div id="adres" class="new-adres collapse right-cont-grey">
<div class="col-sm-12">
[...] // Form_widgets with values from database
</div>
</section>
{{ form_end(form_company_adres) }}
{% endfor %}


So in short:

How can I use the address form for every address without it interferring? Maybe create the view (render) within the twig file inside the loop?
My form is pre-filled with data from the database, so that I can edit that.

Answer Source

revised answer with regards to what we talked about in chat:

As I wrote in the comment, I'm sure you only need to setup a collection field. The whole complexity then basically goes away.

A very simple example would be:

controller:

public function editCompanyAction(Request $request)
{
    // get the data anyway you see fit and produce a model (this could also be a dedicated DTO)
    $company = $this->getInput();

    // create the form
    $form = $this
        ->createForm(CompanyWithAddressesType::class, $company)
        ->handleRequest($request)
    ;

    // handle submission
    if ($form->isSubmitted() && $form->isValid()) {
        dump($form->getData()); // === $company
    }

    return $this->render('default/index.html.twig', [
        'form' => $form->createView(),
    ]);
}

the forms

The important part here is that the names of the fields you add match the keys in the array which is supplied as data!

class CompanyWithAddressesType extends AbstractType
{
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        // combine the company with a collection of addresses:
        $builder
            ->add('gegevens', CompanyType::class)
            ->add('addresses', CollectionType::class, [
                'entry_type' => AddressType::class,
            ])
        ;
    }
}

class CompanyType extends AbstractType
{
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        // all the fields that are *extra* for companies, address fields are inherited
        // because this type extends the address type (see below getParent)
        $builder
            ->add('NawWebsite', TextType::class)
            ->add('email1', TextType::class)
            ->add('AdrNaam1', TextType::class)
        ;
    }

    /**
     * @return @inheritdoc
     */
    public function getParent()
    {
        return AddressType::class;
    }
}

class AddressType extends AbstractType
{
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        // all the fields that define an address
        $builder
            ->add('AdrStraat', TextType::class)
            ->add('AdrHuisnummer', TextType::class)
            ->add('AdrPostcode', TextType::class)
            ->add('NawFactuurAdres', CheckboxType::class)
        ;
    }
}

twig:

Simply output the form. Any form customization is applicable

{{ form_start(form) }}
    <div>
        <h4>Company</h4>
        {{ form_row(form.gegevens.NawWebsite) }}
        {{ form_row(form.gegevens.email1) }}
        {{ form_row(form.gegevens.AdrNaam1) }}
    </div>

    <div>
        <h4>Main address</h4>
        {{ form_rest(form.gegevens) }}
    </div>

    <div>
        <h4>Extra addresses</h4>
        {% for address in form.addresses %}
            {{ form_errors(address) }}
            {{ form_widget(address) }}
        {% endfor %}
    </div>

    <button>Submit</button>
{{ form_end(form) }}
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download