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.
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
Please, send me your code and i will do my best to help you.
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
Very classy
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?
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
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
Thanks, really good to know.
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.
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
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
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?
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?
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/
Hi, I use Symfony 1.4.3 and $request->getParameter(’user[step]‘); has gone. How to get current step without getParameter ?
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
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 ?
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?
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
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.
This is my solution
$UserLists = $request->getParameter(’user_lists’);
$step = $UserLists['step'];
This an Great post, I will be sure to save this post in my Reddit account. Have a awesome day.