Tipo di campo collection

Questo tipo di campo è usato per rendere un insieme di campi o form. Nel senso più semplice, potrebbe essere un array di campi text che popolano un array di campi emails. In esempi più complessi, si potrebbero includere interi form, che è utile quando si creano form che espongono relazioni molti-a-molti (p.e. un prodotto in cui si possono gestire molte foto correlate).

Reso come dipende dall’opzione type
Opzioni
Opzioni ereditate
Tipo genitore form
Classe Symfony\Component\Form\Extension\Core\Type\CollectionType

Nota

Se si ha a che fare con collezioni di entità Doctrine, prestare particolare attenzione alle opzioni allow_add, allow_delete e by_reference. Si può vedere un esempio completo nella ricetta Unire una collezione di form.

Uso di base

Questo tipo è usato quando si vuole gestire un insieme di elementi simili in un form. Per esempio, si supponga di avere un campo emails, che corrisponde a un array di indirizzi email. Nel form, si vuole esporre ogni indirizzo email con un campo testuale:

$builder->add('emails', 'collection', array(
    // ogni elemento nell'array sarà un campo "email"
    'type'   => 'email',
    // queste opzioni sono passate a ogni tipo "email"
    'options'  => array(
        'required'  => false,
        'attr'      => array('class' => 'email-box')
    ),
));

Il modo più semplice di renderlo è tutto insieme:

  • Twig
    {{ form_row(form.emails) }}
    
  • PHP
    <?php echo $view['form']->row($form['emails']) ?>
    

Un metodo molto più flessibile sarebbe questo:

  • Twig
    {{ form_label(form.emails) }}
    {{ form_errors(form.emails) }}
    
    <ul>
    {% for emailField in form.emails %}
        <li>
            {{ form_errors(emailField) }}
            {{ form_widget(emailField) }}
        </li>
    {% endfor %}
    </ul>
    
  • PHP
    <?php echo $view['form']->label($form['emails']) ?>
    <?php echo $view['form']->errors($form['emails']) ?>
    
    <ul>
    <?php foreach ($form['emails'] as $emailField): ?>
        <li>
            <?php echo $view['form']->errors($emailField) ?>
            <?php echo $view['form']->widget($emailField) ?>
        </li>
    <?php endforeach ?>
    </ul>
    

In entrambi i casi, non sarebbe reso alcun campo, a meno che l’array emails non contenga almeno un’email.

In questo semplice esempio, non è ancora possibile aggiungere nuovi indirizzi o rimuoverne di esistenti. L’aggiunta di nuovi indirizzi è possibile tramite l’opzione allow_add (e facoltativamente l’opzione prototype) (vedere esempio sotto). La rimozione di email è possibile tramite l’opzione allow_delete.

Aggiungere e rimuovere elementi

Se allow_add è true, se vengono inviati elementi non riconosciuti, saranno aggiunti all’array di elementi. Questo in teoria è buono, ma in pratica richiede un po’ di sforzi in più per far funzionare il JavaScript lato client.

Proseguendo con l’esempio precedente, si supponga di partire con due email nell’array emails. In questo caso, saranno resi due campi input, che assomiglieranno a questi (a seconda del nome del form):

<input type="email" id="form_emails_0" name="form[emails][0]" value="foo@foo.com" />
<input type="email" id="form_emails_1" name="form[emails][1]" value="bar@bar.com" />

Per consentire l’aggiunta di altre email, impostare allow_add a true e, tramite JavaScript, rendere un altro campo dal nome form[emails][2] (e così via per ulteriori campi).

Per facilitare le cose, impostare l’opzione prototype a true consente di rendere un campo “template”, utilizzabile poi nel codice JavaScript per la creazione dinamica di questi nuovi campi. Un campo prototipo assomiglierà a questo:

<input type="email" id="form_emails___name__" name="form[emails][__name__]" value="" />

Sostituendo __name__ con un valore unico (p.e. 2), si possono costruire e inserire nuovi campi HTML nel form.

