Symfony User entity (PhpStorm warning) - symfony

I have Symfony entity with auto-generated setUser() method:
public function setUser(?User $user): self
{
$this->user = $user;
return $this;
}
In controller I use built-in getUser() method, which returns UserInterface object. And when I pass that UserInterface object to setUser() method, PhpStorm complains that:
Expected parameter of type '\App\Entity\User', 'object|\Symfony\Component\Security\Core\User\UserInterface' provided
I'd like to write code without such PhpStorm warnings. Should I create new User() to pass it to setUser() method or just ignore that?

Solution: (thanks to Jakumi comment)
public function __invoke() {
/** #var $user App\Entity\User */
$user = $this->getUser();
$entity = new Entity();
$entity->setUser($user);
//...
}

Related

Call to a member function encodePassword() on null, Symfony 4

I'm trying to save an encoded password to my entity by using the UserPasswordEncoderInterface class.
class UserFixture extends Fixture
{
private $encoder;
public function _construct( UserPasswordEncoderInterface $encoder){
$this->encoder = $encoder;
}
public function load(ObjectManager $manager)
{
$user = new User();
$user->setUsername('admin');
$user->setPassword(
$this->encoder->encodePassword($user, '0000')
);
$user->setEmail('no-reply#hotmail.com');
$manager->persist($user);
$manager->flush();
}
}
But I'm getting the follwing error: Call to a member function encodePassword() on null. Can't find what I'm doing wrong here!
You should first check your namespace, but as #Cerad says, you mistyped __construct() which is the reason why your encoder is null.
The right command to use in order to load your fixture properly might be :
php bin/console doctrine:fixtures:load
As the documentation recommends : https://symfony.com/doc/master/bundles/DoctrineFixturesBundle/index.html

False "new entity found through the relationship" - Object actually exists in database

