musoNic80
musoNic80

Reputation: 3696

FOSUserBundle override mapping to remove need for username

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.

Upvotes: 7

Views: 11424

Answers (3)

Muhammed
Muhammed

Reputation: 1612

When I didn't want to require users to enter emails (thus making emails optional in FOSUserBundle), I use Symfony 2.7 + FOSUser+SonataUser+SonataAdmin.

At the same time I needed entered emails to be unique in the system. So Users have 2 options when registering:

  1. Leave email empty
  2. Provide a unique email, that is not yet in the system

Below is my solution that works as expected (I don't claim it to be the cleanest one, but hopefully it will show you a way how to accomplish a similar task)

1) Changes to Entity/User.php

namespace AppBundle\Entity;

use Sonata\UserBundle\Entity\BaseUser as BaseUser;
use Doctrine\ORM\Mapping as ORM;


/**
 * @ORM\Entity
 * @ORM\Table(name="fos_user")
 *
 *
 * @ORM\AttributeOverrides({
 *      @ORM\AttributeOverride(name="email",
 *          column=@ORM\Column(
 *              type =  "string",
 *              name     = "email",
 *              nullable = true,
 *              unique   = true
 *          )
 *      ),
 *      @ORM\AttributeOverride(name="emailCanonical",
 *          column=@ORM\Column(
 *              type = "string",
 *              name     = "email_canonical",
 *              nullable = true,
 *              unique   = true
 *          )
 *      )
 * })
 *
\*/
class User extends BaseUser
{

2) Executed app/console doctrine:migrations:diff & migrate, database tables were changed as expected adding "DEFAULT NULL" to email and email_canonical fields

3) No matter what I tried, email was being set to NULL, but email_canonical wasn't, it was returning "". I tried manually setting it to NULL in my RegistrationFormHandler, var_dump there confirmed that it was indeed set to NULL when email wasn't entered. But to the database FOSUser would submit "" empty string, which violated UNIQUE constraint I had set for emails, so the solution was to override method in Entity/User.php (as is discussed in previous answers to this question)

// src/AppBundle/Entity/User.php
// ...
public function setEmailCanonical($emailCanonical)
{
   // when email is empty, force canonical to NULL
   // for some reason by default "" empty string is inserted
   $this->emailCanonical = $this->getEmail();
}

4) Change Validation for FOSUserBundle (or SonataUserBundle) in my case , so that it doesn't require email to be set. (I simply removed .. from validation.xml as non of those applied to email anymore)

Copy these 2 files into your config/validation/ directory (for SonataUser+FOSUser it is: Application/Sonata/UserBundle/Resources)

  1. vendor/friendsofsymfony/user-bundle/FOS/UserBundle/Resources/config/storage-validation/orm.xml
  2. above path, config/validation/orm.xml

Rename "Registration" group in those files to your own name, like "myRegistration".

Bind your new validation_group to fos_user in config.yml. If using Sonata User, it is:

sonata_user:
   profile:
      register:
          form:
              ...
              validation_groups:
                - myRegistration
                - Default

Have fun.

Upvotes: 1

Shaggy
Shaggy

Reputation: 233

If you are using doctrine 2, you can use Life Cycle Events to put your logic inside a callback.

http://docs.doctrine-project.org/en/2.0.x/reference/events.html

/**
 * @ORM\PreUpdate()
 * @ORM\PrePersist()
 */
public function setUsernameToEmail()
{
    $this->username = $this->email;
    $this->usernameCanonical = $this->emailCanonical;
}

Upvotes: 2

Prynz
Prynz

Reputation: 551

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.

Upvotes: 15

Related Questions