Symfony 1.2 and multipage form - wizard

This time i want to give you simple example about how to create multipage (wiazard) form using great Symfony 1.2 form framework. It would be simple 3 steps registeration form with validation after each step. In this example i’m using propel but it would also work great wiht Doctrine. Lets start!

First, we need to start a new project, create frontend application and multiPageForm module (from the command line prompt):

symfony generate:project multipageFormApp

symfony generate:app frontend

symfony generate:module frontend multiPageForm

Then, after setting database connection with:

symfony configure:database “mysql:host=localost;dbname=your_db” user pass

put the follwing code into the schema.yml:


propel:
  user:
    id:
    username: { type: varchar(30), index: unique, required: true }
    password:  { type: varchar(30), required: true }
    account_type: { type: smallint, default: 0, required: true}
    email: { type: varchar(30), required: true }
    name: { type: varchar(30), required: true}
    surname: { type: varchar(30), required: true}
    address: varchar(50)
    city: varchar(30)
    zipcode: varchar(10)
    country: varchar(30)
    phone_number: {type: varchar(30), required: true}
    is_confirmed: { type: boolean, default: 0 }
    last_login: timestamp
    updated_at:
    created_at:

Then call:

symfony propel:build-all

or each propel task manually (build-sql, insert-sql, build-model, build-forms, etc).

Simply call:

symfony cc

and we can start coding.

Symfony generated our user form for us. Now we need to split UserForm into parts, for example UserFormPage1, UserFormPage2, UserFormPage3, etc. You can put into each part what ever you want but for me it make sens to do it like this:

Page 1:
- username
- password
- password again
- account type

Page 2:
- name
- surname
- e-mail
- e-mail again
- address
- city
- country
- phone number

Page 3:
- no fields but just confirmation.

To create our forms we will use unsetAllExcept function introduced here.

UserFormPage1:


<?php

class UserFormPage1 extends BaseUserForm {

	public function configure() {

		$this->unsetAllExcept(array(
			'username',
			'password',
			'account_type'
		));

		$accountTypeChoices = array(0 => 'Firm',
									1 => 'Person');

		 # Edit defualt widget Schema
		$this->widgetSchema['step'] = new sfWidgetFormInputHidden();

		$this->widgetSchema['password'] = new sfWidgetFormInputPassword();

		$this->widgetSchema['password_again'] = new sfWidgetFormInputPassword();

		$this->widgetSchema['account_type'] = new sfWidgetFormSelectRadio(array(
				'choices' => $accountTypeChoices )); 

		$this->widgetSchema->moveField('password_again', 'after', 'password');

		 # Edit default validator Schema
    	$this->validatorSchema['password_again'] = clone $this->validatorSchema['password'];

    	$this->mergePostValidator(new sfValidatorSchemaCompare('password', sfValidatorSchemaCompare::EQUAL, 'password_again', array(), array('invalid' => 'The two passwords must be the same.')));

    	$this->validatorSchema->addOption('allow_extra_fields', true);

    	$this->setDefaults(array(
    		'account_type' => 0,
			'step' => 1
    	));
	}
}

UserFormPage2:


<?php

class UserFormPage2 extends BaseUserForm {

	public function configure() {

		$this->unsetAllExcept(array(
			'email',
			'name',
			'surname',
			'address',
			'city',
			'zipcode',
			'country',
			'phone_number'
		));

		 # Edit default widget Schema
		$this->widgetSchema['step'] = new sfWidgetFormInputHidden();

		$this->widgetSchema['email_again'] = new sfWidgetFormInput();

		$this->widgetSchema->moveField('email_again', 'after', 'email');

		 # Edit default validator Schema
		$this->validatorSchema['email'] = new sfValidatorEmail();

		$this->validatorSchema['email_again'] = clone $this->validatorSchema['email'];

		$this->mergePostValidator(new sfValidatorSchemaCompare('email', sfValidatorSchemaCompare::EQUAL, 'email_again', array(), array('invalid' => 'The two e-mails must be the same.')));

		$this->validatorSchema->addOption('allow_extra_fields', true);

		$this->setDefaults(array(
			'step' => 2
    	));
	}
}

