sfDoctrineGuardPlugin: Signup, Validation and Profile
by rp
I am a big fan of using plugins in symfony especially that are well written and save me a bunch of time implementing some bog standard functionality. SfDoctrineGuardPlugin is an example of one such plugin but due the generic nature of the signup process there is a lot left to be implemented even after using the plugin. There are various other plugins that try and bridge the gap but i find myself stuggling with them especially when they try and do a lot more than I actually mode. So I thought I would document my process of implementing the signup module and document the bits of information that I found were missing.
Note: This process works for symfony 1.2 onwards (currently using 1.4)
So the first thing to do is write a custom form class that extends the sfGuardUser.class.php were you can add your additional fields and validation like password, password_confirmation, accepting terms and conditions etc. Also this is the place where you would need to disable the fields that you don’t in the register form. The class below is called RegisterForm.class.php and i have tried to keep it pretty documented as possible.
<?php
/**
* RegisterForm for signup process and requires
* sfGuardPlugin
*
* @author Rajat Pandit
*/
class RegisterForm extends sfGuardUserForm {
public function configure()
{
// remove the fields that you no longer need
unset(
$this['is_active'],
$this['is_super_admin'],
$this['updated_at'],
$this['groups_list'],
$this['permissions_list'],
$this['last_login'],
$this['created_at'],
$this['salt'],
$this['algorithm']
);
// change the name format as we dont want to the
// world to know that we are using sfGuardPlugin
$this->widgetSchema->setNameFormat('signup[%s]');
// Setup proper password validation with confirmation
$this->widgetSchema['password'] = new sfWidgetFormInputPassword();
$this->widgetSchema['password_confirmation'] = new sfWidgetFormInputPassword();
$this->widgetSchema['toc'] = new sfWidgetFormInputCheckbox();
$this->widgetSchema->setLabel('username', 'Email');
$this->widgetSchema->setLabel(
'password_confirmation',
'Confirm Password'
);
$this->widgetSchema->setLabel(
'toc',
'I agree to the Terms of Service, Privacy, & Refund policies'
);
$this->validatorSchema['username'] = new sfValidatorEmail(
array(),
array(
'required' => 'Please provide an email address',
'invalid' => 'Please enter a valid email address'
)
);
$this->validatorSchema['toc'] = new sfValidatorBoolean(
array('required' => true),
array(
'required' =>
'You need to accept the terms and conditions to proceed')
);
$this->validatorSchema['password_confirmation'] = clone $this->validatorSchema['password'];
$this->validatorSchema['password']->setOption('required', true);
$this->widgetSchema->moveField('password_confirmation', 'after', 'password');
$this->mergePostValidator(new sfValidatorSchemaCompare('password',
sfValidatorSchemaCompare::EQUAL, 'password_confirmation',
array(),
array(
'required' => 'You are required to enter a password'
)));
}
}
The next thing is to create a template that will represent this form. I shall leave that as an exercise for the user. You can read more about it on the symfony documentation section.
Now in your action, you need to write the bit of code that will send the form object to the view and then handle saving the object. The other interesting thing I am doing in my action is to create and update the profile table as well. The schema that i have used for profile table is as follows, its important to have all the values in the relations section to be setup properly otherwise the sfGuardUser()->getProfile() won’t work.
sf_guard_user_profile:
actAs:
Timestampable: ~
columns:
user_id: { type: integer(4), notnull: true}
first_name: { type: string(200)}
last_name: { type: string(200)}
token: { type: string(32) }
relations:
User:
class: sfGuardUser
local: user_id
foreign: id
foreignAlias: Profile
foreignType: one
type: one
onDelete: CASCADE
The code for the action is as follows:
public function executeRegister(sfWebRequest $request)
{
$params = $request->getParameter('signup');
$this->form = new RegisterForm();
if ($request->isMethod('post')) {
$this->form->bind($params);
// save the object is the user
// details entered a correct and everything else is place.
if ($this->form->isValid()) {
$this->form->save();
// get the user object
$user = $this->form->getObject();
// get the normal user group
$normal_user_group = sfConfig::get('app_config_normal_user');
// deactivate the account, till the user verifies the account
$user->setIsActive(false);
// set the activation token
$profile = $user->getProfile();
$profile->setToken(md5(time()));
// notify the user about the signup
$this->notifySignup($user, $profile);
$user->save(); // save the record in the database
$this->getUser()->setFlash('register_confirm', true);
}
}
}
Just as an additional note, this is not a copy-paste-use solution, this is just a pointer of how you can get something working and then write additional code in the right place to extend things. Leave a comment if you have a question and I would be happy to answer it.
Just what I was looking for, thanks
Hello Rajat, I am trying to implement a registration process with email activation using sfDoctrineGuardPlugin. I’m just starting to use symfony and my knowledge is very limited for now. Thanks to your explanation, it gives me some light on how to approach this. However, there are some questions I would like to ask you regarding the code.
1. Can you explain more about this
$profile = $user->getProfile();
2. Where in the code that it actually sends notification email to user? I don’t see any email field in database and where do you check that user has activated the account?
Thank you in advance for your help
~n
1. Can you explain more about this
$profile = $user->getProfile();
returns an instance of sf_guard_user_profile table record, if it doesn’t contain one then it makes an entry and then return on.
2. Where in the code that it actually sends notification email to user? I don’t see any email field in database and where do you check that user has activated the account?
ok so notification has to be your own implementation, in this case i am using the username field to store the e-mail and then sending emails from there, its down to you how you want to go about implementing it. you can use the username field to store the username and the add a new column ‘email’ in the profile table sf_guard_user_profile table. its down to you.
HTH
- RP
Rajat, thanks for the respond. It’s really helpful.
Sorry, one more question,
what does the setFlash() function do in
“$this->getUser()->setFlash(‘register_confirm’, true)” ?
is it a function that you created yourself?
Thanks,
~n
nope. its part of the symfony API. Its useful to set value that you want to be automatically be cleared in the next request
http://www.symfony-project.org/api/1_4/sfUser#method_setflash
New to symfony myself – love the technology/philosophy/etc but the incomplete documentation is the biggest barrier IMHO.
Thanks for the post. What is token for in profile? Sounds redundant to what Guard already does.
thanks for your comments Roger. The token in this context is just to add an additional step for validation of email. The user gets sent an email with a link to activate it. The link can contain this token which can be cleared from the table once the account has been confirmed. If you don’t need that additional step, free free to take that bit out.
thanks Rajat! Great post. Wonder what symfony 2.0 will be like
Hope one day someone will extend this great how to with implementations of the notifySignup() method and executeActivate() action
Do you think the activate action should take username and the md5 as the links parameter or just the md5?
i.e. server/user/jd/code/
or
server/code/