Doctrine2: using the querybuilder for many-to-many relationships - symfony

To search people in a system I have written a function in a repository.
This function receives an array with field names and criteria, and uses the querybuilder to formulate a valid DQL-statement.
Everything works fine, until the moment I want to make a search rule like:
"Find all people that belong to a certain domain."
...where the Person and Domain entities have a many-to-many relationship.
According to Symfony2 that relationship is defined correctly, and indeed everything works in Twig templates and controllers.
The function looks as follows:-
private function query($extra_conditions = null)
{
$qb = $this->_em->createQueryBuilder();
$query =
$qb->select('p')
->from('AppMainBundle:Person', 'p')
->leftjoin('AppMainBundle:Domain', 'd')
->where("p.firstname IS NOT NULL")
->andWhere("p.lastname IS NOT NULL")
->andWhere("p.function IS NOT NULL");
if ($extra_conditions!=null)
{
foreach ($extra_conditions as $condition)
{
#filter on firstname
if (key($condition)=='firstname')
$query = $query->andWhere('p.firstname LIKE ?1')
->setParameter(1, $condition['firstname']);
#filter on lastname
if (key($condition)=='lastname')
$query = $query->andWhere('p.lastname LIKE ?1')
->setParameter(1, $condition['lastname']);
#filter on gender
if (key($condition)=='gender')
$query = $query->andWhere('p.gender = '.$condition['gender']);
#filter on domain(s)
if (key($condition)=='domains')
$query = $query->andWhere('d.id IN ('.$condition['domains'].')');
#filter on town
if (key($condition)=='town')
$query = $query->andWhere('p.town LIKE ?1')
->setParameter(1, $condition['town']);
}
}
$query = $query->orderBy("p.lastname", "ASC");
$query = $qb->getQuery();
return $query->getResult();
}
There is no error, but whatever domains are given as search criteria; people from all domains are found and returned.
Domains are provided as an array like (2, 4).
What can I change to make it possible to find people belonging to certain domains?

After some trial-and-error I found two possible answers:
1) Apparently in the querybuilder you can refer to fields that live in join-tables, and are not actually part of an entity. Hence one solution would be:
#filter on domain(s)
if (key($condition)=='domains')
$query = $query->andWhere('p.domain_id IN ('.$condition['domains'].')');
...where domain_id lives in the join table "person_domain" created by Symfony2 unther the hood.
2) The inner join is now no longer needed. Using an inner join is possible as well, however it was written the wrong way and would not have worked anyway. It should have been written as:
->leftjoin('p:domain', 'd', 'ON p.id = d.id')
Hope that other people may benefit from the above.

Related

How to combine queryBuilder and raw queries on join table from many to many relation?

I have got following query
SELECT news.*, count(*) AS common_tags
FROM news
JOIN news_news_tag ON news.id = news_news_tag.news_id
WHERE news_tag_id IN
(SELECT news_tag_id FROM news_news_tag WHERE news_id = 2 )
AND news.id != 2 GROUP BY news.id ORDER BY common_tags DESC
What I want to achieve is to get hydrated news objects ordered by number of common tags with provided news id. News and Tag are many to many relation with news_news_tag join table.
News entity have got much more other relations. This is why I don't want to create a native query by myself to handle all other relations.
I would like to convert above query to use it with query builder. I wasn't able to use a DQL because my where statement uses a join (junction) table and I also need to use a join on that table.
All in all I have got 2 problems:
How can I create DQL subquery to select something from many to many join table)? If I know that I could do something like: ->where($queryBuilder->expr()->in('u.id', $mySubQueryAsDQL))
How to add that join statement that I could use news_tag_id in where statement?
If it is not possible I think that I would need to create two bidirectional one-to-many and many-to-one relations instead of many-to-many and work on special joining entity.
I finally came up with following solution. I decided to split that query into two separate ones. In first I can use just simple raw query as I need results only to where statement. Second query can be build using query builder in a normal way.
You have to only get rid of an extra count column at the end.
However if someone knows the solution how to use my first raw query directly inside query builder as a raw subquery for where statement please share with me.
public function getRelatedNews($newsId, $limit = 6)
$connection = $this->getEntityManager()->getConnection();
$sql = 'SELECT news_tag_id FROM news_news_tag WHERE news_id = :newsId';
try {
$stmt = $connection->prepare($sql);
} catch (DBALException $e) {
return [];
}
$stmt->execute(['newsId' => $newsId]);
$newsTagId = $stmt->fetchAll();
if (empty($newsTagId)) {
return [];
}
$newsTagId = array_column($newsTagId, 'news_tag_id');
$query = $this->createQueryBuilder('n')
->addSelect('COUNT(n.id) as common_tags_count')
->innerJoin("n.tags", "t")
->andWhere('t.id IN(:tagsId)')->setParameter('tagsId', array_values($newsTagId))
->andWhere('n.id != :newsId')->setParameter('newsId', $newsId)
->orderBy('common_tags_count', 'DESC')
->setMaxResults($limit)
->groupBy('n.id')
;
$results = $query->getQuery()->getResult();
$news = [];
foreach ($results as $result) {
$news[] = $result[0];
}
return $news;
}

