PUGXMultiUserBundle
PUGXMultiUserBundle copied to clipboard
Logging in a user directly in code gives a "no user provider for user" error
Great work on this plugin. I have 4 different types of users in my app and it works fine except this issue:
After a user registers, I would like to automatically log him in.
With FOSUserBundle I can log in a user like this :
$user = $em->getRepository('MyAppUser2Bundle:User2')->findOneByUsername('testuser');
$this->container->get('fos_user.security.login_manager')->loginUser(
$this->container->getParameter('fos_user.firewall_name'), $user);
Except when I do this with a PUGXMultiUserBundle user, I get this error :
There is no user provider for user "MyApp\User2Bundle\Entity\User2". vendor\symfony\symfony\src\Symfony\Component\Security\Http\Firewall\ContextListener.php at line 177
I have in security.yml:
security:
providers:
fos_userbundle:
id: fos_user.user_provider.username
And MyApp\User2Bundle\Entity\User2 class extends a User class that itself extends FOS\UserBundle\Model\User, so I don't think I need a special user provider...
Any clue what I got wrong here?
EDIT : actually when I try to login a user from the first type of user (in my case UserAdmin) it works, but it doesn't for the other 3 types of users.
Of what I find, the User Provider's class is not the base PUGXMultiUserBundle class that extends FOS\UserBundle\Model\User but the class of the first user defined in config.yml (if I switch the users, I can only log in this way with the first type of user).
Same here, any solution ?
I had to deal with the same problem. After some investigation I got this !
TLDR: You have to persist the user class that you want to use in the UserDiscriminator
when you manually log the user. In exemple:
$discriminator = $this->get('pugx_user.manager.user_discriminator');
// the 2nd parameter at true is the key
$discriminator->setClass('MyApp\User2Bundle\Entity\User2', true);
$user = $em->getRepository('MyAppUser2Bundle:User2')->findOneByUsername('testuser');
$this->container->get('fos_user.security.login_manager')->loginUser(
$this->container->getParameter('fos_user.firewall_name'),
$user
);
Detailed explanation: First, sorry if my english is not that good. I assume that you try to redirect the user after you log in him.
The SF2 ContextListener
refresh the current user from the session for each (master?) requests. To do so, he loops over all providers registered (only fos_userbundle
here) and call the refreshUser()
method for each provider.
Each UserProvider
must implements the SF2 UserProviderInterface
, which force the implementation of the methods refreshUser()
and supportsClass()
.
The FOS UserProvider
call his own supportsClass()
:
// [...]/vendor/friendsofsymfony/user-bundle/Security/UserProvider.php
public function supportsClass($class)
{
$userClass = $this->userManager->getClass();
return $userClass === $class || is_subclass_of($class, $userClass);
}
We see that it calls the getClass()
method from the UserManager
, which is the PUGX UserManager
in this situation.
// [...]/vendor/pugx/multi-user-bundle/PUGX/MultiUserBundle/Doctrine/UserManager.php
public function getClass()
{
return $this->userDiscriminator->getClass();
}
The UserDiscriminator is particular to the PUGXMultiUserBundle
. We're close to the end. :)
// [...]/vendor/pugx/multi-user-bundle/PUGX/MultiUserBundle/Model/UserDiscriminator.php
public function getClass()
{
if (!is_null($this->class)) {
return $this->class;
}
$storedClass = $this->session->get(static::SESSION_NAME, null);
if ($storedClass) {
$this->class = $storedClass;
}
if (is_null($this->class)) {
$entities = $this->getClasses();
$this->class = $entities[0];
}
return $this->class;
}
public function setClass($class, $persist = false)
{
if (!in_array($class, $this->getClasses())) {
throw new \LogicException(sprintf('Impossible to set the class discriminator, because the class "%s" is not present in the entities list', $class));
}
if ($persist) {
$this->session->set(static::SESSION_NAME, $class);
}
$this->class = $class;
}
OK. There we are. The getClass()
method check if the user class has been set previously (via the setClass()
). But we are on the ContextListener, on a new request, so no way to have use it.
Then it tries to retrieve the user class from the session. This is the main point: You can save the user class that you want to use in session. To do so, you just have to call the setClass()
method with true
as second argument. You can't call it from the ContextListener
either, but you can call it when you manually log your user (in your controller):
$discriminator = $this->get('pugx_user.manager.user_discriminator');
// the 2nd parameter at true is the key
$discriminator->setClass('MyApp\User2Bundle\Entity\User2', true);
$user = $em->getRepository('MyAppUser2Bundle:User2')->findOneByUsername('testuser');
$this->container->get('fos_user.security.login_manager')->loginUser(
$this->container->getParameter('fos_user.firewall_name'),
$user
);
This way, for the next requests, the UserDiscriminator::getClass()
method will retrieve the user class from the session. At this point, the ContextListener
will no longer have difficulties to refresh your user.
If you check the last part of the UserDiscriminator::getClass()
method, then you will notice that if no user class is found, it take the first user class defined in your config.yml
. This explains why you could log in only the first user defined in your config.yml
.