Doctrine saves entities twice - symfony

Iam using Symfony(4.3) and Doctrine. When I want to get a user like this in the constructor of the script:
$userRepo = $this->em->getRepository(User::class);
$this->systemUser = $userRepo->findOneBy([
"firstName" => "system",
"lastName" => "system",
]);
And save the reference to this user for example at a createdBy-field like this:
$newUser->setName("test");
$newUser->setCreatedBy($this->systemUser);
$this->entityManager->persist($newUser);
$this->entityManager->flush();
The systemUser is persisted to the database after every flush().
I already tried to get only the reference with
$this->systemUser = $this->entityManager->getReference(User::class, $this->systemUser->getId());
But this doesn't work either.
Edit:
The setCreatedBy-Method:
public function setCreatedBy(User $user): void {
$this->createdBy = $user;
}
The 'createdBy'-Field:
/**
* #var User
* #ORM\ManyToOne(targetEntity="App\Entity\User", cascade={"persist"})
* #ORM\JoinColumn(nullable=true)
*/
protected $createdBy;

You have configured the cascade={"persist"} option for the relation which means Doctrine will save "new" entities found through the relation.
As your question suggests the createdBy field can only contain a relationship to already existing users. This means cascade is definitely not necessary here. It's also the reason why existing entities are persisted again in your case.
Remove the cascade option from the mapping-configuration for the property to resolve your issue.
Clear your cache afterwards.

To answer my own question:
I forgot to mention that I insert ~ 70.000 items in a loop and use clear() every 100 rows. The problem is the use of the entity manager's clear()-method. it detaches all doctrine-managed entites so doctrine thinks that these entities are new and saves them to the db. So I have to do it like this:
$newUser->setCreatedBy($this->em->getReference(User::class, $this->systemUser->getId()));

Related

Symfony 3 - Update Many-To-Many

I have been looking around for a clean solution on how to update (keep in sync) a many to many relationship?
I have the following scenario:
A Sprint Entity owns the Many To Many relationship towards the Ticket entity.
When editing a Ticket (or Sprint, but I am not there yet), I want to be able to select (checkboxes) the Sprints that this ticket belongs to.
Upon persistance (save), I want to update my join table tickets_sprint (which is just a join table on ticket_id, sprint_id).
Adding Sprints to the Ticket seems easy enough, but removing Sprints from the Ticket is not reflected at all.
Code
Ticket Entity contains this method for adding a Ticket to a Sprint:
public function setSprints($sprints) {
/**
* #var $sprint \AppBundle\Entity\Sprint
*/
foreach ($sprints as $sprint) {
$this->sprints[] = $sprint;
$sprint->addTicket($this);
}
}
I have read here that the only way to go would be to remove all relations and re-save them upon persistance.
Coming from the Laravel world, this hardly feels like a good idea :)
This is how it is done in Laravel:
/**
* #param \App\User $user
* #param \App\Http\Requests\StoreUserRequest $request
* #return \Illuminate\Http\RedirectResponse
* Update the specified resource in storage.
*/
public function update(User $user, StoreUserRequest $request)
{
$user->fill($request->input());
$user->employee_code = strtolower($user->employee_code);
$user->roles()->sync($request->role ? : []);
$user->save();
\Session::flash('flash_message_success', 'The user was successfully updated.');
return redirect()->route('frontend::users.show', [$user]);
}
All suggestions are welcome!
The EntityType that you may use to create a multiple selectbox doesn't have a by_reference option like CollectionType.
If your Ticket Entity use the "inversedBy" side, you don't need to add the reference in the other object. So you can symply do this :
public function setSprints($sprints) {
$this->sprints = $sprints;
}
Maybe this will be enough to add and remove your elements automatically (Sorry didn't try).
Otherwise you have to do it manually and you can create a new method to remove elements returns by the difference between your new ArrayCollection and the old one.

limit columns returned in relationnal entity symfony2

Is it possible to filter an entity and display only few columns in symfony2?
I think I can do a custom query for this, but it seems a bit dirty and I am sure there is a better solution.
For example I have my variable $createdBy below, and it contains few data that shouldnt be displayed in this parent entity such as password etc...
/**
* #var Customer
*
* #ORM\ManyToOne(targetEntity="MyCompany\Bundle\CustomerBundle\Entity\Customer")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="created_by", referencedColumnName="id", nullable=false)
* })
*/
protected $createdBy;
So I need to display my Customer entity, but only containing fields like id and name for example.
EDIT :
I already have an instance of Project, the entity with my createdBy field, and I want to grab my customer data 'formatted' for this entity and not returning too much fields like password ...
Thanks
It sounds like expected behavior to me. The doctrine documentation seems to imply that eager fetching is only one level deep.
According to the docs:
Whenever you query for an entity that has persistent associations and
these associations are mapped as EAGER, they will automatically be
loaded together with the entity being queried and is thus immediately
available to your application.
http://doctrine-orm.readthedocs.org/en/latest/reference/working-with-objects.html#by-eager-loading
The entity being queried has eager on createdBy so it will be populated.
to bypass you can create a method in your entity repository as following :
// join entities and load wanted fields
public function findCustom()
{
return $this->getEntityManager()
->createQuery(
'SELECT p FROM AppBundle:Product p ORDER BY p.name ASC'
)
->getResult();
}
hope this helps you
try this and let me know if it works, you should fill the right repository name
'XXXXBundle:CustomerYYYY', 'c'
public function findUser($user_id){
$qb = $this->_em->createQueryBuilder('c')
->select(array('c', 'cb.id', 'cb.name'))
->from('XXXXBundle:Customer', 'c')
->where('c.id <> :id')
->leftJoin('c.createdBy', 'cb')
->setParameter('id', $user_id)->getQuery();
if ($qb != null)
return $qb->getOneOrNullResult();
return null;
}

Remove all related children in Symfony entity

Suppose we have a field with ManyToMany relation as
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="Users")
* #ORM\JoinTable(name="users_roles",
* joinColumns={#ORM\JoinColumn(name="User_Id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="Role_Id", referencedColumnName="id")})
*/
protected $userRole;
To remove one related element from table we can have this function in our entity:
/**
* Remove userRole
* #param \Acme\MyBundle\Entity\Users $user
*/
public function remvoveUserRole(\Acme\MyBundle\Entity\Users $user)
{
$this->userRole->removeElement($user);
}
The Question:
The ArrayCollection type has the function removeElement which is used to remove one element of the relationship. There is another function clear which in api says Clears the collection, removing all elements, therefore can I have a function like below in my entity to clear all the related elements so that by flushing it removes all?
/**
* Remove all user Roles
*/
public function remvoveAllUserRole()
{
$this->userRole->clear();
}
will it work for just ManyToMany related tables or it might work for ManyToOne, too?
Ţîgan Ion is right - removeElement/clear only removes those elements from memory.
However, I think you could achieve something as close depending on how did you configure cascade and orphanRemoval in your relationship.
$em = ...; // your EntityManager
$roles = $user->getRoles();
$roles->clear();
$user = $em->merge($user); // this is crucial
$em->flush();
In order for this to work, you need to configure User relationship to
cascade={"merge"} - this will make $em->merge() call propagate to roles.
orphanRemoval = true - since this is #ManyToMany, this will make EntityManager remove free-dangling roles.
Can't test this now, but as far as I can see it could work. I will try this out tomorrow and update the answer in need be.
Hope this helps...
Note: This logic works for ManyToMany relationship, but not for ManyToOne
I tested the way to delete all related roles for specific use (ManyToMany) and it worked. What you need is to define a function in your UserEntity as
/**
* Remove all user Roles
*/
public function remvoveAllUserRole()
{
$this->userRole->clear();
}
Then in your controller or anywhere else(if you need) you can call the function as below
$specificUser = $em->getRepository('MyBundle:Users')->findOneBy(array('username' => 'test user'));
if (!empty($specificUser)) {
$specificUser->removeAllUserRole();
$em->flush();
}
Then it will delete all related roles for the test user and we don't need to use the for loop and remove them one by one
if I'm not mistaken, this will not work, you have to delete the "role
s" from the arrayCollection directly from the database
$roles = $user->getRoles()
foreach $role from $roles
$em->remove($role);
$em->flush();
now you should get an empty collection
p.s: the best way is to test your ideas

Doctrine one-to-many situation: how to easily fetch related entities

To simplify, two entities are defined: User and Comment. User can post many comments and every comment has only one user assigned, thus Comment entity has:
/**
* #var \Frontuser
*
* #ORM\ManyToOne(targetEntity="Frontuser")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="ownerUserID", referencedColumnName="id")
* })
*/
private $owneruserid;
However, when in action:
$orm = $this->getDoctrine()->getManager();
$repo = $orm->getRepository('CompDBBundle:Comment');
$repo->findBy(array('owneruserid' => $uid);
Error occured, that there's no such field like owneruserid.
How can I fetch all the user's comments then? The same happens to similar relations in my DB - looks likes you cannot run find() with foreign keys as parameters. I believe a function $user->getComments() should be automatically generated/recognised by Doctrine to allow efficient, quick access to related entities.
The example's simple but, what if there are more entities related to my User in the same way? Do I have to declare repositories for each and try to fetch them by it's owneruserid foreign keys?
Using doctrine, when you define a related entity it's type is the entity class (in this case FrontUser). Therefore firstly your related entity variable name is misleading. It should be e.g.
private $ownerUser;
Then, in order to do a findBy on a related entity field you must supply an entity instance e.g.
$orm = $this->getDoctrine()->getManager();
$userRepo = $orm->getRepository('CompDBBundle:FrontUser');
$user = $userRepo->findById($uid);
$commentRepo = $orm->getRepository('CompDBBundle:Comment');
$userComments = $commentRepo->findByOwnerUser($user);
If you don't have or want to retrieve the user entity you could use a DQL query with the 'uid' as a parameter instead.

Symfony2: Error persisting ManyToMany/OneToMany Relationships

I don't know why, maybe i am missing some basic logic but I always run again into the same issue. I can't persists ManyToMany collections, and it also faces me with OneToMany collections, though I can work around that.
I read through the doctrine documentation, and I think I do understand the thing with mappedBy and inversedBy (where the last one is always the owner and therefor responsible for persisting the data, please correct me if I am wrong).
So here's a basic example that I have right now, which I can't figure out.
I have an Entity called Site:
#Site.php
...
/**
* #ORM\ManyToMany(targetEntity="Category", mappedBy="sites")
*/
protected $categories;
and another one called Category:
#Category.php
...
/**
* #ORM\ManyToMany(targetEntity="Site", inversedBy="categories")
* #ORM\JoinTable(name="sites_categories")
*/
protected $sites;
Using the Symfony2 entity genenerator it added me some getters and setters to my Entites which look like this.
Site:
#Site.php
...
/**
* Add categories
*
* #param My\MyBundle\Entity\Category $categories
*/
public function addCategory(\My\MyBundle\Entity\Category $categories)
{
$this->categories[] = $categories;
}
/**
* Get categories
*
* #return Doctrine\Common\Collections\Collection
*/
public function getCategories()
{
return $this->categories;
}
The same counts for
Category:
#Category.php
...
/**
* Add sites
*
* #param My\MyBundle\Entity\Site $sites
*/
public function addSite(\My\MyBundle\Entity\Site $sites)
{
$this->sites[] = $sites;
}
/**
* Get sites
*
* #return Doctrine\Common\Collections\Collection
*/
public function getSites()
{
return $this->sites;
}
Fair enough.
Now in my controller, I am trying to persist a Site object:
public function newsiteAction() {
$site = new Site();
$form = $this->createFormBuilder($site); // generated with the FormBuilder, so the form includes Category Entity
// ... some more logic, like if(POST), bindRequest() etc.
if ($form->isValid()) {
$em = $this->getDoctrine()
->getEntityManager();
$em->persist($site);
$em->flush();
}
}
The result is always the same. It persists the Site Object, but not the Category entity. And I also know why (I think): Because the Category entity is the owning side.
But, do I always have to do something like this for persisting it? (which is actually my workaround for some OneToMany collections)
$categories = $form->get('categories')->getData();
foreach($categories as $category) {
// persist etc.
}
But I am running into many issues here, like I would have to do the same loop as above for deleting, editing etc.
Any hints? I will really give a cyber hug to the person who can clear my mind about that. Thanks!
.
.
.
UPDATE
I ended up changing around the relationship (owning and inverse side) between the ManyToMany mapping.
If somebody else runs into that problem, you need to be clear about the concept of bidrectional relationships, which took me a while to understand too (and I hope I got it now, see this link).
Basically what anserwed my question is: The object you want to persist must always be the owning site (The owning site is always the entity that has "inversed by" in the annotiation).
Also there is a concept of cascade annotation (see this link, thanks to moonwave99)
So thanks, and I hope that helps somebody for future reference! :)
Regarding OneToMany relationship, you want to know about cascade annotation - from Doctrine docs [8.6]:
The following cascade options exist:
persist : Cascades persist operations to the associated entities.
remove : Cascades remove operations to the associated entities.
merge : Cascades merge operations to the associated entities.
detach : Cascades detach operations to the associated entities.
all : Cascades persist, remove, merge and detach operations to associated entities.
following docs example:
<?php
class User
{
//...
/**
* Bidirectional - One-To-Many (INVERSE SIDE)
*
* #OneToMany(targetEntity="Comment", mappedBy="author", cascade={"persist", "remove"})
*/
private $commentsAuthored;
//...
}
When you add comments to the author, they get persisted as you save them - when you delete the author, comments say farewell too.
I had same issues when setting up a REST service lately, and cascade annotation got me rid of all the workarounds you mentioned before [which I used at the very beginning] - hope this was helpful.

Resources