Open gavinwilliams opened 12 years ago
A recent project of mine required exactly what you're trying to do, rather then disabling/removing the username I made it a hidden field that was filled in with a random set of numbers during registration and enabled the email log in functionality found here: https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Resources/doc/logging_by_username_or_email.md.
Not really what you're looking for but maybe it helps.
Another way to do this is to hide the username field and in the prePersist/preUpdate for your User entity set the username to the email address. That fixes the logging in by username or email issue too since the username is the same as the email address.
@treethinking the prePersist/preUpate won't help with the validation of setting the username. The form validates before the Hooks are executed, thus the registration will fail without further modifications
I just did it for the project I'm working on, what I did is :
Override the FormHandler, simply replace process by its own code in FOSUserBundle's equivalent class, but adding the username you want there :
$user = $this->userManager->createUser();//Original FOSUser line
$user->setUsername(uniqid("u", true));//Creating my own uid for the username
$this->form->setData($user);//Original FOSUser line
and leave the rest of the method as it is defined in FOSUserBundle RegistrationFormHandler.php
And I think that's about it, I was able to register correctly with my email only.
@GTheron : It's just some naming, why don't you simply use the email as username?
You should not extend your user class from the FOS\UserBundle\Entity\User
but you should implement the FOS\UserBundle\Model\UserInterface
instead.
@Baachi why ?
@stof If you extend from FOS\UserBundle\Entity\User
you have a empty column named username. To avoid this, you should implement the UserInterface
instead. :)
@Baachi but you have to do all the mapping yourself. It is not always worth it
Yes, i know. That sucks, but to create a column that always empty is very dirty.
@bamarni If a user wants to change their email, the username one remains valid for login, which can be seen as a problem in some situations, as the username cannot (or at least should not) be changed, which is why it seemed good to have a uid in the username. Note that it can always be used in "emergency" situations.
From a general viewpoint, when you think about it, asking users to come up with a unique username when their email address already satisfies the very same uniqueness constraint is really not that great a thing to do in many cases. It would therefore be great to have support for "email usernames" added to this bundle.
As for my use case, the general concept of what GTheron wrote above (assigning a unique internal ID as a username) appears to be a good approach for the time being, with the difference that I would get my IDs from a non-random source.
Fyi, for those who want to set their programmatic username only after registration form validation, it will be necessary to create and apply a custom validation_groups
configuration, completely replacing the default Registration
validation group configured for registration forms. In this new validation group, the only username field value allowed must be null. All the other validation settings can be copied over.
The issue is that it doesn't seem to be possible to simply override only the username validation in a new validation group and add that group behind the existing one, e.g. validation_groups: [Registration, RegistrationNoUsername]
. It seems Registration
always has precedence.
@advancingu As a side note, the unique_id is not actually random, as it's based on a unique identifier corresponding to the machine time on which it's produced, therefore it cannot appear twice.
@GTheron You're right that unique_id is not random as I said, however I would assume that if you run your application on more than one server in parallel you might still get duplicates with unique_id. Of course, chances are small but they're greater than zero.
I agree with @advancingu. We're upgrading to sf2.1 right now and revisiting our FOSUserBundle integration. The dual username/email requirement is the only thing that requires a non-obvious workaround. It would be nice if "email as username" was baked-in as a feature.
@nurikabe me too, vote for the new feature
Wouldn't it be better to simply make the "main login fields" fully configurable?
FOSUserBundle is aiming at flexibility. Should it simply not assume anything about the role of fields, except password and some internal needs? There would be defaults, easily configurable by some yaml pairs:
fields:
login: [email, company_registration_number]
non_editable: [email, surname, firstname]
@stof: i tried to find the answer to the following question on GIT and stackoverflow, but couldn't: can we expect FOSUserBundle to at least make the login field configurable some day? Or isn't that planned for the moment? Thank you :)
@NinjDS Stof started a PR for FOSUserBundle 2.
And i think your idea is out of the scope from FOSUserBundle.
Indeed, i saw the PR some minute later. But why do you think it's not the purpose of FOSUserBundle? It's not named "FOSUsernameBundle" :)
Anyway, you may be right because i start to realize that FOSUserBundle is not what i need. It's very specific, aimed at particular cases (which fits in 90% of the cases i agree). But as soon as your user management is not too common, it's probably better to write it by yourself rather than overwrite 50% of the bundle's code.
In my case i need minimalistic user structure all drive by the email, and to which one or more "shop" entities ar attached, as a part of the user account. It requires me to tweak all FOSUserBundle form types and validations to deal with the username trick, and override the process
methods everywhere to manage the "shop" links.
I'm not yet too confortable with this bundle so i don't see the whole job it caries. In other words, i don't see how much code i'll have to write if i want my own user management bundle to replace it taking my specifications into account, but i feel like it will be a better solution to go with.
@NinjDS configuring form fields in YAML is out of the scope of the bundle. For the edit form, we are using the Form component, and we are making the form type configurable. It allows to do approximately anything in the edit form, which is far more flexible than your non_editable
suggestion.
@stof Hi, You are right, the way it's done makes it very configurable but not flexible. Flexible would mean that no assumption is made about what a user account is. It's the opposite here, starting with the fact that a user profile is defined primarly by a username and integrates an e-mail. I'm not telling FOSUB is bad or anything, no, really, it's great i'm sure, when your needs gets in its way. Not my case i'm afraid ;)
Hey guys, if you afraid that unique_id is not unique than you must use sluggable!
What I always thought about this: Why not removing setUsername()
, getUsername()
, setUsernameCanonical()
and getUsernameCanonical()
complety and replace it with a single getIdentity()
? The intention is, that the one, who wants to use the email as login/identifier can just return it, as it is
public function getIdentity()
{
return $this->getEmailCanonical();
}
I think nowadays this is more common, than a username. But those, who wants to use usernames instead, shouldn't have any problems adding this instead. And when somebody wants to use the real name of the user instead, it is easily usable too :) (Isn't useful because they are usually not unique, but it is possible).
What I want to say: Separating the login-name from the username and/or email would make the UserInterface
more flexible against chances, what the login-name should be.
Any suggestions?
i think UserInterface itself should be changed as you suggested @KingCrunch , but in symfony not just for FOSUserBundle
Ah, havent thought this far... Right, getUsername()
is part of \Symfony\Component\Security\Core\User\UserInterface
... Well, I thik for a change of a Symfony-Core interface it is too late (2.3 is out). In this case I would treat getUsername()
like the getIdentity()
I mentioned before and just remove the setters and getUsernameCanonical()
.
You can create your own custom provider:
# app/config/security.yml
providers:
fos_userbundle:
id: my_custom.user_provider.email
# UserBundle/Resources/config/services.yml
services:
my_custom.user_provider.email:
class: UserBundle\Security\EmailProvider
public: false
arguments: [@fos_user.user_manager]
# UserBundle\Security\EmailProvider
namespace UserBundle\Security;
use FOS\UserBundle\Security\UserProvider;
class EmailProvider extends UserProvider
{
protected function findUser($username)
{
return $this->userManager->findUserByEmail($username);
}
}
This works out quite simply in FOSUserBundle V2.0, first set the UserProvider to allow username or email:
providers:
fos_userbundle:
id: fos_user.user_provider.username_email
Then add an EventListener for Registration Initialization
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_INITIALIZE => 'onRegistrationInit',
);
}
/**
* take action when registration is initialized
* set the username to a unique id
* @param \FOS\UserBundle\Event\FormEvent $event
*/
public function onRegistrationInit(UserEvent $userevent)
{
$user = $userevent->getUser();
$user->setUsername(uniqid());
}
And override forms and templates to get rid of references to the user name.
Just to add my two cents (sorry if this has already been discussed), the bare minimum to register a user in any online application is an email address and a password. There are a considerable number of applications where a username is not appropriate.
From a UX perspective (especially in commerce) usernames are difficult for users to remember, whereas an email address is arguably easier to remember.
As it stands to achieve what I think is a better practice requires work-around on the bundle's defaults, and leaves us with an unused DB column (a minor issue for the majority of small projects, but still not ideal).
I think that usernames should be an optional extra on the bundle rather than the default, and the bundle would be a lot more useful if that were the case.
The suggestion from @KingCrunch could be a good approach, because the bare minimum (i.e. a means of confirming an account and password resetting) for account registration in a web app could easily replace email for a cellphone number for example.
I remembered this issue and I thought about a PR, but first: This would obviously to a BC-break. 2.x
is currently in dev, so from the versioning point of view there is no problem. However, this bundle in version 2 seems to be quite widespreaded, so it may (actually I have no idea) affect many users. But again: It is a dev-version :confused:
I would remove all "username"-related properties and setters/getters and redirect getUsername()
to getEmailCanonical()
like mentioned above 2 months earlier.
public function getUsername()
{
return $this->getEmailCanonical();
}
What remains: Should I also provide an additional User
-class with username, to make it easier for those, who want to keep it? Or maybe a trait
? :wink:
What is the opinion of the maintainers?
I added my method to remove the username to gist: https://gist.github.com/nchervyakov/c013aa2eaf4da72150f6
Like @PeterWooster said, this works out of the box. But it took me a while because im new to Symonfy. So here is the solution:
You have to write a event listener like described in the docs.
First, enable login with email OR username, provided by FOSUserBundle as described here:
# app/config/security.yml
security:
providers:
fos_userbundle:
id: fos_user.user_provider.username_email
Now create the event listener as described here:
// src/Acme/UserBundle/EventListener/UserRegistrationListener.php
<?php
namespace Acme\UserBundle\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use FOS\UserBundle\Event\UserEvent;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FormEvent;
class UserRegistrationListener implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_INITIALIZE => 'onRegistrationInit',
);
}
/**
* take action when registration is initialized
* set the username to a unique id
* @param \FOS\UserBundle\Event\FormEvent $event
*/
public function onRegistrationInit(UserEvent $userevent)
{
$user = $userevent->getUser();
$user->setUsername(uniqid());
}
}
Finally, register it as a service:
// src/Acme/UserBundle/Resources/config/services.yml
services:
acme_user_registration_listener:
class: Acme\UserBundle\EventListener\UserRegistrationListener
tags:
- { name: kernel.event_subscriber }
@MickL thanks for this workaround, if you could spare some time to explain how you override templates and forms to remove reference to the "username" field it would be great for beginner users ! :)
@MickL what you have written there looks perfect but from what I can tell events have either been removed or moved from version 1.3 onwards (can't seem to find any reference of this in the change log).
As a quick fix I have added this to my user entity.
public function setEmail($email)
{
if (is_null($this->getUsername())) {
$this->setUsername(uniqid());
}
return parent::setEmail($email);
}
Events are present in the 2.0-dev/master branch which currently has a beta tag as its most stable tag.
Yes I just downloaded the master branch which is recommended for Symfony 2.1+. Also check out the Documentation. The navigation is (unfortunately) at the very bottom: https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Resources/doc/index.md
P.S. Would be awesome to see an easy configuration-line to enable login with another field than username without any additional work.
It isnt possible to provide a configuration option. Setting a listener as suggested above is a suitable solution - though I wouldnt use the username_email provider. For the systems where we use email-only, we set the username to be the same as the email address.
I solved this problem by copying the setEmail() function to my own entity, and adding '$this->username = $email'.
Would there be any problems with my implementation?
The username should be unique. The email can be changed by the user. This is why the event listener sets an unique id to the username.
Unfortunately I can't use 2.0-dev as I'm using SonataUserBundle which requires 1.3.x so am kind of stuck without events.
@phillipsnick You don't need to use SonataUserBundle when you want to use Sonata.
@KingCrunch but if you want to use the SonataNewsBundle then you need to use SonataUserBundle.
I have found some hacky ways around for now, I guess SonataUserBundle isn't using FOSUserBundle 2.0 as it isn't a final release yet? Seems I'm not the only one having issues https://github.com/sonata-project/SonataUserBundle/issues/322 and https://github.com/sonata-project/SonataUserBundle/issues/348
Overall I'm happy to use FOSUserBundle 1.3 but just curious as to why events have been removed? Can't find the relevant commits/change log/issue which supports this.
@acidjames Good question, so to complete this 'away with the username field solution' you should follow MickL's changes and the following files:
//Acme/UserBundle/Form/Type/RegistrationFormType.php
namespace Acme\UserBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class RegistrationFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// add your custom field
$builder->remove('username');
}
public function getParent()
{
return 'fos_user_registration';
}
public function getName()
{
return 'acme_user_registration';
}
}
//app/config/config.php
// ...
fos_user:
// ...
registration:
form:
type: acme_user_registration
@maxstekel, for this you also need to add the form type to services.yml
as shown in overriding_forms.md
# src/Acme/UserBundle/Resources/config/services.yml
services:
acme_user.registration.form.type:
class: Acme\UserBundle\Form\Type\RegistrationFormType
tags:
- { name: form.type, alias: acme_user_registration }
Is there anything wrong with overriding the User like this? I know there is redundant info in the database but this should take care of all other situations correct?
public function setEmail($email){
$this->email = $email;
$this->username = $email;
}
public function setEmailCanonical($emailCanonical){
$this->emailCanonical = $emailCanonical;
$this->usernameCanonical = $emailCanonical;
}
We did this in one application but it ends up taking up 4 times as much space which can add up. We ended up implementing FOSUserBundle's UserInterface and manually mapped the fields we required instead.
I suppose it is 2 more fields than normal however 4 times as much space is shocking due to the nature of the data. I'll keep that in mind, thanks for your input.
Sorry, you're right - its only twice as much data, not 4 times as much - given the bundle stores a canonical representation of the email address already.
@MickL Is it possible to make a complete explanation what you did? For example the missing pieces. That would be very great! So that we have a stable solution. Thank you. :-)
Is there any way to get FOSUserBundle to register/login by email only and disable/remove the username property?