UserFormPage3:


<?php

class UserFormPage3 extends BaseUserForm {

	public function configure() {

		$this->unsetAllExcept(array(
			'username',
			'password',
			'account_type',
			'email',
			'name',
			'surname',
			'address',
			'city',
			'zipcode',
			'country',
			'phone_number'
		));

		 # Edit defualt widget Schema
		$this->widgetSchema['step'] = new sfWidgetFormInputHidden();

    	$this->validatorSchema->addOption('allow_extra_fields', true);

    	$this->setDefaults(array(
			'step' => 3
    	));
	}
}

Everything should be clear as far.

Now we can put them together to work. In index action of multiPageForm module we should add follwoing code:


<?php

class multiPageFormActions extends sfActions
{
 /**
  * Executes index action
  *
  * @param sfRequest $request A request object
  */
  public function executeIndex(sfWebRequest $request)
  {
     # 3 steps form
  	$confirmStep = 3;

    if($request->isMethod('post') && $request->hasParameter('user')) {

    	 # Get current form step
    	$step = $request->getParameter('user[step]');
    	$nextStep = $step + 1;

    	 # When user click "back" button
    	if ($request->hasParameter('_back')) {

    		$currForm = $currForm = 'UserFormPage' . –$step;

    		$this->form = new $currForm;
    		$this->form->bind($this->getUser()->getAttribute($step));

    	 # When user click "next" button
    	} else {

    		$currForm = 'UserFormPage' . $step;
    		$this->form = new $currForm;

	    	switch($step){

	    		 # Final step - confirmation
	    		case $confirmStep:

    				if( $this->form->bindAndSave($this->getValuesToStep($confirmStep))) {

    					$this->getUser()->setFlash('register_success', true);
    					$this->redirect('multiPageForm/index');
    				}

    				break;

    			 # each step
	    		default:

	    			 # Next step form
	    			$nextForm = 'UserFormPage' . $nextStep;

	    			$this->form->bind($request->getParameter('user'));

	    			if($this->form->isValid()) {

	    				 # Save values to user session
	    				$this->getUser()->setAttribute($step, $request->getParameter('user'));

	    				$this->form = new $nextForm;

	    				 # if next Step is a confirm step - set values
	    				if ($nextStep == $confirmStep) {

	    					$values = $this->getValuesToStep($confirmStep);
	    					$values['step'] = $confirmStep;
	    					$this->form->bind($values);

	    					$this->confirmStep = true;
	    				}
	    			}
	    	}
    	}

    }else{

    	$this->form = new UserFormPage1();
    }
  }

  protected function getValuesToStep($step) {

  	$values = array();

  	for ($i=1; $i<$step; $i++) {

  		$values = array_merge($values, $this->getUser()->getAttribute($i) );
  	}

  	return $values;
  }
}

This action is used to handle all steps of our simple registration form. Data from forms is stored in user session and after confirmation (step 3) saved to database.

In template: indexSuccess, it would be someting like this:


	<?php if ($sf_user->hasFlash('register_success')): ?>

		<div class="success">
			You have been successfully registered.
		</div>

	<?php else: ?>

		<?php if ($form->hasErrors()): ?>

			<div class="error">
				Sorry, but there are some errors. Chceck your form again.
			</div>

		<?php endif; ?>

		<form action="<?php echo url_for('multiPageForm/index') ?>" method="POST" > 

		<table>

			<?php if(isset($confirmStep)): ?> 

				<?php echo $form->renderHiddenFields(); ?>

				<?php foreach($form as $field): ?>

					<?php if(!$field->isHidden()): ?>
						<tr>
							<th><?php echo $field->renderLabel() ?></th>
							<td><?php echo $field->getValue() ?></td>
						</tr>
					<?php endif; ?>

				<?php endforeach; ?>

			<?php else: ?>

				<?php echo $form; ?>

			<?php endif; ?>

		</table>

		<?php if ($form['step']->getValue() != 1): ?>

			<input type="submit" name="_back" value="Back">

		<?php endif;  ?>

		<input type="submit" name="_next" value="Next">

		</form>

	<?php endif; ?>

