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();
Related
Using the Doctrine QueryBuilder, I want to execute a query which in native SQL looks like this:
`SELECT image FROM image i INNER JOIN about_images a ON i.id = a.image_id`;
The result in DQL is as follows:
ImageRepository.php:
return $this->createQueryBuilder('i')
->select('i')
->innerJoin('about_images', 'a', 'WITH', 'i.id = a.imageId')
->getQuery()
->getResult();
Where image is an entity, and about_images is a join table (the result of a #ManyToMany relationship). However, I get the error that about_images is not defined, which makes sense as it is not managed by doctrine.
AboutPage.php (i.e the entity where the join table is created)
/**
* #var Image[]|ArrayCollection
*
* #ORM\ManyToMany(targetEntity="App\Entity\Image", cascade={"persist", "remove"})
* #ORM\JoinTable(name="about_images",
* joinColumns={#ORM\JoinColumn(name="about_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="image_id", referencedColumnName="id", unique=true)})
*/
private $images;
Fields from Image entity:
/**
* #var int
*
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #var string
*
* #ORM\Column(type="string", length=255)
*/
private $image;
/**
* #var File
*
* #Vich\UploadableField(mapping="collection_images", fileNameProperty="image")
* #Assert\File(maxSize="150M", mimeTypes={"image/jpeg", "image/jpg", "image/png", "image/gif"},
* mimeTypesMessage="The type ({{ type }}) is invalid. Allowed image types are {{ types }}")
*/
private $imageFile;
/**
* #var string
*
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $imageAlt;
/**
* #var DateTime
*
* #ORM\Column(type="datetime")
*/
private $updatedAt;
/**
* #var string
*
* #ORM\Column(type="string", nullable=true)
*/
private $alt;
How can I solve this problem? The results should be Image entities.
You can write native SQL and then map the output to your entities using a ResultSetMapper.
For your example it could look something like this in your Repository class:
public function findImagesWithAboutImages()
{
$sql = 'SELECT i.* FROM image i INNER JOIN about_images a ON i.id = a.image_id';
$entityManager = $this->getEntityManager();
$mappingBuilder = new ResultSetMappingBuilder($entityManager);
$mappingBuilder->addRootEntityFromClassMetadata(Image::class, 'i');
$query = $entityManager->createNativeQuery($sql, $mappingBuilder);
// If you want to set parameters e.g. you have something like WHERE id = :id you can do it on this query object using setParameter()
return $query->getResult();
}
If you want related data you will have to add it to the select clause with an alias and then use $mappingBuilder->addJoinedEntityFromClassMetadata() to assign these fields to the joined entity much like above with the root entity.
Your annotations in your entity already define how each field maps to a property and what type it has, so basically you should get an array of Image-entities with everything (but the related entities) loaded usable.
It is not quite clear the example sql with the code you have provided, but if you have a relation defined in your entities, you can join them with a query builder just by telling the relation field of the entity, so I think this should work
return $this->createQueryBuilder('i')
->select('i')
->innerJoin('i.images', 'a')
->getQuery()
->getResult();
As you have defined already your relations in your entities, Doctrine knows how to join your tables, so you just have to specify the relation field name and the alias.
And always remember that you have to use the field name in your entity (normally cameCasedStyle), not the column name at your database tables (normally snake_cased_style).
I am rookie in Symfony Doctrine and need some help with Join entities.
Normally Column are joins by primary key ID
/**
* User
*
* #ORM\Table(name="users")
* #ORM\Entity(repositoryClass="MainBundle\Repository\UserRepository")
* UniqueEntity("email", message="Account with email already exists.")
*/
class User implements AdvancedUserInterface, \Serializable
{
/**
* #var \MainBundle\Entity\PersonDetails
*
* #ORM\ManyToOne(targetEntity="MainBundle\Entity\Person")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="person_details_id", referencedColumnName="id", nullable=true)
* })
*/
private $personDetails = null;
This is ok.
But problem is that I want to Join two columns in Relation OneToOne by id field in User Entity
/**
* User
*
* #ORM\Table(name="users")
* #ORM\Entity(repositoryClass="MainBundle\Repository\UserRepository")
* UniqueEntity("email", message="Account with email already exists.")
*/
class User implements AdvancedUserInterface, \Serializable
{
/**
* #var \MainBundle\Entity\PersonDetails
*
* #ORM\ManyToOne(targetEntity="MainBundle\Entity\Person")
* #ORM\JoinColumn(name="id", referencedColumnName="user_id", nullable=true)
* })
*/
private $personDetails = null;
When I try to join columns on this way I get error
Missing value for primary key id on MainBundle\Entity\PersonDetails
Is it possible to index other field than id or what I trying to do is impossible?
Thanks guys.
You have mixed up the column-name and the field-name that shall be referenced in your #JoinColumn declaration.
#JoinColumn(name="id", referencedColumnName="user_id")
This way Doctrine looks for a field/property named user_id on your User entity. I guess you want the column in the join-table to be named user_id and the entries being id's of the User entity.
UserDetail
/**
* #ORM\Entity
*/
class UserDetail
{
/**
* #ORM\ManyToOne(
* targetEntity="User",
* inversedBy="details"
* )
* #ORM\JoinColumn(
* name="user_id",
* referencedColumnName="id"
* )
*/
protected $user;
public function setUser(User $user)
{
$this->user = $user;
return $this;
}
/** #ORM\Column() */
protected $key;
/** #ORM\Column() */
protected $value;
public function __construct($key, $value)
{
$this->key = $key;
$this->value = $value;
}
User
class User
{
/**
* #ORM\Id()
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #ORM\OneToMany(
* targetEntity="UserDetail",
* mappedBy="user",
* cascade={
* "persist",
* "remove",
* "merge"
* },
* orphanRemoval=true
* )
*/
protected $details;
public function __construct()
{
$this->details = new ArrayCollection();
}
public function addDetail(UserDetail $detail)
{
$detail->setUser($this);
$this->details->add($detail);
return $this;
}
Now if you add a detail to your User like this and persist/flush afterwards:
$user->addDetail(new UserDetail('Height', '173cm'));
This will result in a join-colum in the user_detail table that looks like this:
| key | value | user_id |
|---------------|-----------|---------|
| Height | 173cm | 1 |
Citing Doctrine documentation:
It is not possible to use join columns pointing to non-primary keys.
Doctrine will think these are the primary keys and create lazy-loading
proxies with the data, which can lead to unexpected results. Doctrine
can for performance reasons not validate the correctness of this
settings at runtime but only through the Validate Schema command.
I had the same problem, I solved it by performing the mapping only to fields that are primary key. If I needed to get the related entities by other fields, I implemented methods in the Entity repository.
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.
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
So, i have the following structure of entities:
/**
* #ORM\Entity
*/
class Group
{
/**
* Many-To-Many, Unidirectional
*
* #var ArrayCollection $permissions
*
* #ORM\ManyToMany(targetEntity="Permission")
* #ORM\JoinTable(name="group_has_permission",
* joinColumns={#ORM\JoinColumn(name="group_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="permission_id", referencedColumnName="id")}
* )
*/
protected $permissions;
public function __construct()
{
$this->permissions = new ArrayCollection();
}
}
/**
* #ORM\Entity
*/
class Permission {}
It's just an example, but i'm confused. I need another entity probably called "group_has_permission" with two fields: group_id and permission_id, right? Or am i wrong?
You don't need to create a new entity.
Doctrine will create for you a group table, a permission table & a join table in order to link a group to multiple permissions. This is transparent for you.