Click here to Skip to main content
15,395,065 members
Articles / Programming Languages / PHP
Posted 7 Jun 2014

Tagged as


1 bookmarked

Forms in Symfony 2+ without Doctrine

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
7 Jun 2014CPOL2 min read
Forms in Symfony 2+ without Doctrine


This tip presents the steps you have to take to create a functional form using the Symfony Form Component and the Symfony framework without the use of Doctrine ORM.


For an easy understanding of this tip, you can read about the structure of the Symfony framework and how it works here and you can grab a copy of the framework from here.

Using the Code

To use the code from this tip, you have to copy and paste the content into the indicated spots from the Symfony framework structure (model, form, controller, view).

In this code, I use the Address model that you can grab from the attached documentation (src/Demo/TestBundle/Model/Address.php file).


Step 0

Suppose we have to create an add/edit address form and integrate it into the Symfony Framework version 2.5 already installed somewhere on your web server and properly configured with the following structure:

Image 1

Before any action, we should define our routes:

  • This code comes from the src/Demo/TestBundle/Resources/routing.yml file:
    pattern:  /add-address
    defaults: { _controller: DemoTestBundle:Address:addAddressForm }
        _method: GET|POST

    pattern:  /localities/{id}
    defaults: { _controller: DemoTestBundle:Address:getLocalities }
        _method: GET
        id: (\d+)

Step 1

Create the form class generator using the Symfony Form Component (AddressForm.php)

  • This file should be saved under the src/Demo/TestBundle/Form folder, in the AddressForm.php file
  • Here I listed only the buildForm() method of the AddressForm form generator class (the rest of the class you can find in the archive)
  • I split the code into separate section to explain what it does
    - $_data property is used when we edit a form 
    - this property will be filled with information from an address, send from the controller to the form generator class

//the buildForm() method of the AddressForm form generator class
   public function buildForm(FormBuilderInterface $builder, array $options){
       $regions = Address::getAllRegions();

       //get localities for editing
       $localities = array();
           $localities = Address::getLocalitiesByRegion($this->_data['region_id']);

I set the options for the region and locality form inputs here because they are needed in many places in this method.

The locality content comes from an Ajax request and I attached a form event listener to it (see below how).

$options =
            => array(
                    'label'             => 'County*',
                    'choices'           => $regions,
                    'data'              => !empty($this->_data['region_id'])?$this->_data['region_id']:0,
                    'empty_value'       => 'Pick a county',
                    'attr'              => array('style'=>'width:210px'),
                    'trim'              => true,
                    'required'          => false,
                    'constraints'       => array(new NotBlank(array('message'=>'Please pick a county!'))),
                    'invalid_message'   => 'Please pick a county!'
            => array(
                    'label'            => 'Locality*',
                    'choices'           => !empty($localities)?$localities:array(),
                    'data'              => !empty($this->_data['locality_id'])?$this->_data['locality_id']:0,
                    'empty_value'       => 'Pick a locality',
                    'required'          => false,
                    'attr'              => array(
                                                'style'    =>'width:210px',
                                                'disabled'  => !empty($this->_data['locality_id'])?false:true
                    //'label_attr'        => array('style'=>'margin-left:30px;'),
                    'trim'              => true,
                    'constraints'       => array(new NotBlank(array('message'=>'Please pick a locality!'))),
                    'invalid_message'   => 'Please pick a locality!',
                    'auto_initialize'   => false

The form elements are listed in the order they are created, so I begin with the hidden id input and I finish with the zip code input.

$builder->add('id', 'hidden', array('data'=>!empty
                    'label'             => 'Address*',
                    'attr'              => array(
                                                'style'    => 'width:210px',
                                                //this should customize HTML5 validation messages BUT is not working
                                                'oninvalid'=> 'setCustomValidity("")',    
                                                'onfocus'  => 'setCustomValidity("")'
                    //default is required ==> force HTML5 validation by browsers
                    'required'          => true,  
                    'trim'              => true,
                    'data'              => !empty($this->_data['address'])?$this->_data['address']:'',
                    //needed to acces form errors in controller, for example (not to display them directly in Twig)
                    //'error_bubbling'    => true,   
                    'constraints'       => array(
                                                new NotBlank(array('message'=>'Please fill the address!')),
                                                new Length(
                                                            'min'      =>10,
                            because in SF 2.2 is a BUG with this validator
                            ==> tries to apply transChoice
                                                            Please fill minimum %s characters!|Please fill minimum 10 characters!',
                                                            'max'       =>200,
                                                            Please fill maximum %s characters!|Please fill maximum 200 characters!'
                    'invalid_message'   => 'Please fill the address!'
$builder->add('region', 'choice', $options['region']);
$builder->add('locality', 'choice', $options['locality']);

As I mentioned above, I attached an event listener to the region form element. When it is changed, the locality input is populated with content, based on the selected region id.

$extVaribles = array('factory'  => $builder->getFormFactory(),
                    'options'   => $options,
                    'self'      => $this);
$regionModifier = function (FormInterface $form, $region) use (&$extVaribles){
    $regionData = !empty($extVaribles['options']['region']['data'])?
    $extVaribles['options']['locality']['choices'] = 
    $extVaribles['options']['region']['data']      = $regionData;
    $form->add('locality', 'choice', $extVaribles['options']['locality']);

$builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) use ($regionModifier){
                                                        $region = $event->getData();
                                                        $regionModifier($event->getForm(), $region);
$builder->get('region')->addEventListener(FormEvents::POST_SUBMIT, function(FormEvent $event) use ($regionModifier){
                                                        $region = $event->getForm()->getData();
                                                        $regionModifier($event->getForm()->getParent(), $region);

This is the last input element of the address form, the zip code.

$builder->add('postcode', 'text', array('label'     => 'Zip code',
                 'data'      => !empty($this->_data['zip_code'])?$this->_data['zip_code']:'',
                 'attr'      => array('style'=>'width:210px !important;'),
                 'required'  => false,
                 'trim'      => true));
}    //this acolade is from the start of the buildForm() method

Step 2

Create the view where we are going to display the form (address.html.twig).

  • This file should be saved under the src/Demo/TestBundle/Resources/views/Address folder
  • Here I listed the form generation; the rest of the code is in the address.html.twig view file
{# overwrite the form_rows block and the form_errors block #}
{% block form_rows %}
    {% spaceless %}
        {% for child in addressForm %}
            {# check for input type: if hidden do not display label #}
            {% if != "hidden" %}
                label can be translated from form class, by specifying the translation domain
                and then add the text in that file
                {{ form_label(child) }}
                {#{{ form_label(child, child.vars.label|trans) }}#}   
                {# to translate label from TWIG; NOT translated by default #}
            {% endif %}
            {{ form_widget(child) }}
            {% if form_errors(child) %}
                {% block form_errors %}
                    {% spaceless %}
                        {# domain for errors/validation rules is #}
                        {{ form_errors(child)|striptags }}
                    {% endspaceless %}
                {% endblock form_errors %}
            {% endif %}
        {% endfor %}
    {% endspaceless %}
{% endblock form_rows %}    

Step 3

Create the controller that takes care of the form processing (AddressController.php)

  • This file should be saved under the src/Demo/TestBundle/Controller folder
  • Here I listed the address form processing method; the rest of the code is in the AddressController.php controller file
public function addAddressFormAction() {
    $form = $this--->createForm(new AddressForm());

            //do smth
            echo 'VALID';
            //get form errors
            $formErrors = array();
            foreach($form->all() as $item){
                if(is_array($item->getErrors()) && count($item->getErrors()) > 0){
                    $localErrors = explode('ERROR: ', $item->getErrorsAsString());
                    $formErrors[$item->getName()] = !empty($localErrors[1])?$localErrors[1]:'';

            //set errors into a notification handler or send them to the view in order to display them

    return $this->render('DemoTestBundle:Address:address.html.twig',

Step 4

Your application should look like this:

Image 2

or like this, when there are validation errors:

Image 3

Points of Interest

Client-side validation of Symfony forms:


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author
United States United States
No Biography provided

Comments and Discussions

-- No messages could be retrieved (timeout) --