sfGuardPlugin is pretty awesome. It allows a symfony developer the ability to quickly implement a user login and access control system. However, there is an issue with respect to telling the plugin whether to use the username or the email column for validating user submitted input. After some Googling I found a few sites which forced sfGuardPlugin to use email rather than username, but only after quite a bit of work. What I am going to show will accomplish the same end goal, but with minor changes.

First, lets take a look at the calling code that determines how the user submitted login information will be validated.


// plugins/sfDoctrineGuardPlugin/lib/validator/sfGuardValidatorUser.class.php

protected function doClean($values)
{
    $username = isset($values[$this->getOption('username_field')]) ? $values[$this->getOption('username_field')] : '';
    $password = isset($values[$this->getOption('password_field')]) ? $values[$this->getOption('password_field')] : '';

    $allowEmail = sfConfig::get('app_sf_guard_plugin_allow_login_with_email', true);
    $method = $allowEmail ? 'retrieveByUsernameOrEmailAddress' : 'retrieveByUsername';

    // don't allow to sign in with an empty username
    if ($username)
    {
        if ($callable = sfConfig::get('app_sf_guard_plugin_retrieve_by_username_callable'))
        {
            $user = call_user_func_array($callable, array($username));
        } else {
            $user = $this->getTable()->retrieveByUsername($username);
        }
        // user exists?
        if($user)
        {
            // password is ok?
            if ($user->getIsActive() && $user->checkPassword($password))
            {
                return array_merge($values, array('user' => $user));
            }
        }
    }

    if ($this->getOption('throw_global_error'))
    {
        throw new sfValidatorError($this, 'invalid');
    }

    throw new sfValidatorErrorSchema($this, array($this->getOption('username_field') => new sfValidatorError($this, 'invalid')));
}

We are not going to make changes to this code because it’s core code and would be overridden every time you update sfGuardPlugin, rather we are just going to dissect it a bit to see what changes we need to make to another file, and why.

line 08: The code is looking for ‘allow_login_with_email' within our app.yml file, and if it doesn't exist, defaults to true.

line 09: What is the purpose of this line? $method is being set, but is never used. This should be removed in a future iteration.

line 14: Apparently there is the flexibility to define your own callback function by defining retrieve_by_username_callable within our app.yaml file, but if you look at line 16, it forces us to use a function rather than a method.

line 18: Finally, the magic method which determines which field will be used in determining a user’s login information (email or username). But notice the problem here? The code is calling getTable()->retrieveByUsername($username), as far as I know, table classes are supposed to be containers for static methods which help us interface with their instantiated object counterparts. Whatever, we are going to overlook this.

Ok, now for the change YOU need to make to use email rather than username.


// lib/model/doctrine/sfDoctrineGuardPlugin/sfGuardUserTable.class.php

class sfGuardUserTable extends PluginsGuardUserTable
{
    ....

    public function retrieveByUsername($username, $isActive = true)
    {
        $query = self::getInstance()
            ->createQuery('u')
            ->where('u.email_address = ?', $username)
            ->addWhere('u.is_active = ?', $isActive);

        return $query->fetchOne();
    }

    ....
}

Now clear cache, and try to login with an email address instead of an username. With any luck it should work as expected. Sweet right? How about if you want to check both username AND email? No prob, consider the following:

public function retrieveByUsername($username, $isActive = true)
{
    $query = self::getInstance()
        ->createQuery('u')
        ->where('u.email_address = ?', $username)
        ->orWhere(‘u.username = ?’, $username)
        ->addWhere('u.is_active = ?', $isActive);

    return $query->fetchOne();
}

Notice the ‘orWhere’ addition, this will allow sfGuardPlugin to check either the email column OR the username column.

So that’s it, hopefully this seemingly easily feature in theory, however difficult to understand in practice, worked for you.