One of listeners must count and slice given target

I'm getting the above error when performing a search in a Symfony2 CRM I've been working on. According to Google searches it seems this is an issue relating to the KNP Paginator bundle, but I cannot seem to find a solid solution.
In this instance, I am using data from an OpenCart database, and I need to be able to search by Postcode and Company name, both of which exist in the address table which is joined via a mapped value within the Customer entity, defaultaddress.
To this end, I've had to write a custom query in a function called findCustomerByPostcode like so:
$this->getEntityManager()
->createQuery('SELECT c FROM AppBundle:Oc49Customer c JOIN AppBundle:Oc49Address a WITH c.defaultaddress = a.id WHERE a.postcode LIKE :postcode')
->setParameter('postcode','%'.$postcode.'%')
->getResult();
However, when I perform a search on postcode, I get the following error in the browser:
One of listeners must count and slice given target
which refers to the Paginator.php file within the KNP bundle. I have updated to the most recent version, 2.5 yet I cannot seem to shake this error, and to me it does not even make sense.
Any help is much appreciated, as I cannot think of another way of search via a value within a joined table.
Which returns results from the postcode search, and then in the Controller:
$customers = $customer_repository->findCustomerByPostcode($filter_value);
$paginator = $this->get('knp_paginator');
$pagination = $paginator->paginate(
$customers,
$request->query->get('page', 1),
20
);
You can use the queryBuilder with the left join
public function findCustomerByPostcode($postcode) {
$query = $this->entityManager->createQueryBuilder();
$query
->select('c', 'a')
->from('AppBundle:Customer', 'c')
->leftJoin('c.address', 'a')
->setParameter('postcode', $postcode)
return $query->getQuery()->getResult();
}

doctrine query onetonmany / notIn with objects / symfony forms querybuilder

I am using symfony 2 and doctrine to prefilter a form field type 'entity' with the help of a querybuilder.
My querybuilder should return all products which the user has not already added to a list.
All relations are bidirectionnal.
I have products linked to userIngredients (oneToMany) each linked to one user (manyToOne)
I have come with this so far but it's not working, I get products not added by other users.
return $this
->createQueryBuilder('p')
->leftJoin('p.userIngredients', 'i')
->where('i.user <> ?1')
->setParameter(1,$user);
1; Any clue on how to correct this ?
Alternatively, I could select the products I don't want and then reselect those who don't match but using an expression and NotIn seems to only work for strings
$products = $this
->createQueryBuilder('p')
->leftJoin('p.userIngredients', 'i')
->where('i.user = ?1')
->setParameter(1,$user)
->getQuery()
->getResult();
return $this
->createQueryBuilder('p')
->where($this->createQueryBuilder('p')->expr()->notIn('p', $products));
2; how could we correct this to make it work with objects ?
3; alternatively : is there a way to pass not a querybuilder but an array of results to symfony form builders ?
I got thinks thanks to Javad:
(slight modification, I'm using an array result, not dql):
$qb = $this->_em->createQueryBuilder();
$ids = $qb
->select('p.id')
->from('AppBundle:MarketPlace\Product','p','p.id')
->leftJoin('p.userIngredients', 'i')
->where('i.user = ?1')
->setParameter(1,$user)
->getQuery()
->getResult();
//I don't know why I couldn't directly get an array of ids otherwise... if you know how to do better directly from the query, I'm interested (getScalarResult does not make it)
$ids=array_keys($ids);
$result = $this
->createQueryBuilder('p')
->where($this->createQueryBuilder('p')->expr()->notIn('p.id', $ids));
return $result;

Symfony2 join query

I have a table of videos and in that table I have field comment which contains id of comment in other table, now I used join query to get that in one query, but how do I get that comment?
Here is my code:
$Actions = $this->EntityManager()->getRepository('AppBundle:Video')
->createQueryBuilder('V')
->join('AppBundle:VideoComment', 'VC')
->where('V.videoId = :VideoID')
->andWhere('VC.videoId = :VideoID')
->setParameter('VideoID', $VideoID)
->getQuery()
->getResult();
How do I get the actual comment from that joined entity?
You can do what #cezar said earlier but with one little change: you have to define field to retrieve related entries from comments table.
So, your query might look like this:
$em = $this->get('doctrine.orm.entity_manager');
$videos = $em->createQuery('select v
from YourBundle:Video v
left join YourBundle:Comment c
where v.comment = c.id')
->getResult();
or you can do the similar stuff using query builder:
$videos = $em->createQueryBuilder('v')
->add('select', 'v, c')
->add('from', 'YourBundle:Video v')
->leftJoin('YourBundle:Comment', 'c')
->where('v.comment = c.id')
... // some other conditions if you need
->getQuery()
->getResult();
Both cases I described account for that Video and Comment entity might not be in formal relations (I mean their relations might not be described in your doctrine/orm file).
Here is one proposal:
<?php
namespace You\AppBundle\Repository; // You is your vendor name, AppBundle is your bundle
use Doctrine\ORM\EntityRepository;
class VideoCommentRepository extends EntityRepository
{
public function getVideoComment($VideoId)
{
$query = $this->getEntityManager()->createQuery(
'SELECT v FROM YouAppBundle:Video v LEFT JOIN v.comment c
WHERE v.id = :id'
)->setParameter('id', $VideoId);
return $query->getResult();
}
}
As you said you have a table 'video' and in that table there is a field 'comment' that contains the IDs of the comments. I suppose you have 'oneToMany' relation from 'video' to 'comment'. With this simple query you should be able to get all comments for a given VideoID. I didn't test this, but I think it should work. Try it out and adapt it as needed.

Delete function using join in DQL

I'm trying to use the Doctrine QueryBuilder to perform the Delete function using query
I need to delete a record that is present in 2 tables ,
in TcTracks table the id is "id" and in TcWall teh id is "related_id"
my controller
public function deleteAction(Request $request){
$deleteQuery = $this->getDoctrine()
->getManager()
->createQueryBuilder('d')
->delete('TcPlayerBundle:TcTracks', 'd')
->innerJoin('TcprofileBundle:TcWall', 't', 'ON', 'd.id = t.related_id')
->where('d.id = :dId')
->setParameter('wId', $request->get('related_id'))
->setParameter('dId', $request->get('id'))
->getQuery();
$deleted = $deleteQuery->getResult();
$deleted->flush();
return $this->render('TcPlayerBundle:Default:all.html.twig',array(
'tracks' => $tracks
));
}
i need to delete same record in two tables , but its not performing for both tables, kindly help me
i'm getting error as
Invalid parameter number: number of bound variables does not match number of tokens
Remove the line below:
->setParameter('wId', $request->get('related_id'))
There is only :dId but no :wId.
Actually you did inner join by 'd.id = t.related_id', so related_id is not necessary to set any more.
You don't have wID parameter anywhere in query. In the same time you're using ->setParameter('wId', $request->get('related_id')) which is obsolete in this case. Try to remove this row and it should be fine.
Is one-to-many realation with your entities
TcPlayerBundle:TcTracks
and
TcprofileBundle:TcWall
if like that,you can do some like:
oneToMany:
cascade: [remove]
in your config 'yourentity.orm.yml' file..
hope hlep you!

Resources