Set logged user in another entity with FOSUserBundle - symfony

I want to set the logged user in a set method for another entity. For example, I have the entities Article and User (User is an FOSUser entity). I want to set the current user as author for an article.
In the Article entity, I write:
/**
* #ORM\ManyToOne(targetEntity="UserBundle\Entity\User", inversedBy="articles")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;
/**
* Set user
*
* #param \UserBundle\Entity\User $user
* #return Article
*/
public function setUser(\UserBundle\Entity\User $user = null)
{
//This line is the problem
$this->user = $this->container->get('security.context')->getToken()->getUser();
return $this;
}
I'm trying to get the user from the container, but when I perform a save, the set value is null. I test the line in a controller and I get successfully the current user. Maybe inside an entity the method for get the logged user is different?

An Entity cannot access the service container, also you cannot use any service inside.
To make it working, call the Article::setUser() from a controller or another context that can use the container. e.g. :
$currentUser = $this->container->get('security.token_storage')->getToken()->getUser();
$article = new Article();
$article->setUser($currentUser);
// ...
$em = $this->getDoctrine()->getManager();
$em->persist($article);
$em->flush();
And the field will be correctly filled.

You have to set the user with the function chalasr said
$article->setUser($this->getUser());
persist it to db and you are done :)

Related

Setting a default value for a one-on-one association

Doctrine is a beast in combination with Symfony, but I can't seem to find good examples on how to achieve what I need.
My user schema is pretty standard. I want to set a one-to-one association that points to the forum permission a user has.
For this to work, I need to create a "default entity" that holds the default permissions given to the user upon creation.
Here is the User#forumPermission association
/**
* #var $forumPermissions ?ForumPermission
* One User instance has One Forum Permission instance.
* #ORM\OneToOne(targetEntity="ForumPermission")
* #JoinColumn(name="forum_permission_id", referencedColumnName="id")
*/
private $forumPermission;
/**
* #return ForumPermission
*/
public function getForumPermission() : ?ForumPermission
{
return $this->forumPermission;
}
/**
* #param ForumPermission $forumPermission
*/
public function setForumPermission(ForumPermission $forumPermission): void
{
$this->forumPermission = $forumPermission;
}
This means the forum_permission table is empty at this time.
After that, I read in the doctrine docs that you can listen to all kinds of events related to flushing and persisting. Here is the docs sections for onFlush
This is what I came up with
public function onFlush(OnFlushEventArgs $eventArgs)
{
$em = $eventArgs->getEntityManager();
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledEntityInsertions() as $entity) {
/* #var $entity User */
if (true === $entity instanceof User
&& null === $entity->getForumPermission()) {
$entity->setForumPermission($this->getDefaultForumPermission($em, $uow));
}
}
}
private function getDefaultForumPermission(
\Doctrine\ORM\EntityManagerInterface $em,
\Doctrine\ORM\UnitOfWork $uow)
{
// Get the default permissions form the database
$defaultForumPermission = $em->getRepository(ForumPermission::class)->find(1);
// I will create a new entity if the default permissions do not exist
if (null === $defaultForumPermission) {
$defaultForumPermission = new ForumPermission();
$uow->persist($defaultForumPermission);
$uow->computeChangeSet($em->getClassMetadata('\App\Entity\ForumPermission'), $defaultForumPermission);
}
return $defaultForumPermission;
}
The docs aren't very clear on what you can do and where you should do it.
I figure I could do this all in the controller, but I like to keep everything where it belongs as intended by design.
So I'm wondering if this is the best way to do it, should I be doing this during prePersist or another event? Any help is greatly appreciated.
If I get it right, each time a User is created you want to create a new ForumPermission associated to this User. The best way to do it is to listen to the prePersist Doctrine event.
See https://symfony.com/doc/current/doctrine/event_listeners_subscribers.html for additional information.

How to disable Blameable-behaviour programmatically in Symfony2

I'm trying to run a console command in symfony2 in which some properties of a certain class are being updated. One of the properties has got a corresponding reviewedBy-property which is being set by the blameable-behaviour like so:
/**
* #var bool
* #ORM\Column(name="public_cmt", type="boolean", nullable=true)
*/
private $publicCmt;
/**
* #var User $publicCmtReviewedBy
*
* #Gedmo\Blameable(on="change", field="public_cmt")
* #ORM\ManyToOne(targetEntity="My\Bundle\EntityBundle\Entity\User")
* #ORM\JoinColumn(name="public_cmt_reviewed_by", referencedColumnName="id", nullable=true)
*/
private $publicCmtReviewedBy;
When i run the task there's no user which can be 'blamed' so I get the following exception:
[Doctrine\ORM\ORMInvalidArgumentException]
EntityManager#persist() expects parameter 1 to be an entity object, NULL given.
However I can also not disable blameable because it's not registered as a filter by the time i start the task and programmatically trying to set the user through:
// create the authentication token
$token = new UsernamePasswordToken(
$user,
null,
'main',
$user->getRoles());
// give it to the security context
$this->getService('security.context')->setToken($token);
doesn't work. Anyone got an idea?
If you use the StofDoctrineExtensionsBundle you can simply do :
$this->container->get('stof_doctrine_extensions.listener.blameable')
->setUserValue('task-user');
see : https://github.com/stof/StofDoctrineExtensionsBundle/issues/197
First of all, I'm not sure if 'field' cares if you use the database column or the property, but you might need to change it to field="publicCmt".
What you should do is override the Blameable Listener. I'm going to assume you are using the StofDoctrineExtensionsBundle. First override in your config:
# app/config/config.yml
stof_doctrine_extensions:
class:
blameable: MyBundle\BlameableListener
Now just extend the existing listener. You have a couple options - either you want to allow for NULL values (no blame), or, you want to have a default user. Say for example you want to just skip the persist and allow a null, you would override as such:
namespace MyBundle\EventListener;
use Gedmo\Blameable\BlameableListener;
class MyBlameableListener extends BlameableListener
{
public function getUserValue($meta, $field)
{
try {
$user = parent::getUserValue($meta, $field);
}
catch (\Exception $e) {
$user = null;
return $user;
}
protected function updateField($object, $ea, $meta, $field)
{
if (!$user) {
return;
}
parent::updateField($object, $ea, $meta, $field);
}
}
So it tries to use the parent getUserValue() function first to grab the user, and if not it returns null. We must put in a try/catch because it throws an Exception if there is no current user. Now in our updateField() function, we simply don't do anything if there is no user.
Disclaimer - there may be parts of that updateField() function that you still need...I haven't tested this.
This is just an example. Another idea would be to have a default database user. You could put that in your config file with a particular username. Then instead of returning null if there is no user from the security token, you could instead grab the default user from the database and use that (naturally you'd have to inject the entity manager in the service as well).
Slight modification of the above answer with identical config.yml-entry: we can check if a user is set and if not: since we have access to the object-manager in the updateField-method, get a default-user, set it and then execute the parent-method.
namespace MyBundle\EventListener;
use Gedmo\Blameable\BlameableListener;
class MyBlameableListener extends BlameableListener
{
protected function updateField($object, $ea, $meta, $field)
{
// If we don't have a user, we are in a task and set a default-user
if (null === $this->getUserValue($meta, $field)) {
/* #var $ur UserRepository */
$ur = $ea->getObjectManager()->getRepository('MyBundle:User');
$taskUser = $ur->findOneBy(array('name' => 'task-user'));
$this->setUserValue($taskUser);
}
parent::updateField($object, $ea, $meta, $field);
}
}

How to persist entities from an embedded form submission?

I have a Person entity and an Address entity, set up with a bi-directional one-to-one relationship, with the FK on Address:
...
class Person
{
...
/**
* #ORM\OneToOne(targetEntity="Address", mappedBy="person")
*/
protected $address;
...
}
...
class Address
{
...
/**
* #ORM\Id
* #ORM\OneToOne(targetEntity="Person", inversedBy="address")
* #ORM\JoinColumn(name="personID", referencedColumnName="id")
*/
protected $person;
}
The Address Entity does NOT have a dedicated primary key, it instead derives its identity through the foreign key relationship with Person, as explained here
The form I have for creating a new Person also embeds the form for Address. When the form is submitted, this controller action is executed:
public function createAction(Request $request)
{
$person = new Person();
$form = $this->createCreateForm($person);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($person);
$em->flush();
return $this->redirect($this->generateUrl('people'));
}
return array(
'person' => $person,
'form' => $form->createView(),
);
}
I've inspected the data, and $person has its $address property filled out with the proper form details as expected. However, once the $person object is persisted, I get the error:
A new entity was found through the relationship
'Acme\AcmeBundle\Entity\Person#address' that was not configured to
cascade persist operations for entity...
I've tried a couple of things and none seem to work:
Setting cascade={"persist"} on the OneToOne relationship definition on the Person object. Doing so results in error:
Entity of type Acme\AcmeBundle\Entity\Address is missing an assigned
ID for field 'person'...
On the Person#setAddress method, I've taken the $address parameter and manually called $address->setPerson($this) on it. Doesn't work either.
It seems like my problem is that Doctrine is trying to save the Address object before saving the Person object, and it can't because it needs to know the ID of the associated Person first.
For instance, If I alter the the persist code to something like this, it works:
...
// Pull out the address data and remove it from the Person object
$address = $person->getAddress();
$person->setAddress(null);
// Save the person object and flush so we get an ID
$em->persist($person);
$em->flush();
// Now set the person object on the address and save the address
$address->setPerson($person);
$em->persist($address);
$em->flush();
...
How can I do this properly? I want to retain the ability to embed forms with this type of one-to-one relationship, but things are starting to get complicated. How do I get Doctrine to flush the $person object before flushing the $address object, without manually doing it myself like above?
Keep the cascade=persist.
Then modify Person::setAddress
class Person
{
public function setAddress($address)
{
$this->address = $address;
$address->setPerson($this); //*** This is what you are missing ***
This is a very common question but it's hard to search for.
Your mapping is incorrect. You are using #ORM\Id incorrectly on $person.
If you haven't yet, add a real $id to Address and add again `cascade={"persist"}.
class Person
{
...
/**
* #ORM\OneToOne(targetEntity="Address", mappedBy="person", cascade={"persist"})
*/
protected $address;
...
}
...
class Address
{
...
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToOne(targetEntity="Person", inversedBy="address")
* #ORM\JoinColumn(name="personID", referencedColumnName="id")
*/
protected $person;
}
If Person were the owning side, Address should also be automatically persisted by Doctrine, don't know your model but maybe you should consider changing it.

FOS User Bundle, login on many mails

I'm using FOS User Bundle.
Now I created a table with mails for my users, they are able to add as many mails as they want. Now I'm thinking about letting them login on all of this mails kept in this table. Any tips for the best way to do? Should I only change some things in FOS or just create my own login form?
Passwords of course are still in table with users. So I have to somehow check if email is my table with mails and when it's found I have to check match password with the password from the users table (each mail is related to user, by Id of course).
No, only one email can be used to login. However you could provide a way to let the user choose one of his emails he wants to login with.
Another option of course is to override the login listeners of symfony2s security component. E.g. https://gist.github.com/smottt/1075753
class LoginListener
{
/** #var \Symfony\Component\Security\Core\SecurityContext */
private $securityContext;
/** #var \Doctrine\ORM\EntityManager */
private $em;
/**
* Constructor
*
* #param SecurityContext $securityContext
* #param Doctrine $doctrine
*/
public function __construct(SecurityContext $securityContext, Doctrine $doctrine)
{
$this->securityContext = $securityContext;
$this->em = $doctrine->getEntityManager();
}
/**
* Do the magic.
*
* #param InteractiveLoginEvent $event
*/
public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
{
}
}

symfony2 entity oneToMany and methods

Hi i had fully successfully setted my entity onetoMany and ManyToOne i generated setters and getters and in user entity it created this method:
user entity:
/**
* #ORM\OneToMany(targetEntity="TB\RequestsBundle\Entity\Requests", mappedBy="followeeuser")
*/
protected $followees;
requests entity:
/**
* #ORM\ManyToOne(targetEntity="TB\UserBundle\Entity\User", inversedBy="followees")
* #ORM\JoinColumn(name="followee_id", referencedColumnName="id", nullable=false)
*/
protected $followeeuser;
And when i using my own custom queries it works good... but i cant figure out how to use this generated function from symfony:
public function addFollowee(\TB\UserBundle\Entity\User $followee)
{
$this->followees[] = $followee;
}
I dont know what to pass there... i tried first get user object based on id of user from twig... worked good but the error occur:
$user->addFollowee($userRepository->find($target_user_id));
Found entity of type TB\UserBundle\Entity\User on association TB\UserBundle\Entity\User#followees, but expecting TB\RequestsBundle\Entity\Requests
Maybe you should think about what you're trying to before coding it. Grab a pen and a sheet of paper. :)
Tell me if I'm wrong, but here is what I think you're trying to do :
One user can have many "followee".
One "followee" can have one user.
So, a OneToMany relation is ok.
Here is how to write it, from the doc :
Requests.php (btw, you should use Request.php)
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="requests")
**/
private $user;
User.php
/**
* #ORM\OneToMany(targetEntity="Requests", mappedBy="user", cascade={"all"})
**/
private $requests;
public function __construct()
{
$this->requests = new \ArrayCollection();
}
Now you can check if you your relation is ok, and update your schema :
php app/console doctrine:schema:validate
php app/console doctrine:schema:update --force
About getters/setters :
Requests.php
public function getUser()
{
return $this->user;
}
public function setUser(User $user) // Please add a Use statement on top of your document
{
$this->user = $user;
return $this;
}
User.php
public function addRequest(Requests $request)
{
$this->requests->add($request);
return $this;
}
public function removeRequest(Requests $request)
{
$this->requests->removeElement($request);
return $this;
}
// Get requests and set requests (you know how to write those ones)
Now, to set a user to a Request, use
$request->setUser($user);
And to add a Request to a user, use
$user->addRequest($request);

Resources