Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / PHP

Forms in Symfony 2+ without Doctrine

5.00/5 (1 vote)
7 Jun 2014CPOL2 min read 15K  
Forms in Symfony 2+ without Doctrine

Introduction

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.

Background

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

Implementation

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:
PHP
demo_test_add_address:
    pattern:  /add-address
    defaults: { _controller: DemoTestBundle:Address:addAddressForm }
    requirements:
        _method: GET|POST

demo_test_localities:
    pattern:  /localities/{id}
    defaults: { _controller: DemoTestBundle:Address:getLocalities }
    requirements:
        _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
PHP
<?php
/*
    - $_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();
       ksort($regions);

       //get localities for editing
       $localities = array();
       if(!empty($this->_data['region_id'])){
           $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).

PHP
<?php
$options =
    array(
        'region'
            => 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!'
                    ),
        'locality'
            => 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.

PHP
<?php
$builder->add('id', 'hidden', array('data'=>!empty
($this->_data['id'])?$this->_data['id']:0));
 
$builder->add('street_address',
                'text',
                array(
                    '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(
                                                        array(
                                                            'min'      =>10,
                                                            /*
                            because in SF 2.2 is a BUG with this validator
                            ==> tries to apply transChoice
                            */
                                                            'minMessage'=>'
                                                            Please fill minimum %s characters!|Please fill minimum 10 characters!',
                                                            'max'       =>200,
                                                            'maxMessage'=>'
                                                            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.

PHP
<?php
$extVaribles = array('factory'  => $builder->getFormFactory(),
                    'options'   => $options,
                    'self'      => $this);
$regionModifier = function (FormInterface $form, $region) use (&$extVaribles){
    $regionData = !empty($extVaribles['options']['region']['data'])?
    $extVaribles['options']['region']['data']:$region;
    $extVaribles['options']['locality']['choices'] = 
    $extVaribles['self']->getLocalities($regionData);
    $extVaribles['options']['region']['data']      = $regionData;
    unset($extVaribles['options']['locality']['attr']['disabled']);
    
    $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.

PHP
<?php
$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
PHP
{# 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 child.vars.name != "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 validators.bg.xlf #}
                        {{ 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
PHP
<!--?php
public function addAddressFormAction() {
    $form = $this--->createForm(new AddressForm());

    if($this->get('request')->isMethod('POST')){
        $form->handleRequest($this->get('request'));
        if($form->isValid()){
            //do smth
            echo 'VALID';
        }else{
            //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',
    array('addressForm'=>$form->createView()));
}
?>

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:

License

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