In this template we have also some success ane error messages after retrieving each step.
In real world application we would also have to add some css style to our form to make it more user frendly. We can also add something like “localizator” - it’d be really usefull in forms with more then 2 steps.

22 Responses to “Symfony 1.2 and multipage form - wizard”


  1. 1 Will

    Hi

    Great tutorial, it all worked great until I got to processing the second page. If it was invalid then it would tell me to correct it but if it was valid then I always had these errors as shown below. I have tried repeatedly retrying the form and clearing cache but every time I get the same problem. I really like the way you broke down the form I by unsetting the parts so hopefuly you can give me a pointer to fix this part. The problem seems to be on line

    $values = array_merge($values, $this->getUser()->getAttribute($i) );

    Warning: array_merge() [function.array-merge]: Argument #2 is not an array in C:\xampp\htdocs\xampp\symfony\multipageFormApp\apps\frontend\modules\multiPageForm\actions\actions.class.php on line 97

    Warning: array_merge() [function.array-merge]: Argument #1 is not an array in C:\xampp\htdocs\xampp\symfony\multipageFormApp\apps\frontend\modules\multiPageForm\actions\actions.class.php on line 97

    Warning: array_merge() [function.array-merge]: Argument #1 is not an array in C:\xampp\htdocs\xampp\symfony\multipageFormApp\apps\frontend\modules\multiPageForm\actions\actions.class.php on line 97

    Warning: Cannot modify header information - headers already sent by (output started at C:\xampp\htdocs\xampp\symfony\multipageFormApp\apps\frontend\modules\multiPageForm\actions\actions.class.php:97) in C:\xampp\php\PEAR\symfony\response\sfWebResponse.class.php on line 335

    Warning: Cannot modify header information - headers already sent by (output started at C:\xampp\htdocs\xampp\symfony\multipageFormApp\apps\frontend\modules\multiPageForm\actions\actions.class.php:97) in C:\xampp\php\PEAR\symfony\response\sfWebResponse.class.php on line 349
    Thanks for all the help

    Will

  2. 2 Kamil Adryjanek

    Please, send me your code and i will do my best to help you.

  3. 3 Will

    hi

    sorry about the slow reply, I didn’t notice that you had posted again (my mistake). Would you like to see the whole project? I set up a fresh symfony project for this tutorial as it would be a really useful feature for me to be able to use.
    Anyway I could zip it and send to you if you give me your email address or I could post the pages you are interested in here. Anyway my email is will_melbourne at hotmail . Let me know what suits you. Thanks so much for the help

    Will

  4. 4 FXDeveloper

    Very classy

  5. 5 Alexander

    Hi, I like the tutorial, but I face many troubles. I can see the tutorial is for advanced symfony programmers. but I am just fresh in the field. however I have face some difficulties, how do I call the url to display the form? secondly, which are the files do we need to modify?

  6. 6 Kamil Adryjanek

    Hi. Maybe not for advanced developers but for intermediate. To display the form you need to call for example http://localhost/multiPageFormApp/web/frontend_dev.php/multiPageForm. Files that need to be modified:
    1. schema.yml;
    2. multiPageFormActions.class.php;
    3. multiPageFormSuccess.php.
    Files that need to be created:
    1. UserFormPage1.class.php;
    2. UserFormPage2.class.php;
    3. UserFormPage3.class.php.
    And that’s all. If you have any other question just write.
    Cheers, Kamil

  7. 7 Scott

    there is a new method being introduced in symfony 1.3 called “useFields()”, check out the notes in http://trac.symfony-project.org/changeset/17824

  8. 8 Kamil Adryjanek

    Thanks, really good to know.

  9. 9 Nitin Chopra

    thanx dude for such a good article. Currently i am devloping a site using symfony, i had a very limited knowledge of symfony. i have to do this kind of thing. Rest i got an idea, i’ll cdutomize it a per requirement. thanx a lot for sharing this piece of information.

  10. 10 pabz

    First thing, I’m new to symfony so please bear with me. My question is where do you declared the field in $request->getParameter(’user’)? I’m trying these wizard using doctrine? can you guide me please…thanks

  11. 11 Kamil Adryjanek

    Hi, i don’t really know about which field you are talking about. If you mean user - it’s group of fields. Default name is user because of its model name “user”. You can change it calling sfWidgetSchema class method setNameFormat, for example:
    $this->widgetSchema->setNameFormat(’profile[%s]‘). Then in action you need to call $request->getParameter(’profile’) to get all form fields. Cheers

  12. 12 pabz

    Hi, thank you very much for quick response. i think i get it now, though i’m not able to test it coz when i tried sending the form it’s saying that widget ’step’ does not exist. After researching I thought the addOption method would resolve it but I’m still giving me the same error. Any thoughts?

  13. 13 pabz

    hi again, sorry to bug you too much. But i tried changing my form action to new instead of index, since it is “new” that i use to start the page1. After doing so, i receive these error..”Empty module and/or action after parsing the URL “/platform/new” (/).” Platform is the name of my model and below is my routing.yml.
    platform:
    class: sfDoctrineRouteCollection
    options: { model: Platform }

    platform_show_name:
    url: /platform/:slug
    class: sfDoctrineRoute
    param: { module: platform, action: show }
    options:
    model: Platform
    type: object
    method_for_query: retrieveUndeleted

    platform_show_name_id:
    url: /platform/:name_slug/:id
    class: sfDoctrineRoute
    options:
    model: Platform
    type: object
    method_for_query: retrieveUndeleted
    param: { module: platform, action: show }
    requirements:
    id: \d+
    sf_method: [get]

    platform_list:
    url: /platform/
    param: { module: platform, action: index }

    Hope you could help me with it?

  14. 14 Kaore

    Hi, here is another post describing a method to spread a form over multiple pages… It uses symfony 1.4 and doctrine: http://tech.cibul.org/a-way-to-make-multipage-forms-with-symfony/

  15. 15 Jhon ?

    Hi, I use Symfony 1.4.3 and $request->getParameter(’user[step]‘); has gone. How to get current step without getParameter ?

  16. 16 Kamil Adryjanek

    Hi, you mean that this method is not used anymore?
    You are wrong: http://www.symfony-project.org/api/1_4/sfRequest#method_getparameter
    Cheers

  17. 17 Jhon ?

    Thank you for quick response :) Yes but when I use this $step = $request->getParameter(’user_list[step]‘); And I check with simple echo it doesn’t work. Yes in 1.4 has this method, but only for routing. Or ?

  18. 18 Jhon ?

    Sorry, my mistake. The function works with the following methods.
    const GET = ‘GET’;
    const POST = ‘POST’;
    const PUT = ‘PUT’;
    const DELETE = ‘DELETE’;
    const HEAD = ‘HEAD’;

    Then why can not I take the value of the field?

  19. 19 Kamil Adryjanek

    It depends on your model and form. If you want to use:
    $request->getParameter(’user_list[step]‘);
    The name format of your form should be set to “user_list[%s]“. Please, check it? To set the name format call:
    $this->widgetSchema->setNameFormat(”user_list[%s]“);
    in your form. More info: http://www.symfony-project.org/api/1_4/sfWidgetFormSchema#method_setnameformat

  20. 20 Jhon ?

    Yes, I now. Look at this.
    print_r($request->getParameterHolder()->getAll());
    Array ( [user_lists] => Array ( [_csrf_token] => 2f2f08e2e4df449477c68ed9d1aa2589 [title] => fdfdfdsfs [body] => [image] => [id] => [step] => 1 ) [_next] => Next [module] => lists [action] => index )
    Here my step is step => 1, but I can’t catch it when use $request->getParameter(’user_lists[step]‘);

    Sorry for my spam.

  21. 21 Jhon ?

    This is my solution
    $UserLists = $request->getParameter(’user_lists’);
    $step = $UserLists['step'];

  22. 22 Derick Weekes

    This an Great post, I will be sure to save this post in my Reddit account. Have a awesome day.

Leave a Reply

PodglÄ…d komentarza:




About me:

  • PHP programmer
  • Symfony developer
  • Zend framework developer

Categories: