Symfony Doctrine Query For Many to Many Releationships - php

I have Many to Many relationship between Institutes and Courses. I want to build query that returns only the institutes list whom some courses has been assigned. I have wrote queries in this situation for one to many. but for not many to many. here is the relationships,
class Institutes {
/**
* #ORM\ManyToMany(targetEntity="Courses", inversedBy="institutes")
* #ORM\JoinTable(name="institute_courses",
* joinColumns={#ORM\JoinColumn(name="institute_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="course_id", referencedColumnName="id")}
* )
*/
protected $courses;
}
class Courses {
/**
* #ORM\ManyToMany(targetEntity="Institutes", mappedBy="courses")
*/
protected $institutes;
}
here is the query that i have written, but didn't work properly.
$repository->createQueryBuilder('s')
->leftJoin('CoursesBundle:Courses','c', 'ON c.institutes = s.courses')
->where('s.active = :active')
->andWhere('s.verified = :active')
->setParameter('active', true)
->orderBy('s.name', 'ASC');

This should do the trick:
$repository->createQueryBuilder('i')
->innerJoin('i.courses','c')
->where('i.active = TRUE')
->andWhere('i.verified = TRUE')
->orderBy('i.name', 'ASC');

You can use a JOIN as you would with other kinds of associations. The following query will find all courses which have been assigned at least to one institute:
SELECT c FROM SomeBundle:Courses c JOIN c.institutes i
You can filter the results further by adding a join condition:
SELECT c FROM SomeBundle:Courses c JOIN c.institutes i WITH i.something = :someParam

Related

Fetching all objects of one class with self-referencing ManyToMany

Think of this class:
class Person {
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="Person", fetch="EXTRA_LAZY")
* #ORM\OrderBy({"name" = "ASC"})
*/
public $friends;
/**
*
* #var Person
* #ORM\ManyToOne(targetEntity="Person")
*/
public $bestFriend;
}
I have to iterate a lot over all Persons, so I'd like to fetch join them all at once.
To save memory, I have to do this partially.
So what I can do is:
$this->em->createQuery('SELECT partial p.{id, name, bestFriend} FROM Person p')->getResult();
This is cool, after this query, all persons are in the UoW, and I can traverse the graph via $aPersion->bestFriend->bestFriend without creating an additional query to the DB, since all Persons are in memory.
However, this does not work with the ToMany association. Adding friends to the partial select gives an error. If I want to iterate over all friends, this will first create a query to the join table...
How can I realise the full hydration of the friends-ToMany-assotiation with one query? Maybe a second query could help? Or a clever join clause?
Thanks in advance!
I would create a query in PersonRepository.php with a leftJoin and a addSelect like so:
$qb = $this->em->getRepository('App:Person')
->createQueryBuilder('p')
->leftJoin('p.friends', 'friends')
->select('partial p.{id, name, bestFriend}'}
->addSelect('partial friends.{id, name}') // Retrieve what you want here
->getQuery()->getResult();
return $qb;
I have not tested this, but believe it should work.
#DirkJFaber your answer was right,
in terms of DQL here is my solution:
$this->em->createQuery('
SELECT partial p.{id, name, bestFriend}, f FROM Person p JOIN f.friends f')->getResult();

Doctrine QueryBuilder need change ON condition for leftJoin

I have native sql query with left join when have on with or condition, how to represent it in query builder ?
$query = " SELECT te.id
FROM task_executions AS te
INNER JOIN tasks AS t ON t.id = te.task_id
LEFT JOIN cost_objects AS co ON co.id = t.cost_object_id
LEFT JOIN cost_object_managers AS com ON com.cost_object_id = co.id OR com.cost_object_id = co.parent_id
and I need represent it in query builder. But in User entity I have ManyToMany relation, without separate table and when I try left join WITH condition this is not same what I need. I need change relation for ON
LEFT JOIN cost_object_managers AS com ON com.cost_object_id = co.id OR com.cost_object_id = co.parent_id
User entity
class User
{
...
/**
* #ORM\ManyToMany(targetEntity="CostObject", mappedBy="users")
*/
private $costObjects;
}
CostObject entity
class CostObject
{
/**
* #var CostObject
*
* #ORM\ManyToOne(targetEntity="CostObject", inversedBy="children")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="parent_id", referencedColumnName="id", onDelete="CASCADE")
* })
*/
private $parent;
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="User", inversedBy="costObjects")
* #ORM\JoinTable(name="cost_object_managers",
* joinColumns={#ORM\JoinColumn(name="cost_object_id", referencedColumnName="id", onDelete="CASCADE")},
* inverseJoinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")}
* )
*/
private $users;
and my query builder without condition
$qb->select('te')
->from('AppBundle:TaskExecution', 'te')
->innerJoin('te.task', 't')
->leftJoin('t.costObject', 'co')
->leftJoin('co.users', 'com')
this is $query->getSQL()
SELECT some_name FROM task_executions t0_ INNER JOIN tasks t1_ ON t0_.task_id = t1_.id LEFT JOIN cost_objects c2_ ON t1_.cost_object_id = c2_.id LEFT JOIN cost_object_managers c4_ ON c2_.id = c4_.cost_object_id LEFT JOIN users u3_ ON u3_.id = c4_.user_id ORDER BY t0_.execution_start DESC
In this example I see ON relation condition LEFT JOIN users u3_ ON u3_.id = c4_.user_id. And need change it like in native sql
Now I have
$qb->select('te')
->from('AppBundle:TaskExecution', 'te')
->innerJoin('te.task', 't')
->leftJoin('t.costObject', 'co')
->leftJoin(
'co.users',
'com',
Join::ON,
$qb->expr()->orX(
'co = com.costObjects',
'co.parent = com.costObjects'
)
)
but got error
[Syntax Error] line 0, col 112: Error: Expected end of string, got 'ON'
if I used WITH condition, in my sql represent I still have relation by id, I don't need that
->leftJoin(
'co.users',
'com',
Join::WITH,
$qb->expr()->orX(
'co MEMBER OF com.costObjects',
'co.parent MEMBER OF com.costObjects'
)
)
LEFT JOIN users u3_ ON u3_.id = c4_.user_id AND (EXISTS (SELECT 1 FROM cost_object_managers c5_ INNER JOIN cost_objects c6_ ON c5_.cost_object_id = c6_.id WHERE c5_.user_id = u3_.id AND c6_.id IN (c2_.id)) OR EXISTS (SELECT 1 FROM cost_object_managers c5_ INNER JOIN cost_objects c6_ ON c5_.cost_object_id = c6_.id WHERE c5_.user_id = u3_.id AND c6_.id IN (c2_.parent_id)))
I mean users u3_ ON u3_.id = c4_.user_id AND but in native query we have only LEFT JOIN cost_object_managers AS com ON com.cost_object_id = co.id OR com.cost_object_id = co.parent_id
How it's reproduce in Query Builder with ON condition type?
If you have the query and it works for you, you don't need to do all the work to transform it to DQL or the QueryBuilder programmatic syntax. You can just use Doctrine's Native Query and then - if needed - map the result to your object. Just create a custom repository and in it a new method that roughly looks like this:
public function findTaskExecutionBy...()
{
$query = $this->entityManager->createNativeQuery('SELECT te.id FROM ...');
return $query->getSingleScalarResult(); // If it's just one id you expect
}
You can also use $query->getResult() if you expect multiple id's to be returned. Or use the ResultSetMapping if you want the whole Task-object:
$rsm = new ResultSetMappingBuilder($this->entityManager);
$rsm->addRootEntityFromClassMetadata('App\Entity\Task', 'te');
$query = $this->entityManager->createNativeQuery(
'SELECT te.* FROM ...',
You can also check the Doctrine documentation for a more detailed explanation and some more examples: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/native-sql.html
$rsm
);

Doctrine Join Many To Many without association

I have: two entities with undirectional M:M association.
class ShareInfo
{
// ...
/**
* #ORM\ManyToMany(targetEntity="Item")
* #ORM\JoinTable(name="share_info_items",
* joinColumns={#ORM\JoinColumn(name="share_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="item_id", referencedColumnName="id")})
*
* #var Item[]
*/
private $items;
}
class Item
{
// ...
// This entity has no association with ShareInfo,
// because M:M is undirectional and defined in ShareInfo entity
}
What I want:
Select data from items table (Item entity), where at least one M:M record between Item and ShareInfo exists.
My suggestion which doesn't work (I've got a semantic error):
$queryBuilder
->select('i')
->from(Item::class, 'i')
->innerJoin(ShareInfo::class, 'shareInfo', 'WITH', 'shareInfo.items = i');
In pure SQL I'd do something like this:
SELECT i.*
FROM items i
INNER JOIN share_info_items shareInfo
ON shareInfo.item_id = i.id
Can't believe there is no DQL analog for this. The only solution I can imagine is to split undirectional M:M association into bi-directional
P.S. This question has no duplicates, I checked well.
The way to achieve this is through a subquery:
$em=$this->getDoctrine()->getManager();
$queryBuilder1=$em->createQueryBuilder();
$queryBuilder1->select(array('DISTINCT i.id'))
->from('AppBundle:ShareInfo', 'share_info')
->innerJoin('share_info.items', 'i');
$queryBuilder=$em->createQueryBuilder();
$queryBuilder->select('i')
->from('AppBundle:items', 'i')
->where($queryBuilder->expr()
->in('i.id',$queryBuilder1->getDql()));

query builder, one to many where many is empty

I have an entity Category, and this category has a recursive relationship with itself where each category can be a parent to several other categories. The relationship looks like this:
/**
* #var parent
* #ORM\ManyToOne(targetEntity="Category")
* #ORM\JoinColumn(referencedColumnName="id", onDelete="CASCADE")
*/
private $parent;
/**
* #ORM\OneToMany(targetEntity="Category", mappedBy="parent")
*/
private $children;
I need to make a query builder query that selects all categories that are either children(have a parent) Or the categories that have no parent and also have no children (in other words all categories except the parents that have children) I can't seem to be able to do it. Please help.
You need next DQL query:
$query = 'SELECT c FROM AcmeBundle:Category c LEFT JOIN c.parent p LEFT JOIN c.children ch WHERE p IS NOT NULL OR (ch IS NULL AND p IS NULL)';
If you need QueryBuilder sequence for this query you can use next code:
$qb = $em->createQueryBuilder();
$query = $qb
->select('c')
->from('AcmeBundle:Category', 'c')
->leftJoin('c.parent', 'p')
->leftJoin('c.children', 'ch')
->where($qb->expr()->orX(
$qb->expr()->isNotNull('p'),
$qb->expr()->andX(
$qb->expr()->isNull('ch'),
$qb->expr()->isNull('p'),
)
))
->getQuery();

Join DQL when intermediate table has not entity mapped

I'm building a repository method to look if a fos_group is assigned to any fos_user before allowing group to be deleted. Basically this is the raw query I'm trying to make using DQL:
SELECT
COUNT(*)
FROM
fos_user u
LEFT JOIN fos_user_user_group fug ON (fug.user_id = u.id)
WHERE
fug.group_id = :group_id
This is how I setup the relationship between Users and Groups:
/**
* #ORM\ManyToMany(targetEntity="Group")
* #ORM\JoinTable(name="fos_user_user_group",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="group_id", referencedColumnName="id")}
* )
*/
protected $groups;
And this is what I have done til now:
$qb = $this->getEntityManager()->createQueryBuilder();
$qb
->select('COUNT(gr.id)')
->from("UserBundle:Group", "gr");
return $qb->getQuery()->getSingleScalarResult();
But I'm stuck since I do not know how to add the join sentence with not mapped table/entity (fos_user_user_group), can anyone give me some push?
You have mapped your property there you are in right way just join the property $groups in user entity it will join your junction table fos_user_user_group and the use what ever you where clause is
$qb
->select('COUNT(g.id)')
->from('UserBundle:User', 'u')
->join('u.groups g')
->where('g.id = :val')
->setParameter('val',$your_group_id);

Categories