Avoid duplicate group relation Many-To-Many [Doctrine2 - Symfony] - symfony

I have a method to find all discussions by array of users.
But I would like discussions with this exactly participants.
User A
User B
User C
are in discussion 1
and User A and User D are in discussion 2
User B and User C are in discussion 3
I would like only the discussion 2 with the array participants [User A, User D] but I have discussion 1 and 2
This is to avoid duplicate Discussion with same participant
public function findDiscussionsWithThisParticipants(array $participants)
{
$participantIds = array_map(function($o) { return $o->getId();}, $participants);
$queryBuilder = $this->createQueryBuilder('d')
->andWhere(':participantIds MEMBER OF d.participants')
->setParameters(array('participantIds' => $participantIds))
;
return $queryBuilder
->getQuery()
->getResult()
;
}
There is no relation to Discussion on User.
class Discussion
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToMany(targetEntity=User::class)
* #Groups({"discussion:write", "discussion:read"})
*/
private $participants;
}

Related

Doctrine concurrency with many-to-many and new entities

I have a Symfony controller action which adds a specified user to the list of followed users (the list is a ManyToMany). The code is quite simple:
public function followUserAction(Request $request, User $followee, $follow)
{
/** #var User */
$user = $this->getUser();
// same user
if ($user === $followee) {
return;
}
$em = $this->getDoctrine()->getManager();
$follow = boolval($follow);
if ($follow && !$user->getFollowedUsers()->contains($followee)) {
$user->addFollowedUser($followee);
} elseif (!$follow && $user->getFollowedUsers()->contains($followee)) {
$user->removeFollowedUser($followee);
}
$em->flush();
}
This is the mapping:
/**
* #var ArrayCollection<int|string, User>
*
* #ORM\ManyToMany(targetEntity="User", fetch="EXTRA_LAZY", inversedBy="followingUsers")
* #ORM\JoinTable(
* joinColumns={#ORM\JoinColumn(name="following_user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="followed_user_id", referencedColumnName="id")}
* )
*/
private $followedUsers;
/**
* #var ArrayCollection<int|string, User>
*
* #ORM\ManyToMany(targetEntity="User", fetch="EXTRA_LAZY", mappedBy="followedUsers")
*/
private $followingUsers;
The problem is that when a user clicks many times I am receiving a Duplicate entry. I followed the Doctrine's article about concurrency:
https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/reference/transactions-and-concurrency.html
But unfortunately it doesn't cover the following case:
Check if record exists (SELECT)
If it doesn't exist => insert it
If it already exists => do nothing
If two users make the first SELECT at the same, they will both try to insert the new record.
Any way to solve it?
You can use UniqueConstraintViolationException as proposed at this answer.
Also I think it's good to block UI or throttle user requests at front.

How to persist entities when one has two parents

Here's my situation:
Framework Symfony 4, using Doctrine(version).
I have three entities : A, B and C.
Entity A has a OneToMany relationship with B, and a OneToMany relationship with C.
Entity B has a OneToMany relationship with C.
In a form, I have a collectionForm about entity C embedded in a collectionForm about entity B.
Adding entities B & C is optional in the form.
However, when trying to add entity C (and therefore entity B), seeing as entity C has two parents (A & B), doctrine fails to know in which order to persist those, and gives the following error :
A new entity was found through the relationship 'App\Entity\EntityC#entityB' that was not configured to cascade persist operations for entity: App\Entity\EntityB#0000000010cd7f460000000077359844. 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"}). If you cannot find out which entity causes the problem implement 'App\Entity\EntityB#__toString()' to get a clue.
How do I tell doctrine to persist entity A first, then B and then C in this case ?
So far, I've tried removing the OneToMany relationship between B and C, without any luck, and adding a ManyToOne relationship in entityC for entity B.
I feel if I could bypass this relationship between B and C this could work, but I don't know how.
EntityA :
class entityA extends BaseEntityA {
/**
* #ORM\OneToMany(targetEntity="App\Entity\EntityB", mappedBy="entityA", cascade={"persist"})
* #ORM\JoinColumn(name="id", referencedColumnName="entityA_id", nullable=false)
*/
protected $entitiesB;
/**
* #ORM\OneToMany(targetEntity="App\Entity\EntityC", mappedBy="entityA", cascade={"persist"})
* #ORM\JoinColumn(name="id", referencedColumnName="entityA_id", nullable=false)
*/
protected $entitesC;
...
/**
* Add EntityB entity to collection (one to many).
*
* #param \App\Entity\EntityB $entityB
*
* #return \App\Entity\EntityA
*/
public function addEntityB(\App\Entity\EntityB $entityB)
{
$this->entitiesB[] = $entityB;
$entityB->setEntityA($this);
return $this;
}
/**
* Add EntityC entity to collection (one to many).
*
* #param \App\Entity\EntityC $entityC
*
* #return \App\Entity\EntityA
*/
public function addEntityC(\App\Entity\EntityC $entityC)
{
$this->entitiesC[] = $entityC;
$vehicle->setEntityA($this);
return $this;
}
Entity B :
class EntityB extends BaseEntityB {
/**
* #ORM\OneToMany(targetEntity="App\Entity\EntityC", mappedBy="entityB", cascade={"persist"})
* #ORM\JoinColumn(name="id", referencedColumnName="entityB_id", nullable=false)
*/
protected $entitiesC;
...
/**
* #param Collection $entitiesC
*
* #return $this
*/
public function setEntityC(Collection $entitiesC)
{
$this->entitiesC = $entitiesC;
return $this;
}
/**
* Add EntityC entity to collection (one to many).
*
* #param \App\Entity\EntityC $entityC
*
* #return \App\Entity\EntityB
*/
public function addEntityC(\App\Entity\EntityC $entityC)
{
$this->entitiesC[] = $entityC;
$entityC->setEntityB($this);
$entityC->setEntityA($this->getEntityA());
// Things I tried to remove the relationship between B and C
// $this->getEntityA()->addEntityC($entityC);
// $entityC->setEntityB($this);
return $this;
}
Entity C :
class EntityC extends BaseEntityC {
// Somtehing I tried at some point
// /**
// * #ORM\ManyToOne(targetEntity="EntityB", inversedBy="entitiesC", cascade={"persist"})
// * #ORM\JoinColumn(name="entityB_id", referencedColumnName="id")
// */
// protected $entityB;
...
}
I would like to persist this entities in the right order (A then B then C), using only one form if possible.

Many to many relation can get associated object

I have a user and a school entity. This both entities have a many to many relation.
class User
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\School")
* #ORM\JoinTable(name="user_school",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="school_id", referencedColumnName="id")}
* )
*/
private $school;
public function __construct()
{
$this->school = new ArrayCollection();
}
}
class School
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
public function getId()
{
return $this->id;
}
}
If i call the method for retriving the school id in my controller like:
public function indexAction(Request $request)
{
$user = $this->getUser();
$school = $user->getSchool();
echo $school->getId();
}
I get the error message
Attempted to call an undefined method named "getId" of class "Doctrine\ORM\PersistentCollection"
Can someone give me hint , what i'm doing wrong?
Even though $user->getSchool(); is singular in method name, it returns a PersistentCollection - a collection of schools (since the relationship is ManyToMany). If you want to get a specific school's id, you would have to iterate through the schools, like so:
$scools = $user->getSchool()->toArray();
foreach ($scools as $scool) {
// do something with $school
}
In the mapping you defined, User::$school is a ManyToMany, which means the result of getSchool will be a Collection of Schools, not a single School entity.
Two scenarios:
A User can have multiple Schools. Then you probably should rename $school to $schools and getSchool to getSchools. And you can't call $user->getSchools()->getId() since $user->getSchools() is a Collection and does not have a getId method. Check #NoyGabay's answer for a way to access Schools ids.
A User can only have one School. Then you did not define your mapping correctly; you wanted a ManyToOne instead of ManyToMany. Check Doctrine's documentation for association mapping.

ManyToMany relationship with extra fields in symfony2 orm doctrine

Hi i have that same question as here: Many-to-many self relation with extra fields? but i cant find an answer :/ I tried first ManyToOne and at the other site OneToMany ... but then i could not use something like
public function hasFriend(User $user)
{
return $this->myFriends->contains($user);
}
because there was some this problem:
This function is called, taking a User type $user variable and you then use the contains() function on $this->myFriends.
$this->myFriends is an ArrayCollection of Requests (so different type than User) and from the doctrine documentation about contains():
The comparison of two elements is strict, that means not only the value but also the type must match.
So what is the best way to solve this ManyToMany relationship with extra fields? Or if i would go back and set the onetomany and manytoone relationship how can i modify the hasFriend method? To example check if ID is in array collection of ID's.
EDIT: i have this table... and what i need is:
1. select my friends... and my followers ...check if i am friend with him or not. (because he can be friend with me and i dont have to be with him... like on twitter). I could make manytomany but i need extra fields like: "viewed" "time when he subscribe me" as you can see at my table.
And make query like this and then be able in twig check if (app.user.hasFriend(follower) or something like that)
$qb = $this->createQueryBuilder('r')
->select('u')
->innerJoin('UserBundle:User', 'u')
->Where('r.friend_id=:id')
->setParameter('id', $id)
->orderBy('r.time', 'DESC')
->setMaxResults(50);
return $qb->getQuery()
->getResult();
I was trying to have a many to many relationship with extra fields, and couldn't make it work either... The thing I read in a forum (can't remember where) was:
If you add data to a relationship, then it's not a relationship anymore. It's a new entity.
And it's the right thing to do. Create a new entity with the new fields, and if you need it, create a custom repository to add the methods you need.
A <--- Many to many with field ---> B
would become
A --One to many--> C (with new fields) <-- One to many--B
and of course, C has ManyToOne relationships with both A and B.
I searched everywhere on how to do this, but in the end, it's the right thing to do, if you add data, it's no longer a relationship.
You can also copy what contains usually do, or try to overwrite it in a custom repository, to do whatever you need it to do.
I hope this helps.
I'm adding another answer since it has nothing to do with my original answer. Using the new info you posted, I'm calling the table/entity you posted "Follower". The original entity, "User".
What happens if you create the following associations:
namespace Acme\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Acme\UserBundle\Entity\User
*
* #ORM\Table()
* #ORM\Entity
*/
class User
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="Acme\FollowerBundle\Entity\Follower", mappedBy="followeduser")
*/
protected $followers;
/**
* #ORM\OneToMany(targetEntity="Acme\FollowerBundle\Entity\Follower", mappedBy="followeeuser")
*/
protected $followees;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
public function __construct()
{
$this->followers = new \Doctrine\Common\Collections\ArrayCollection();
$this->followees = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add followers
*
* #param Acme\FollowerBundle\Entity\Follower $follower
*/
public function addFollower(\Acme\FollowerBundle\Entity\Follower $follower)
{
$this->followers[] = $follower;
}
/**
* Add followees
*
* #param Acme\FollowerBundle\Entity\Follower $followee
*/
public function addFollowee(\Acme\FollowerBundle\Entity\Follower $followee)
{
$this->followees[] = $followee;
}
/**
* Get followers
*
* #return Doctrine\Common\Collections\Collection
*/
public function getFollowers()
{
return $this->followers;
}
/**
* Get followees
*
* #return Doctrine\Common\Collections\Collection
*/
public function getFollowees()
{
return $this->followees;
}
}
namespace Acme\FollowerBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Acme\FollowerBundle\Entity\Follower
*
* #ORM\Table()
* #ORM\Entity
*/
class Follower
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Acme\UserBundle\Entity\User", inversedBy="followers")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $followeduser;
/**
* #ORM\ManyToOne(targetEntity="Acme\UserBundle\Entity\User", inversedBy="followees")
* #ORM\JoinColumn(name="followee_id", referencedColumnName="id")
*/
protected $followeeuser;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set followeduser
*
* #param Acme\UserBundle\Entity\User $followeduser
*/
public function setFolloweduser(\Acme\UserBundle\Entity\User $followeduser)
{
$this->followeduser = $followeduser;
}
/**
* Get followeduser
*
* #return Acme\UserBundle\Entity\User
*/
public function getFolloweduser()
{
return $this->followeduser;
}
/**
* Set followeeuser
*
* #param Acme\UserBundle\Entity\User $followeeuser
*/
public function setFolloweeuser(\Acme\UserBundle\Entity\User $followeeuser)
{
$this->followeeuser = $followeeuser;
}
/**
* Get followeeuser
*
* #return Acme\UserBundle\Entity\User
*/
public function getFolloweeuser()
{
return $this->followeeuser;
}
}
I'm not sure if this would do the trick, I really don't have much time to test it, but if it doesn't, I thnk that it's on it's way. I'm using two relations, because you don't need a many to many. You need to reference that a user can have a lot of followers, and a follower can follow a lot of users, but since the "user" table is the same one, I did two relations, they have nothing to do with eachother, they just reference the same entity but for different things.
Try that and experiment what happens. You should be able to do things like:
$user->getFollowers();
$follower->getFollowedUser();
and you could then check if a user is being followed by a follower whose user_id equals $userThatIwantToCheck
and you could search in Followers for a Follower whose user = $user and followeduser=$possibleFriend

Implementing a friends list in Symfony2.1 with Doctrine

I want to implement friends list of particular user in Symfony2.1 and Doctrine.
Lets say friends table:
User1 User2 Status //0-pending request,1-accepted
A B 0
A C 1
D A 1
E A 1
Now I want to get A's friends name in the list. For this SQL query can be implemented using UNION as read in many other answers. But I want to implement this in doctrine query builder.
One option is like query separately for two columns and combine the result and sort. But this takes more time to execute and get result. I want to get quick response as soon as possible. Is there any way to query it?
You don't need any additional effort, e.g. by using Doctrine Query Builder!
Simply design the entity class User to have a many-to-many self-reference with User, e.g.:
* #ORM\Table()
* #ORM\Entity()
*/
class User
{
....
/**
* #var string $name
*
* #ORM\Column(name="name", type="string", unique=true, length=255)
*
*/
private $name;
/**
* #ORM\ManyToMany(targetEntity="User", mappedBy="myFriends")
**/
private $friendsWithMe;
/**
* #ORM\ManyToMany(targetEntity="User", inversedBy="friendsWithMe")
* #ORM\JoinTable(name="friends",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="friend_user_id", referencedColumnName="id")}
* )
**/
private $myFriends;
public function __construct() {
$this->friendsWithMe = new \Doctrine\Common\Collections\ArrayCollection();
$this->myFriends = new \Doctrine\Common\Collections\ArrayCollection();
}
}
Then you can simply get the User entity and obtains all the friends as follows:
$user = $this->getDoctrine()
->getRepository('AcmeUserBundle:User')
->findOneById($anUserId);
$friends = $user->getMyFriends();
$names = array();
foreach($friends as $friend) $names[] = $friend->getName();

Resources