We have a service which purpose is to log user actions into database. The underlying entity ActionLog has a manyToOne relation with our User entity. Both those entities are tied to the same DBAL connection (and ORM EntityManager).
Issue is: an exception is raised when a new ActionLog entity is persisted and flushed, saying we should cascade persist the object set as the #user property because considered new:
A new entity was found through the relationship 'Doctrine\Model\ActionLog#user' that was not configured to cascade persist operations for entity: John Doe. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example #ManyToOne(..,cascade={"persist"}).
And this is annoying because the User instance actually comes straight from the database and as such isn't new at all! We expect this User object to already be "MANAGED" by the entityManager and be referenced through the identity map (in other words, the object is not "detached").
So, why would Doctrine consider the User entity instance (authenticated user) as detached/new?
Using Symfony 4.0.6 ; doctrine/orm v2.6.1, doctrine/dbal 2.6.3, doctrine/doctrine-bundle 1.8.1
ActionLog model mapping extract
Doctrine\Model\ActionLog:
type: entity
table: action_log
repositoryClass: Doctrine\Repository\ActionLogRepository
manyToOne:
user:
targetEntity: Doctrine\Model\User
id: # …
fields: # …
Log service declaration
log_manager:
class: Service\Log\LogManager
public: true
arguments:
- "#?security.token_storage"
calls:
# setter required instead of the dependency injection
# to prevent circular dependency.
- ['setEntityManager', ["#doctrine.orm.entity_manager"]]
Log service implementation - Creates new ActionLog record
<?php
namespace Service\Log;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Security\Core\User\UserInterface;
use Doctrine\Model\User;
use Doctrine\Model\ActionLog;
class LogManager
{
/**
* #var ObjectManager
*/
protected $om;
/**
* #var TokenStorage
*/
protected $tokenStorage;
/**
* #var User
*/
protected $user;
/**
* #var bool
*/
protected $disabled = false;
public function __construct(TokenStorage $tokenStorage = null)
{
$this->tokenStorage = $tokenStorage;
}
public function setEntityManager(ObjectManager $om)
{
$this->om = $om;
}
public function log(string $namespace, string $action, string $message = null, array $changeSet = null)
{
$log = new ActionLog;
$log
->setNamespace($namespace)
->setAction($action)
->setMessage($message)
->setChangeset($changeSet)
;
if ($this->isDisabled()) {
return;
}
if (!$log->getUser()) {
$user = $this->getUser();
$log->setUsername(
$user instanceof UserInterface
? $user->getUsername()
: ''
);
$user instanceof User && $log->setUser($user);
}
$this->om->persist($log);
$this->om->flush();
}
public function setUser(User $user): self
{
$this->user = $user;
return $this;
}
public function getUser(): ?UserInterface
{
if (!$this->user) {
if ($token = $this->tokenStorage->getToken()) {
$this->user = $token->getUser();
}
}
return is_string($this->user) ? null : $this->user;
}
public function disable(bool $disabled = true): self
{
$this->disabled = $disabled;
return $this;
}
public function isDisabled(): bool
{
return $this->disabled;
}
}
User entity dump. As you can see infos come from the database.
User {#417 ▼
#name: "John Doe"
#email: "john_doe#example.com"
#password: "ec40577ad8057ee34ce0bb9414673bf3"
#createdAt: DateTime #1523344938 {#427 ▶}
#enabled: true
#lastLogin: null
#id: 1
}
# Associated database row
'1', 'John Doe', 'john_doe#example.com', 'ec40577ad8057ee34ce0bb9414673bf3', '2018-04-10 07:22:18', '1', '1', null
The assert was right, the User instance being passed to ActionLog::setUser method was not a known reference from the ORM point of view.
What happens: the object comes from the authentication process which unserialize the User data from session storage on each request (what suggested yceruto) and a User instance is created.
My custom userProvider should refresh the user object via the ORM but it doesn't, hence the "new reference" upon persist. I have no idea why although my UserProvider implementation lets suppose it should:
/**
* #var ObjectManager
*/
protected $em;
public function __construct(ObjectManager $em)
{
$this->em = $em;
}
public function loadUserByUsername($username)
{
$user = $this->em->getRepository(User::class)->loadUserByUsername($username);
if ($user && $user->isAdmin()) {
return $user;
}
throw new UsernameNotFoundException(
sprintf('Username "%s" does not exist.', $username)
);
}
public function refreshUser(UserInterface $user)
{
return $this->loadUserByUsername($user->getUsername());
}
public function supportsClass($class)
{
return UserInterface::class === $class;
}
This said, I managed to (temporarily) solve the issue using the ORM proxy mechanism with the help of the Doctrine\ORM\EntityManager::getReference method, this can be done since the rebuilt object from session holds the User id (primary key).
The fix consist in replacing the following instruction in the Log_manager service:
$this->user = $token->getUser();
# ↓ BECOMES ↓
$this->user = $this->om->getReference(User::class, $token->getUser()->getId());
Any idea on this? Misuse? Github issue? Whatever the reason, comments are quite welcome.

Symfony 2.6 get Service LoggedUser Catchable Fatal Error: Argument 3 must be an instance

