Kees Sonnema Kees Sonnema - 1 month ago 90
Twig Question

Unable to access form values in array with key-array subset

For a project I'm working on a company with addresses related to that company. I created a form for the company with it's physical address (this address is always present, it also has extra addresses associated to it. This is where I get a problem.

I'm using a CollectionType for the forms to use the same form for every extra address.
I'm creating a form with pre-filled values for the extra addresses (this can be 1, 2, 3 or even 4 addresses) using:

$form_adressen = $this
->createForm(AdressenType::class, $result['extra_address'])
->handleRequest($request)
;


However this does not render the form and gets no values from the controller.
The following code works however:

$form_adressen = $this
->createForm(AdressenType::class, $result)
->handleRequest($request)
;


The result['extra_address'] prints the following data:

Array
(
[1059] => Array
(
[AdrGid] => 1059
[AdrCode] => the address type
[AdrStraat] => streetname
[AdrHuisnummer] => number
[AdrPostcode] => postal code
[AdrPlaats] => cityname
[AdrTelefoon] => telephone
[AdrTelefoon2] => mobile
[AdrTelefax] => fax
[AdrNawGid] => 332 //id of companyaddress associating to this 'extra' address
[NawFactuurAdres] => 1
[AdrOms] => address type description
)
[...] // More addresses
}


And
$result
gives me this:


Array
(
[company] => Array
(
[AdrGid] => 332
[NawFilZoekcode] => company code
[AdrNaam1] => company name
[email2] => company mail
[NawWebsite] => company website
[email1] => second company email
[AdrStraat] => streetname
[AdrHuisnummer] => number
[AdrPostcode] => postal code
[AdrPlaats] => cityname
[LndOms] => country
[AdrTelefoon] => telephone
[AdrTelefoon2] => mobile
[AdrTelefax] => fax
[NawFactuurAdres] => 1
[AdrCode] => adrress type
[AdrOms] => address type description
)

[extra_address] => Array
(
[1059] => Array
(
[AdrGid] => 1059
[AdrCode] => address type
[AdrStraat] => streetname
[AdrHuisnummer] => number
[...] // etc etc


My results array looks like this:

foreach ($company as $row) {
$dataFields = [
[...]
];
foreach ($dataFields as $dataField) {
if (empty($result['gegevens'][$dataField])) {
$result['company'][$dataField] = $row[$dataField];
}
}
if ($row['AdrGid'] != $result['gegevens']['AdrGid']) {
$result['extra_address'][$row['AdrGid']] = [
[...]
];
}
}


EDIT:
My AdressenType to create the form:

class AdressenType extends AbstractType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('addresses', CollectionType::class, [
'entry_type' => AddressType::class
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
));
}
public function getBlockPrefix()
{
return 'appbundle_form_adressenform';
}
}
class AddressType extends AbstractType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
[...] // Add fields
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
));
}
}


This is how I pass my forms to the view:

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


As you can see the extra_address loop uses an id as a key for the arrays, I guess this is why the addresses are not showing up or rendered in the views.

I loop over each form with the following code:

{{ form_start(form_adressen, {' method': 'POST'}) }}
{{ form_row(form_adressen._token) }}
{% for adres in form_adressen.addresses %} // here 'addresses' is my form collectionType, not to be confusing
[...] // form_widgets, etc
{{ endfor }}
{{ form_end(form_adressen) }}


where
form_adressen
is my formType name and 'addresses' is my collectionType for the form_adressen formType.

My question is, how do I fill the addresses form with data if it has an id as key for the array per address? My guess is that the form cannot recognize the keys, so it skips.

Answer Source

The current AdressenType tries to find it's data at ['addresses'] but in the controller you create the form by directly passing in the list of extra addresses.

->createForm(AdressenType::class, $result['extra_address'])

This array probably doesn't have a key 'addresses', and as such the form acts as if no entries exist.

So either:

  1. pass in the correct data:

    ->createForm(AdressenType::class, [
        'addresses' => $result['extra_address']
    ])
    
  2. change the key in AdressenType to 'extra_address' and pass in $result:

    $builder
        ->add('extra_address', CollectionType::class, [
            'entry_type' => AddressType::class
        ])
    ;
    

    with

    ->createForm(AdressenType::class, $result)
    
  3. or better, change AdressenType to extend the CollectionType directly. Which (IMHO) is the better/cleaner thing to do because it removes the dependency on arbitrary array keys:

    <?php
    // ...
    use Symfony\Component\Form\Extension\Core\Type\CollectionType;
    
    class AdressenType extends AbstractType
    {
        /**
         * @inheritdoc
         */
        public function configureOptions(OptionsResolver $resolver)
        {
            $resolver->setDefaults([
                'entry_type' => AddressType::class,
            ]);
        }
    
        /**
         * @inheritdoc
         */
        public function getParent()
        {
            // extend the collection type
            return CollectionType::class;
        }
    }
    

    with:

    ->createForm(AdressenType::class, $result['extra_address'])
    

    and in the view, you could use it as:

    <h4>Extra addresses</h4>
    {{ form_start(form_adressen) }}
        {% for address in form_adressen.children %}
            {{ form_errors(address) }}
            {{ form_widget(address) }}
            <br>
        {% endfor %}
    
        <button>Submit</button>
    {{ form_end(form_adressen) }}
    

With the third option, you might run into problems when you try to customize the form rendering. Used like this the automatically injected csrf-token will be a sibling of the addresses. This could result in an error if you try to access individual fields for each children.

Though the following should work in that case:

{% for key, value in form_adressen.vars.data %}
    {{ form_row(form_adressen.children[key].AdrStraat) }}
    {{ form_row(form_adressen.children[key].AdrHuisnummer) }}
    {{ form_row(form_adressen.children[key].AdrPostcode) }}
    <br>
{% endfor %}