I am using Symfony 2 + FOSUserBundle to manage my users.
The problem is that I would like to build the email address "manually" from the username, and so, do not require any field for the email.
I will create the email from the username because one requirement to sign up is to have an email from my university which follows a strict format (username#schoolname.fr).
I managed to override the RegistrationFormType to avoid the email field from being added to my sign up page but I still have the error "Please enter an email" when I submit the form.
How can I prevent the validation of the email address and how could I "build" it from the username?
Thanks!
(Sorry for the English, I know it's not perfect...)
There is a simple way around it. It is even mentioned in the official FOSUserBundle documentation. You just need to override the controller.
Create your own Bundle and extend the FOSUserBundle:
class CustomUserBundle extends Bundle
{
public function getParent()
{
return 'FOSUserBundle';
}
}
And then override the RegistrationController:
class RegistrationController extends BaseController
{
public function registerAction(Request $request)
{
// here you can implement your own logic. something like this:
$user = new User();
$form = $this->container->get('form.factory')->create(new RegistrationType(), $user);
if ($request->getMethod() == 'POST') {
$form->bind($request);
if ($form->isValid()) {
$user->setEmail($user->getUsername() . '#' . $user->getSchoolname() . '.fr');
// and what not. Also don't forget to either activate the user or send an activation email
}
}
}
}
You should write event listener for fos_user.registration.initialize. From code docs:
/**
* The REGISTRATION_INITIALIZE event occurs when the registration process is initialized.
*
* This event allows you to modify the default values of the user before binding the form.
* The event listener method receives a FOS\UserBundle\Event\UserEvent instance.
*/
const REGISTRATION_INITIALIZE = 'fos_user.registration.initialize';
More info about event dispatcher: http://symfony.com/doc/current/components/event_dispatcher/introduction.html
And example event listener: http://symfony.com/doc/current/cookbook/service_container/event_listener.html
UPDATE - how to code?
In your config.yml (or services.yml or other extension like xml, php) define service like this:
demo_bundle.listener.user_registration:
class: Acme\DemoBundle\EventListener\Registration
tags:
- { name: kernel.event_listener, event: fos_user.registration.initialize, method: overrideUserEmail }
Next, define listener class:
namespace Acme\DemoBundle\EventListener;
class Registration
{
protected function overrideUserEmail(UserEvent $args)
{
$request = $args->getRequest();
$formFields = $request->get('fos_user_registration_form');
// here you can define specific email, ex:
$email = $formFields['username'] . '#sth.com';
$formFields['email'] = $email;
$request->request->set('fos_user_registration_form', $formFields);
}
}
Notice: Of course you can validate this email via injecting #validator to the listener.
Now you should hide email field in registration form. YOu can do that by overriden register_content.html.twig or (in my oppinion better way) override FOS RegistrationFormType like this:
namespace Acme\DemoBundle\Form\Type;
use FOS\UserBundle\Form\Type\RegistrationFormType as BaseType;
use Symfony\Component\Form\FormBuilderInterface;
class RegistrationFormType extends BaseType
{
// some code like __construct(), getName() etc.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// some code for your form builder
->add('email', 'hidden', array('label' => 'form.email', 'translation_domain' => 'FOSUserBundle'))
;
}
}
Now your application is ready for setting email manually.
Related
I have install FOSUserBundle in my Symfony project. Now I want to Remove Registration Form Field that by default provide by FOSUserBundle.
Registration Form Fields Are :
User Name
Email Id
Password
Repeat Password
Now I don't want Email Field when User are register so I override Registration form in my bundle.
\\ Front\FrontBundle\Form\RegistrationType.php
<?php
namespace Front\FrontBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class RegistrationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->remove('email'); // here I code for remove email field.
}
public function getParent()
{
return 'FOS\UserBundle\Form\Type\RegistrationFormType';
// Or for Symfony < 2.8
// return 'fos_user_registration';
}
public function getBlockPrefix()
{
return 'app_user_registration';
}
// For Symfony 2.x
public function getName()
{
return $this->getBlockPrefix();
}
}
then I change config.yml and services.yml file
\\ App/config/config.yml
fos_user:
db_driver: orm
firewall_name: main
user_class: Front\FrontBundle\Entity\User
registration:
form:
type: Front\FrontBundle\Form\RegistrationType
\\app/config/services.yml
services:
app.form.registration:
class: Front\FrontBundle\Form\RegistrationType
tags:
- { name: form.type, alias: app_user_registration }
So After done with this Email Field remove from my Registration Form but when I submit form after filling username , password , repeat password it's give me any error that The email is not valid.
So I need to change any other file to remove email validation with email field ?
Thanks.
you can remove the field as you have done, but you must make sure that there is no residual server side validation that requires it.
how to do this depends on how you've extended the FOS User class (or if you have).
annotated constraints look like this for instance (from the docs)
class Author
{
/**
* #Assert\NotBlank() <--- remove this
*/
public $name;
}
if youv'e extended the class, you can remove it from your own member definition.
If you've not extended it, extend it and then dont put in the validation constraint.
Or failing everything else (and I think this is hacky), issue a default in the controller before you call isValid().
public function someAction(Request $request) {
// ...
$user = new User();
// fill empty value
$user->setEmail('blank#blank.blank');
// form stuff here
// ...
if ($form->isValid()) {
// do some stuff
}
return $this->render(blahblahbal);
}
I have an App that requires a complex access control. And the Voters is what I need to make decisions on Controller-level.
However, I need to build form for different users by different way.
Example: There are Admin(ROLE_ADMIN) and User(ROLE_USER). There is a Post that contains fields:
published
moderated
author
body
timestamps
Admin must be able to edit all fields of any Post.
User - only particular fields: published, body. (bay the way, only if this is an author of this post, but this is decided by voters).
Possible solution i found is dynamic form modification. But if we need more complexity, for example posts belongs to Blog, Blog belongs to author. And Post can be edited by direct author and author of the blog.
And Author of the Blog can also edit postedAt field, but it can't be done by direct author of the post.
I need to write some login in PRE_BIND listener.
Maybe there is some kind of common practice for that situation, or someone can show their own examples of.
You can do this creating a form type extension
Imagine a form type where you want to display a field only if ROLE_ADMIN is granted. For that you can simply add a new property to the field ('author' in this example)
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('published', 'text')
->add('moderated', 'text')
->add('author', 'text', [
'is_granted' => 'ROLE_ADMIN',
])
;
}
For this parameter to be interpreted, you must create a form type extension by injecting the SecurityContext Symfony to ensure the rights of the logged on user.
<?php
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Core\SecurityContextInterface;
class SecurityTypeExtension extends AbstractTypeExtension
{
/**
* The security context
* #var SecurityContextInterface
*/
private $securityContext;
/**
* Object constructor
*/
public function __construct(SecurityContextInterface $securityContext)
{
$this->securityContext = $securityContext;
}
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$grant = $options['is_granted'];
if (null === $grant || $this->securityContext->isGranted($grant)) {
return;
}
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$form = $event->getForm();
if ($form->isRoot()) {
return;
}
$form->getParent()->remove($form->getName());
});
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefined(array('is_granted' => null));
}
/**
* {#inheritdoc}
*/
public function getExtendedType()
{
return 'form';
}
}
Finally, you just have to save the extension as a service :
services:
yourbundle.security_type_extension:
class: YourProject\Bundle\ForumBundle\Form\Extension\SecurityTypeExtension
arguments:
- #security.context
tags:
- { name: form.type_extension, alias: form }
Dynamic form modification seems unnecessary. Once the user is logged in the roles should not change.
You could inject the security.authorization_checker service in your form type and use that in the buildForm method to conditionally add fields to your form. Depending on how much the forms differ, this might become messy with too many if-statements. In that case I would suggest writing different form types altogether (possibly extending a base form type for repeated things).
I can't figure out how to impersonate a user by user's id instead of user's username in Symfony?
The following trick which works with username can't work with id, as symfony is looking for username:
?_switch_user={id}
This is impossible to do without implementing your own firewall listener, as behind the scenes it loads the user from the userprovider (which only has a loadUserByUsername() method in its interface).
You could however implement your own firewall listener and get inspired by having a look at the code in Symfony\Component\Security\Http\Firewall\SwitchUserListener. For detailed information on implementing your own authentication provider, check the cookbook article.
EDIT:
One possible solution might be registering an extra request listener:
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class LookupSwitchUserListener implements EventSubscriberInterface
{
private $repository;
public function __construct(UserRepository $repository)
{
$this->repository = $repository;
}
public static function getSubscribedEvents()
{
return [
KernelEvents::REQUEST => ['lookup', 12] // before the firewall
];
}
public function lookup(GetResponseEvent $event)
{
$request = $event->getRequest();
if ($request->has('_switch_user') {
return; // do nothing if already a _switch_user param present
}
if (!$id = $request->query->has('_switch_user_by_id')) {
return; // do nothing if no _switch_user_by_id param
}
// lookup $username by $id using the repository here
$request->attributes->set('_switch_user', $username);
}
}
Now register this listener in the service container:
services:
my_listener:
class: LookupSwitchUserListener
tags:
- { name: kernel.event_subscriber }
Calling a url with the ?_switch_user_by_id=xxx parameter should now correctly look up the username and set it so the SwitchUserListener can switch to the specified user.
I'm using FOSUserBundle on Symfony2.
I extended the User class to have additional fields, therefore I also added the new fields in the twigs.
One of those fields is a licence code. When a user fills in that field I want to perform a connection to DB to look if that license is valid. If not returns an error, if yes creates an event in the "licenceEvents" table assigning the current user to that license.
[EDIT] As suggested I created a custom validator (which works like a charm), and I'm now struggling with the persisting something on DB once the user is created or updated.
I created an event listener as follows:
<?php
// src/AppBundle/EventListener/UpdateOrCreateProfileSuccessListener.php
namespace AppBundle\EventListener;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FormEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Doctrine\ORM\EntityManager; //added
class UpdateOrCreateProfileSuccessListener implements EventSubscriberInterface
{
private $router;
public function __construct(UrlGeneratorInterface $router, EntityManager $em)
{
$this->router = $router;
$this->em = $em; //added
}
/**
* {#inheritDoc}
*/
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_COMPLETED => array('onUserCreatedorUpdated',-10),
FOSUserEvents::PROFILE_EDIT_COMPLETED => array('onUserCreatedorUpdated',-10),
);
}
public function onUserCreatedorUpdated(FilterUserResponseEvent $event)
{
$user = $event->getUser();
$code = $user->getLicense();
$em = $this->em;
$lastEvent = $em->getRepository('AppBundle:LicenseEvent')->getLastEvent($code);
$licenseEvent = new LicenseEvent();
// here I set all the fields accordingly, persist and flush
$url = $this->router->generate('fos_user_profile_show');
$event->setResponse(new RedirectResponse($url));
}
}
My service is like follows:
my_user.UpdateOrCreateProfileSuccess_Listener:
class: AppBundle\EventListener\UpdateOrCreateProfileSuccessListener
arguments: [#router, #doctrine.orm.entity_manager]
tags:
- { name: kernel.event_subscriber }
The listener is properly triggered, manages to create the connection to DB as expected, but gives me the following error
Catchable Fatal Error: Argument 1 passed to AppBundle\EventListener\UpdateOrCreateProfileSuccessListener::onUserCreatedorUpdated()
must be an instance of AppBundle\EventListener\FilterUserResponseEvent,
instance of FOS\UserBundle\Event\FilterUserResponseEvent given
I must be missing something very stupid...
Another question is: I don't want to change the redirect page, so that if the original page was the "email sent" (after a new user is created) let's go there, otherwise if it's a profile update show the profile page.
I recently implemented the FOSUserBundle in my website as the login procedure. I want it to expand the Author class. So whenever a new user is registered via the FOSUSerBundle a new entry is created in the Author class. Inside the Author class I set slug, createdAt and other useful parameters. The field I want to pass to Authot entity from FOSUserBundle is the "Name" field. Then I want to cascade the FOSUser entity and if its deleted, delete also the Author entity.
So schematically FOSUserBundle.username => Author.name
I do not know how to implement this code except that it has a #ORM/OneToOne relationship. Any ideas please?
You'll have to insert the Author manually after your user registration is completed. The FOSUserBundle provides a way to hook into events like post registration completion. You can create a listener to the FOSUserEvents::REGISTRATION_COMPLETED event and create your Author entity there.
See documentation here: https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Resources/doc/controller_events.md
For example:
services.yml:
services:
my_user_registration_service:
class: MyBundle\EventListener\MyUserRegistrationListener
arguments: [#doctrine.orm.entity_manager]
tags:
- { name: kernel.event_subscriber }
MyUserRegistrationListener:
namespace MyBundle\EventListener;
use Doctrine\ORM\EntityManager;
use FOS\UserBundle\Event\FormEvent;
use FOS\UserBundle\FOSUserEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use MyBundle\Entity\Author;
class EventSubscriber implements EventSubscriberInterface
{
private $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_COMPLETED => 'addAuthor',
);
}
public function addAuthor(FilterUserResponseEvent $event)
{
$user = $event->getUser();
$author = new Author();
$author->setName($user->getUsername();
$this->em->persist($author);
$this->em->flush();
}
}