I want to implement the security.token_storage as a service to get the user that is logged in; so when the user writes a post or a comment the field "Author" is automatically set.
I cannot get it working:
Attempted to call method "get" on class "Blog\BlogBundle\Services\PostManager".
How can I implement it as a service and use it?
The UserManager (as a service):
namespace Usuarios\UsersBundle\Services;
class UserManager
public function getloggedUser()
{
$loggedUser = $this->get('security.token_storage')->getToken()->getUser();
return $loggedUser;
}
The service.yml for the UserManager config:
services:
user_manager:
class: %user_manager.class%
arguments:
- #doctrine.orm.entity_manager
The PostManager, that uses the getloggedUser function:
namespace Blog\BlogBundle\Services;
class PostManager
private $em;
private $formFactory;
private $um;
/**
* #param EntityManager $em
* #param formFactoryInterface $formFactory
* #param UserManager $um
*/
public function __construct(EntityManager $em, FormFactoryInterface $formFactory, UserManager $um)
{
$this->em = $em;
$this->formFactory = $formFactory;
$this->um = $um;
public function createComment (Post $post, Request $request)
{
$comment = new Comment();
$comment->setPost($post);
//this is the line failing:
$comment->setAuthorName($this->um->getloggedUser());
$form = $this->formFactory->create(new CommentType(), $comment);
$form->handleRequest($request);
if ($form->isValid()) {
$this->em->persist($comment);
$this->em->flush();
return true;
}
return $form;
Note "user_manager" is defined as a service and is fully functional since other functions using it are working. Why cannot I call the UserManager service from the PostManager service? The error I get is:
Catchable Fatal Error: Argument 3 passed to Blog\BlogBundle\Services\PostManager::__construct() must be an instance of Usuarios\UsersBundle\Services\UserManager, none given, called in C:\xampp\htdocs\eScribely2\app\cache\dev\appDevDebugProjectContainer.php on line 2535 and defined
services do not have the get convenience method that controllers do. You need to pass the container in as an argument when you build the service and store it as a member variable and then call you service.
$this->container->get('user_manager');
Optionally you can just pass in your user manger and not have to use the container at all.

Symfony2: SonataAdminBundle - How can i get the object representing the current user inside an admin class?

I use the sonata-admin bundle.
I have the relationship with the user (FOSUserBundle) in the PageEntity.
I want to save the current user which create or change a page.
My guess is get the user object in postUpdate and postPersist methods of the admin class and this object transmit in setUser method.
But how to realize this?
On the google's group I saw
public function setSecurityContext($securityContext) {
$this->securityContext = $securityContext;
}
public function getSecurityContext() {
return $this->securityContext;
}
public function prePersist($article) {
$user = $this->getSecurityContext()->getToken()->getUser();
$appunto->setOperatore($user->getUsername());
}
but this doesn't work
In the admin class you can get the current logged in user like this:
$this->getConfigurationPool()->getContainer()->get('security.token_storage')->getToken()->getUser()
EDIT based on feedback
And you are doing it this? Because this should work.
/**
* {#inheritdoc}
*/
public function prePersist($object)
{
$user = $this->getConfigurationPool()->getContainer()->get('security.token_storage')->getToken()->getUser();
$object->setUser($user);
}
/**
* {#inheritdoc}
*/
public function preUpdate($object)
{
$user = $this->getConfigurationPool()->getContainer()->get('security.token_storage')->getToken()->getUser();
$object->setUser($user);
}
Starting with symfony 2.8, you should use security.token_storage instead of security.context to retrieve the user. Use constructor injection to get it in your admin:
public function __construct(
$code,
$class,
$baseControllerName,
TokenStorageInterface $tokenStorage
) {
parent::__construct($code, $class, $baseControllerName);
$this->tokenStorage = $tokenStorage;
}
admin.yml :
arguments:
- ~
- Your\Entity
- ~
- '#security.token_storage'
then use $this->tokenStorage->getToken()->getUser() to get the current user.
I was dealing with this issue on the version 5.3.10 of symfony and 4.2 of sonata. The answer from greg0ire was really helpful, also this info from symfony docs, here is my approach:
In my case I was trying to set a custom query based on a property from User.
// ...
use Symfony\Component\Security\Core\Security;
final class YourClassAdmin extends from AbstractAdmin {
// ...
private $security;
public function __construct($code, $class, $baseControllerName, Security $security)
{
parent::__construct($code, $class, $baseControllerName);
// Avoid calling getUser() in the constructor: auth may not
// be complete yet. Instead, store the entire Security object.
$this->security = $security;
}
// customize the query used to generate the list
protected function configureQuery(ProxyQueryInterface $query): ProxyQueryInterface
{
$query = parent::configureQuery($query);
$rootAlias = current($query->getRootAliases());
// ..
$user = $this->security->getUser();
// ...
return $query;
}
}

Add a default role during user registration with FOSUserBundle

Version : Symfony 2.2
I'm trying to add a default role when a user register on my website. I use FOSUserBundle and i see that when a user register the role field is empty in a database.
I begin with this huge bundle and it's not very easy to understand. So i read all the documentation and i'm not sur what to do.
For now, i create an Event to add this role dynamically, but it doesn't work (i have no error but my database is still empty) I'm not even sur this is the good way to do that ?
My Event :
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FormEvent;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class AddDefaultRoleListener implements EventSubscriberInterface {
private $container;
public function __construct(Container $container)
{
$this->container = $container;
}
/**
* {#inheritDoc}
*/
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_SUCCESS => 'onAddDefaultRoleSuccess',
);
}
public function onAddDefaultRoleSuccess(FormEvent $event)
{
$doctrine = $this->container->get('doctrine');
$em = $doctrine->getManager();
$user = $event->getForm()->getData();
$user->addRole('ROLE_USER');
//$user->setRoles(array('ROLE_USER'));
$em->persist($user);
}
}
As you see i create a simple event which listen on REGISTRATION_SUCCESS, but nothing seems to work. It's my first try with Events and services. So if someone has an advice, i'll take it :)
The recommended way to do it as indicated by a main contributor to the FOSUserBundle (in the comment here linked) is to register an Event Listener on the REGISTRATION_SUCCESS event and use the $event->getForm()->getData() to access the user and modify it.
Following those guidelines, I created the following listener (which works!):
<?php
// src/Acme/DemoBundle/EventListener/RegistrationListener.php
namespace Acme\DemoBundle\EventListener;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FormEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Listener responsible for adding the default user role at registration
*/
class RegistrationListener implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_SUCCESS => 'onRegistrationSuccess',
);
}
public function onRegistrationSuccess(FormEvent $event)
{
$rolesArr = array('ROLE_USER');
/** #var $user \FOS\UserBundle\Model\UserInterface */
$user = $event->getForm()->getData();
$user->setRoles($rolesArr);
}
}
Also, the service needs to be registered as follows:
// src/Acme/DemoBundle/Resources/config/services.yml
services:
demo_user.registration_listener:
class: Acme\DemoBundle\EventListener\RegistrationListener
arguments: []
tags:
- { name: kernel.event_subscriber }
Notice that adding a default role in the User class __construct() may have some issues as indicated in this other answer.
What i have done is override the entity constructor:
Here a piece of my Entity/User.php
public function __construct()
{
parent::__construct();
// your own logic
$this->roles = array('ROLE_USER');
}
This is the lazy way. If you want the right and better way see the #RayOnAir answer
I think #RayOnAir solution is right way of doing this. But it will not work due to FOS default role handling
to make possible to persist default role in database one need to override User::setRoles() method (add it to your User entity):
/**
* Overriding Fos User class due to impossible to set default role ROLE_USER
* #see User at line 138
* #link https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Model/User.php#L138
* {#inheritdoc}
*/
public function addRole($role)
{
$role = strtoupper($role);
if (!in_array($role, $this->roles, true)) {
$this->roles[] = $role;
}
return $this;
}
Tested under:
Symfony version 2.3.6,
FOSUserBundle 2.0.x-dev
You can add an Event Subscriber to a Form Class and use the form event "formEvents::POST_SUBMIT"
<?php
//src/yourNS/UserBundle/Form/Type/RegistrationFormType.php
use Symfony\Component\Form\FormBuilderInterface;
use FOS\UserBundle\Form\Type\RegistrationFormType as BaseType;
use yourNS\UserBundle\Form\EventListener\AddRoleFieldSubscriber;
class RegistrationFormType extends BaseType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
// add your custom field
$builder->add('firstName')
->add('lastName')
->add('address')
//...
//...
->add('phone');
$builder->addEventSubscriber(new AddRoleFieldSubscriber());
}
public function getName()
{
return 'yourNS_user_registration';
}
}
Now the logic for adding the role field resides in it own subscriber class
<?php
//src/yourNS/UserBundle/Form/EventListener/AddRoleFieldSubscriber.php
namespace yourNS\UserBundle\Form\EventListener;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class AddRoleFieldSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(FormEvents::POST_SUBMIT => 'setRole');
}
public function setRole(FormEvent $event)
{
$aRoles = array('ROLE_USER');
/** #var $user \FOS\UserBundle\Model\UserInterface */
$user = $event->getForm()->getData();
$user->setRoles($aRoles);
}
}
Ok now it's working with that :
public function onAddDefaultRoleSuccess(FilterUserResponseEvent $event)
{
$doctrine = $this->container->get('doctrine');
$em = $doctrine->getManager();
$user = $event->getUser();
$user->addRole('ROLE_BLOGGER');
$em->persist($user);
$em->flush();
}
I change my listener and know use REGISTRATION_COMPLETED. If someone has a better idea to do that, don't hesitate :)
public function __construct()
{
parent::__construct();
$this->setRoles(["ROLE_WHATEVER"]);
}

Resources