Usando jQuery, un semplic esempio assomiglierebbe a questo. Se si rendono i campi collection tutti insieme (p.e. form_row(form.emails)), le cose si semplificano ulteriormente, perché l’attributo data-prototype viene reso automaticamente (con una piccola differenza, vedere la nota sotto) e tutto ciò che occorre è il JavaScript:

  • Twig
    {{ form_start(form) }}
        {# ... #}
    
        {# memorizza il prototipo nell'attributo data-prototype #}
        <ul id="email-fields-list"
            data-prototype="{{ form_widget(form.emails.vars.prototype)|e }}">
        {% for emailField in form.emails %}
            <li>
                {{ form_errors(emailField) }}
                {{ form_widget(emailField) }}
            </li>
        {% endfor %}
        </ul>
    
        <a href="#" id="add-another-email">Aggiungere email</a>
    
        {# ... #}
    {{ form_end(form) }}
    
    <script type="text/javascript">
        // tiene traccia di quanti campi email sono stati resi
        var emailCount = '{{ form.emails|length }}';
    
        jQuery(document).ready(function() {
            jQuery('#add-another-email').click(function (e) {
                e.preventDefault();
    
                var emailList = jQuery('#email-fields-list');
    
                // prende il template prototipo
                var newWidget = emailList.attr('data-prototype');
                // sostiuisce "__name__" usato nell'id e il nome del prototipo
                // con un numero univoco per le email
                // l'attributo finale assomiglia a name="contact[emails][2]"
                newWidget = newWidget.replace(/__name__/g, emailCount);
                emailCount++;
    
                // crea un nuovo elemento nella lista e lo aggiunge
                var newLi = jQuery('<li></li>').html(newWidget);
                newLi.appendTo(emailList);
            });
        })
    </script>
    

Suggerimento

Se si rende tutto l’insieme in una volta sola, il prototipo sarà disponibile automaticamente nell’attributo data-prototype dell’elemento (p.e. div o table) che circonda l’insieme. L’unica differenza è che la riga del form viene resa automaticamente, quindi non occorre inserirla in un elemento contenitore, come è stato fatto in precedenza.

Opzioni del campo

allow_add

tipo: Booleano predefinito: false

Se true, ogni elemento non riconosciuto inviato all’insieme sarà aggiunto come nuovo elemento. L’array finale conterrà gli elementi esistenti e il nuovo elemento, appena inviato. Si veda l’esempio sopra per maggiori dettagli.

Si può usare l’opzione prototype per rendere un elemento prototipo, che può essere usato, con JavaScript, per creare dinamicamente nuovi elementi lato client. Per maggiori informazioni, vedere l’esempio sopra e Permettere “nuovi” tag con “prototipo”.

Attenzione

Se si includono altri form, per riflettere una relazione uno-a-molti nella base dati, potrebbe essere necessario assicurarsi a mano che la chiave esterna di questi nuovi oggetti sia impostata correttamente. Se si usa Doctrine, questo non avverrà automaticamente. Vedere il collegamento sopra per maggiori dettagli.

allow_delete

tipo: Booleano predefinito: false

Se true, se un elemento esistente non compare tra i dati inviati, sarà assente dall’array finale di elementi. Questo vuol dire che si può implementare un bottone “cancella” tramite JavaScript, che rimuove semplicemente un elemento del form dal DOM. Quando l’utente invia il form, l’assenza dai dati inviati implicherà una rimozione dall’array finale.

Per maggiori informazioni, vedere Permettere la rimozione di tag.

Attenzione

Si faccia attenzione nell’usare questa opzione quando si include un insieme di oggetti. In questo caso, se un form incluso viene rimosso, sarà correttamente mancante dall’array finale di oggetti. Tuttavia, a seconda della logica dell’applicazione, quando uno di questi oggetti viene rimosso, si potrebbe volerlo cancellare o almeno rimuovere la sua chiave esterna riferita all’oggetto principale. Questi casi non sono gestiti automaticamente. Per maggiori informazioni, vedere Permettere la rimozione di tag.

delete_empty

Nuovo nella versione 2.5: L’opzione delete_empty è stata introdotta in Symfony 2.5.

tipo: Booleano predefinito: false

Se si vogliono rimuovere esplicitamente gli elementi vuoti della collezione dal form, impostare questa opzione a true. Tuttavia, gli elementi esistenti della collezion saranno cancellati se si ha abilitato l’opzione allow_delete. Altrimenti, i valori vuoti saranno mantenuti.

options

tipo: array predefinito: array()

L’array passato al tipo di form specificato nell’opzione type. Per esempio, se si è usato il tipo choice come opzione type (p.e. per un insieme di menù a tendina), si dovrebbe passare almeno l’opzione choices al tipo sottostante:

$builder->add('favorite_cities', 'collection', array(
    'type'   => 'choice',
    'options'  => array(
        'choices'  => array(
            'nashville' => 'Nashville',
            'paris'     => 'Paris',
            'berlin'    => 'Berlin',
            'london'    => 'London',
        ),
    ),
));

prototype

tipo: Booleano predefinito: true

Questa opzione è utile quando si usa l’opzione allow_add. Se true (e se anche allow_add è true), sarà disponibile uno speciale attributo “prototype”, in modo che si possa rendere nella pagina un “template” esempio di come ogni nuovo elemento dovrebbe apparire. L’attributo name dato a tale elemento è __name__. Questo consente l’aggiunta di un bottone “aggiungi” tramite JavaScript, che legge il prototipo, sostituisce __name__ con un nome o numero univoco e lo rende all’interno del form. Quando inviato, sarà aggiunto all’array sottostante, grazie all’opzione allow_add.

Il campo prototipo può essere reso tramite la variabile prototype nel campo collection:

  • Twig
    {{ form_row(form.emails.vars.prototype) }}
    
  • PHP
    <?php echo $view['form']->row($form['emails']->vars['prototype']) ?>
    

Si noti che tutto quello di cui si ha effettivamente bisogno è il widget, ma a seconda di come si rende il form, avere l’intera riga del form potrebbe essere più facile.

Suggerimento

Se si rende l’intero campo collection in una volta sola, la riga del prototipo sarà disponibile automaticamente nell’attributo data-prototype dell’elemento (p.e. div o table) che contiene l’insieme.

Per dettagli su come usare effettivamente questa opzione, vedere l’esempio sopra o Permettere “nuovi” tag con “prototipo”.

prototype_name

tipo: Stringa predefinito: __name__

Se si hanno molti insiemi in un form o, peggio, si hanno insiemi annidati, si potrebbe voler modificare il segnaposto, in modo che i segnaposto senza relazioni non siano sostituiti con il medesimo valore.

type

tipo: stringa o Symfony\Component\Form\FormTypeInterface obbligatorio

Il tipo per ogni elemento dell’insieme (p.e. text, choice, ecc.). Per esempio, con un array di indirizzi email, si userebbe il tipo email. Se si vuole includere un insieme di un qualche altro form, creare una nuova istanza del tipo di form e passarlo in questa opzione.

Opzioni ereditate

Queste opzioni sono ereditate dal tipo form. Non sono elencate tutte le opzioni, solo quelle più attinenti a questo tipo:

by_reference

tipo: Booleano predefinito: true

Se si ha un campo nome, ci si aspetta che setNome() venga invocato sull’oggetto sottostante. In alcuni casi, tuttavia, setNome() potrebbe non essere chiamato. Impostare by_reference assicura che il metodo setNome() sia sempre richiamato.

Per spiegare ciò, ecco un semplice esempio:

$builder = $this->createFormBuilder($article);
$builder
    ->add('title', 'text')
    ->add(
        $builder->create('author', 'form', array('by_reference' => ?))
            ->add('name', 'text')
            ->add('email', 'email')
    )

Se by_reference è true, il codice che segue viene eseguito dietro le quinte quando si invoca il metodo submit() (oppure handleRequest()) sul form:

$article->setTitle('...');
$article->getAuthor()->setName('...');
$article->getAuthor()->setEmail('...');

Notare che setAuthor() non è invocato. L’autore è modificato per riferimento.

Se si configura by_reference a false, all’invio dei dati viene eseguito il seguente codice:

$article->setTitle('...');
$author = $article->getAuthor();
$author->setName('...');
$author->setEmail('...');
$article->setAuthor($author);

Come si vede, quello che by_reference=false veramente implica è forzare il framework a chiamare il setter sull’oggetto relativo.

Similmente, se si usa il tipo collection, dove i tipi di dati nella collection sono oggetti (come per esempio gli ArrayCollection di Doctrine), by_reference deve essere configurata a false, se si vuole che il setter (p.e. setAuthors()) sia richiamato.

cascade_validation

tipo: booleano predefinito: false

Impostare questa opzione a true per forzare la validazione su form inclusi. Per esempio, se si ha un ProductType con incluso CategoryType, impostare cascade_validation a true su ProductType farà validare anche i dati di CategoryType.

Invece di usare questa opzione, si può anche usare il vincolo Valid nel modello, per forzare la validazione di un oggetto figlio memorizzato su una proprietà.

Suggerimento

Per impostazione predefinita, l’opzione error_bubbling è abilitata per il tipo di campo collection, che passa gli errori al form genitore. Se si vogliono allegare gli errori alle posizioni in cui sono effettivamente avvenuti, occorre impostare error_bubbling a false.

empty_data

tipo: mixed

Il valore predefinito è array() (array vuoto).

Questa opzione determina il valore restituito dal campo quando viene selezionato placeholder.

Si può personalizzare a seconda delle esigenze. Per esempio, se si vuole che il campo gender sia impostato a null quando non viene scelto alcun valore, lo si può fare in questo modo:

$builder->add('gender', 'choice', array(
    'choices' => array(
        'm' => 'Maschio',
        'f' => 'Femmina'
    ),
    'required'    => false,
    'placeholder' => 'Scegliere un genere',
    'empty_data'  => null
));

Nota

Se si vuole impostare l’opzione empty_data per l’intera classe del form, vedere la ricetta Configurare dati vuoti per una classe Form.

error_bubbling

tipo: Booleano predefinito: true

Se true, qualsiasi errore per questo campo sarà passato al campo genitore o al form. Per esempio, se impostato a true su un campo normale, qualsiasi errore per il campo sarà collegato al form principale, non al campo stesso.

error_mapping

tipo: array predefinito: empty

Questa opzione consente di modificare il bersaglio di un errore di validazione.

Si immagini di avere un metodo personalizzato, di nome matchingCityAndZipCode, che valida se una città e un codice postale corrsispondano. Purtroppo non c’è un campo “matchingCityAndZipCode” nel form, quindi tutto ciò che può fare Symfony è mostrare l’errore in cima al form stesso.

Con la mappatura personalizzata degli errori, si può fare meglio: mappare l’errore al campo della città, in modo che sia mostrato accanto a esso:

public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults(array(
        'error_mapping' => array(
            'matchingCityAndZipCode' => 'city',
        ),
    ));
}

Ecco le regole per i lati destro e sinistro della mappatura:

  • I lato sinistro contiene i percorsi delle proprietà.
  • Se la validazione è generata su una proprietà o metodo di una classe, il suo percorso è semplicemente “nomeProprietà”.
  • Se la validazione è generata su un elemento di un array od oggetto ArrayAccess, il percorso è [nomeIndice].
  • Si possono costruire percorsi innestati, concatenandoli, separando le proprietà con dei punti. Per esempio: addresses[work].matchingCityAndZipCode
  • Anche il lato sinistro della mappatura accetta un punto ., che si riferisce al campo stesso. Questo significa che un errore aggiunto al campo è aggiunto invece al dato campo innnestato.
  • Il lato destro contiene semplicemente i nomi dei campi nel form.

label

tipo: stringa predefinito: “indovinato” dal nome del campo

Imposta la label usata per la resa del campo. La label può anche essere inserita direttamente all’interno del template:

{{ form_label(form.name, 'Il tuo nome') }}

label_attr

tipo: array predefinito: array()

Imposta gli attributi HTML per l’elemento <label>, che saranno usati durante la resa della label di un campo. È un array associativo con gli attributi HTML come chiavi. Questi attributi possono anche essere impostati all’interno del template:

  • Twig
    {{ form_label(form.name, 'Nome', {'label_attr': {'class': 'CUSTOM_LABEL_CLASS'}}) }}
    
  • PHP
    echo $view['form']->label(
        $form['name'],
        'Nome',
        array('class', 'CUSTOM_LABEL_CLASS')
    );
    

mapped

tipo: booleano

Se si vuole che il campo sia ignorato durante la lettura o la scrittura dell’oggetto, si può impostare l’opzione mapped a false

required

tipo: booleano predefinito: true

Se true, sarà reso un attributo required HTML5. La label corrispondente sarà anche resa con una classe required.

L’attributo è indipendente dalla validazione del form. Nel caso migliore, se si lascia che Symfony indovini il tipo di campo, il valore di questa opzione sarà indovinato dalle informazioni di validazione.

Variabili di campo

Variabile Tipo Uso
allow_add Booleano Il valore dell’opzione allow_add.
allow_delete Booleano Il valore dell’opzione allow_delete.