Symfony – FOSUserBundle override mapping to remove need for username

doctrinefosuserbundlesymfony

I want to remove the need for a username in the FOSUserBundle. My users will login using an email address only and I've added real name fields as part of the user entity.
I realised that I needed to redo the entire mapping as described here.
I think I've done it correctly but when I try to submit the registration form I get the error:

"Only field names mapped by Doctrine can be validated for uniqueness."

The strange thing is that I haven't tried to assert a unique constraint to anything in the user entity.

Here is my full user entity file:

 <?php
        // src/MyApp/UserBundle/Entity/User.php

        namespace MyApp\UserBundle\Entity;

        use FOS\UserBundle\Model\User as BaseUser;
        use Doctrine\ORM\Mapping as ORM;
        use Symfony\Component\Validator\Constraints as Assert;

        /**
         * @ORM\Entity
         * @ORM\Table(name="depbook_user")
         */
        class User extends BaseUser
        {
            /**
             * @ORM\Id
             * @ORM\Column(type="integer")
             * @ORM\GeneratedValue(strategy="AUTO")
             */
            protected $id;

            /**
             * @ORM\Column(type="string", length=255)
             *
             * @Assert\NotBlank(message="Please enter your first name.", groups={"Registration", "Profile"})
             * @Assert\MaxLength(limit="255", message="The name is too long.", groups={"Registration", "Profile"})
             */
            protected $firstName;

            /**
             * @ORM\Column(type="string", length=255)
             *
             * @Assert\NotBlank(message="Please enter your last name.", groups={"Registration", "Profile"})
             * @Assert\MaxLength(limit="255", message="The name is too long.", groups={"Registration", "Profile"})
             */
            protected $lastName;

            /**
             * @ORM\Column(type="string", length=255)
             *
             * @Assert\NotBlank(message="Please enter your email address.", groups={"Registration", "Profile"})
             * @Assert\MaxLength(limit="255", message="The name is too long.", groups={"Registration", "Profile"})
             * @Assert\Email(groups={"Registration"})
             */
            protected $email;

            /**
             * @ORM\Column(type="string", length=255, name="email_canonical", unique=true)
             */
             protected $emailCanonical;

            /**
             * @ORM\Column(type="boolean")
             */
             protected $enabled;

            /**
             * @ORM\Column(type="string")
             */
             protected $salt;

            /**
             * @ORM\Column(type="string")
             */
             protected $password;

            /**
             * @ORM\Column(type="datetime", nullable=true, name="last_login")
             */
             protected $lastLogin;

            /**
             * @ORM\Column(type="boolean")
             */
             protected $locked;

            /**
             * @ORM\Column(type="boolean")
             */
             protected $expired;

            /**
             * @ORM\Column(type="datetime", nullable=true, name="expires_at")
             */
             protected $expiresAt;

            /**
             * @ORM\Column(type="string", nullable=true, name="confirmation_token")
             */
             protected $confirmationToken;

            /**
             * @ORM\Column(type="datetime", nullable=true, name="password_requested_at")
             */
             protected $passwordRequestedAt;

            /**
             * @ORM\Column(type="array")
             */
             protected $roles;

            /**
             * @ORM\Column(type="boolean", name="credentials_expired")
             */
             protected $credentialsExpired;

            /**
             * @ORM\Column(type="datetime", nullable=true, name="credentials_expired_at")
             */
             protected $credentialsExpiredAt;

            public function __construct()
            {
                parent::__construct();
                // your own logic
            }

            /**
             * @return string
             */
            public function getFirstName()
            {
                return $this->firstName;
            }

            /**
             * @return string
             */
            public function getLastName()
            {
                return $this->lastName;
            }

             /**
             * Sets the first name.
             *
             * @param string $firstname
             *
             * @return User
             */
            public function setFirstName($firstname)
            {
                $this->firstName = $firstname;

                return $this;
            }

                 /**
             * Sets the last name.
             *
             * @param string $lastname
             *
             * @return User
             */
            public function setLastName($lastname)
            {
                $this->lastName = $lastname;

                return $this;


       }
    }

I've seen various suggestions about this but none of the suggestions seem to work for me. The FOSUserBundle docs are very sparse about what must be a very common request.

Best Answer

I think the easiest way to go about this is to leave the bundle as is and rather setup your user class to have a username equal to the email address.

Do this by overriding the setEmail() method to also set the $username property to the $email parameter and the setEmailCanonical() to also set the $usernameCanonical to the $emailCanonical.

public function setEmail($email){
    $this->email = $email;
    $this->username = $email;
}

public function setEmailCanonical($emailCanonical){
    $this->emailCanonical = $emailCanonical;
    $this->usernameCanonical = $emailCanonical;
}

All you will have to do other than this is semantics related. Like having your form label read E-mail instead of the default Username label. You can do this by overriding the translations files. I'll leave this up to you (or someone else) since it might not even be necessary for you.

With this strategy you will have redundant data in your database but it will save you a lot of remapping headache.

Related Topic