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.