Symfony2 - Doctrine DQL - many to many query - symfony

I have entities Offer and Area and here one Offer can have many Areas and one Area belong to many Offers:
Offer entity:
/**
* #ORM\ManyToMany(targetEntity="Area")
* #ORM\JoinTable(name="relationship_offer_areas",
* joinColumns={#ORM\JoinColumn(name="offer_id", referencedColumnName="id", onDelete="CASCADE")},
* inverseJoinColumns={#ORM\JoinColumn(name="area_id", referencedColumnName="id")}
* )
*/
private $areas;
Now I am trying to get Offers by Area using DQL (not query builder!):
$query = 'SELECT o FROM IndexBundle:Offer o '.
'LEFT JOIN IndexBundle:Area a '.
'WHERE a = :area '.
'ORDER BY o.startDate ASC';
Here :area is Area entity object. Unfortunately it is not working as expected. I get all offer rows with all the areas.
Any ideas what am I missing? Working with Entities in query language really twist my mind. Thank you!

$query = 'SELECT o FROM IndexBundle:Offer o '.
'LEFT JOIN o.areas a '.
'WHERE a.id = :areaId '.
'ORDER BY o.startDate ASC';
It doesn't know how to use the JOIN on the fly. Use the properties of your entities for joining. It's is just like you do a LEFT JOIN within SQL as you refer to a column where you want additional data from. As you specify in the ON of the join.
Left join using DQL - See documentation: Doctrine DQL - Select examples
$query = $em->createQuery('SELECT u.id, a.id as article_id FROM CmsUser u LEFT JOIN u.articles a');
$results = $query->getResult(); // array of user ids and every article_id for each user

Related

Doctrine left join on unrelated entity

Is there any way to do this. I'm getting more and more confused trying different things.
I have an entity conferences that can have a place.
Places are in a many to one relationship with city.
In my query I'm trying to retrieve the city info but can't seem to retrieve it in the same place result.
This is the query used:
$qbt = $this->_em->createQueryBuilder();
$qbt
->select('conference', 'diffusion', 'speaker', 'placediff', 'confcity')
->from('AppBundle:Conference', 'conference')
->leftJoin('conference.diffusion', 'diffusion')
->leftJoin('conference.speaker','speaker')
->leftJoin('conference.place','placediff')
->leftJoin('AppBundle:City', 'confcity', 'WITH', 'confcity.id = placediff.city');
return $qbt
->getQuery()
->setHint(\Doctrine\ORM\Query::HINT_INCLUDE_META_COLUMNS, true)
->useQueryCache(true)
->useResultCache(true, 3600)
->getArrayResult();
This is what is returns, currently have only one conference.
Would love to have the second array inside place though.
Any ideas on how to accomplish this? Much obliged ~
(UPDATE) In the meantime I switched to raw sql query but really looking for a way to do this using dql
public function rawConf()
{
$conn = $this->getEntityManager()->getConnection();
$sql = 'SELECT
c0_.id AS id,
c0_.startAt AS startat,
c0_.comment AS comment,
d1_.id AS diffusion_id,
d1_.hour AS diffusion_hour,
s2_.id AS speaker_id,
c0_.place_id AS place_id,
c0_.sponsor_id AS sponsor_id,
c0_.tour_id AS tour_id_8,
d1_.movie_id AS diffusion_movie_id,
s2_.contact_id AS speaker_contact_id,
c6_.name AS ville_name,
c6_.postal AS ville_post,
c6_.department as ville_depart
FROM
conference c0_
LEFT JOIN conference_diffusion c3_ ON c0_.id = c3_.conference_id
LEFT JOIN diffusion d1_ ON d1_.id = c3_.diffusion_id
LEFT JOIN conference_speaker c4_ ON c0_.id = c4_.conference_id
LEFT JOIN speaker s2_ ON s2_.id = c4_.speaker_id
LEFT JOIN place p5_ ON c0_.place_id = p5_.id
LEFT JOIN city c6_ ON (c6_.id = p5_.city_id)';
$stmt = $conn->prepare($sql);
$stmt->execute();
return $stmt->fetchAll();
}
You should get "city" from "place" as "$city" is a property of Place entity. Try this simplified query to see it works:
$qbt = $this->_em->createQueryBuilder();
$qbt
->select('conference')
->addSelect('place')
->addSelect('city')
->from('AppBundle:Conference', 'conference')
->innerJoin('conference.place', 'place')
->innerJoin('place.city', 'city')
;
And if it works - you could easily add more joins if you need.

Doctrine: ordering By Count on Associated entities

Goal: Ordering my entities on how ofter they are used on associated relations (oneToMany)
The problem: DQL, it somehow handles the FROM keyword is an entity or class.
The Exception thrown: QueryException: [Semantical Error] line 0 col 103 near 'FROM MyBundle:Entity Error: Class 'FROM' is not defined.
Here is a SQL query I wrote to test how to get the data. It works perfectly
SELECT en.id, (COUNT(DISTINCT ag.artist_id) + COUNT(DISTINCT rg.release_id) + COUNT(DISTINCT tg.track_id)) AS total
FROM myapp.entity AS en
LEFT JOIN myapp.other_one AS o1 ON o1.entity_id = en.id
LEFT JOIN myapp.other_two AS o2 ON o2.entity_id = en.id
GROUP BY en.id
ORDER BY total DESC ;
To get the data in symfony to hydrate to objects I try to use Doctrine Query Language in the EntityRepository like this:
/**
* Find Most Common Entities
*
* #return array
*/
public function findMostCommon()
{
$em = $this->getEntityManager();
$qb = $em->createQueryBuilder();
$qb
->select('en, (COUNT(DISTINCT en.other1) + COUNT(DISTINCT en.other2)) AS count')
->from('MarulaBundle:Entity', 'en')
->leftJoin('MyBundle:Other1', 'o1', 'WITH', 'o1.entity = en.id')
->leftJoin('MyBundle:Other2', 'o2', 'WITH', 'o2.entity = en.id')
->groupBy('en')
->orderBy('count', 'DESC')
;
return $qb->getQuery()->getResult();
}
Since it is possible in a SQL query. I was hoping it would work just as fine with DQL.
Has anyone experienced this error before? Is it even possible to achieve this with Doctrine, or is doctrine limited relating this issue?
OOPS: I should not use the keyword "count"
Solution:
->select('en, (COUNT(DISTINCT en.other1) + COUNT(DISTINCT en.other2)) AS HIDDEN orderCount')
note: adding the HIDDEN keyword helps to get only the entities back, which makes the hydration fit right in

How to count Users by Pathology using DQL

I have an issue to find an alternative way to count Users who suffer a pathology (a count with a group by)
the issue is I prefer not to make a for loop on pathologies to count that, and I'de like if I could get your advice.
here's some pieces of code I have :
User Entity :
/**
* #ORM\OneToMany(targetEntity="Hospitalisation", mappedBy="user")
*/
private $hospitalisations;
Hospitalisation Entity:
/**
* #ORM\ManyToOne(targetEntity="Pathologie", inversedBy="hospitalisations")
*/
private $pathologie;
Pathology Entity:
/**
* #ORM\OneToMany(targetEntity="Hospitalisation", mappedBy="pathologie")
*/
private $hospitalisations;
What I have to do is to count how many users suffers of each pathologie in the database.
Thank you :)
Create a custom repository class PathologieRepository
http://symfony.com/doc/current/book/doctrine.html#custom-repository-classes
Add this (not tested):
public function countUsers($pathologieID)
{
$result = $this->getEntityManager()
->createQuery("
SELECT COUNT(u) as c FROM YourMainBundle:Users u
JOIN u.hospitalisations h
JOIN h.pathologies p
WHERE
p.id = :pathologie
")
->setParameter('pathologie', $pathologieID)
->setMaxResults(1)
->getSingleResult();
return $result['c'];
}
If you want do this in twig like (pseudo):
for pathologie as p
print p.countUsers(p.id)
You have to create a method in your pathologie entity class. But you cannot use entity manager in entity classes (you can by using a hack but this is not recommended). So you have to use loops to count the users.
public function getUserCount()
{
$hospitalisations = $this->getHospitalisations();
$counter = 0;
foreach ($hospitalisations as $h)
{
$counter += count($h->getUsers());
}
return $counter;
}
I solved the issue.
Here's the solution i've come up with:
$result = $this->getEntityManager()
->createQuery("
SELECT p.id, p.nom, COUNT(DISTINCT u) as nbUsers , COUNT(DISTINCT i) as nbImported
FROM AcmeDemoBundlePathologie p
left JOIN p.importedUsers i
left JOIN p.hospitalisations h
left JOIN h.user u
GROUP BY p.id, p.nom
")
->getArrayResult();

Symfony2: Doctrine subquery in the WHERE clause including a LIMIT

I'm trying to convert this SQL into either DQL or whatever the query builder variant would look like.
select *
from project_release r
where (select s.title as status_name
from release_status_log l
left join release_status s
on l.release_status_id = s.id
where l.release_id = r.id
order by l.created_at desc
limit 1
) not in ('Complete', 'Closed')
;
From inside the repository class for the Release entity, I've tried this
return $this->getEntityManager()->createQuery("
select r.*
from MyBundle:Release r
where (select s.title
from MyBundle:ReleaseStatusLog l
join l.status s
where l.release = r
order by l.createdAt desc
limit 1
) IN ('Complete','Closed')
order by r.release_date ASC
limit 10
")->getArrayResult();
Which gives the error
[Syntax Error] line 0, col 265: Error: Expected
Doctrine\ORM\Query\Lexer::T_CLOSE_PARENTHESIS, got 'limit'
Which is referring to the limit 1 in the subquery.
So then I tried this
return $this
->createQueryBuilder('r')
->select('r.*')
->where("(select s.title
from MyBundle:ReleaseStatusLog l
join l.status s
where l.release = r
order by l.created_at desc
limit 1
) $inClause ('Complete', 'Closed')
")
->setMaxResults( $limit )
->orderBy('release_date', 'ASC')
->getQuery()
->getArrayResult()
;
Which gives the same error. How can I execute a subquery limited to 1 row per row in the parent query?
Symfony 2.0.15
Doctrine 2.1.7
PHP 5.3.3
MySQL 5.1.52
I have a solution for this now. I ended up falling back on the native query system with the result set mapping from the entity in questions.
It's not a great solution, but it works and until I see another solution, it's the only option with this type of WHERE clause.
Here's what my finder method looks like now
/**
* Finds Releases by their current status
*
* #param array $statuses White-list of status names
* #param boolean $blackList Treat $statuses as a black-list
* #param integer $limit Limit the number of results returned
* #param string $order Sort order, ASC or DESC
*
* #throws \InvalidArgumentException
*
* #return array <Release>
*/
public function findByCurrentStatus( array $statuses, $blackList=false, $limit=null, $order='ASC' )
{
if ( empty( $statuses ) )
{
throw new \InvalidArgumentException( "Must provide at least one status" );
}
$inClause = $blackList ? 'not in' : 'in';
$rsm = new ResultSetMappingBuilder($this->getEntityManager());
$rsm->addRootEntityFromClassMetadata('MyBundle:Release', 'r');
$SQL = "
select *
from project_release r
where (select s.title as status_name
from release_status_log l
left join release_status s
on l.release_status_id = s.id
where l.release_id = r.id
order by l.created_at desc
limit 1
) $inClause ('" . implode( "','", $statuses ) . "')
order by r.release_date $order
";
if ( $limit )
{
$SQL .= " limit $limit";
}
return $this
->getEntityManager()
->createNativeQuery( $SQL, $rsm )
->getResult()
;
}
I kind of loathed going back to building a query as a string, but oh well. Oh, and for you eagle-eyes, $statuses does not come from user data, so no SQL injection vulnerabilities here ;)
In addition to your Native SQL solution, you can create two queries using DQL within a single repository method.
Some adjustment may be required but you could try this:
public function findCompletedReleases()
{
$em = $this->getEntityManager();
$dqlSubQuery = <<<SQL
SELECT
s.title status_name
FROM
Acme\MyBundle\Entity\ReleaseStatus s,
Acme\MyBundle\Entity\ReleaseStatusLog l,
Acme\MyBundle\Entity\Release r
WHERE
l.release = r.id AND
l.status = s.id
ORDER BY l.createdAt DESC
SQL;
$statusName = $em->createQuery($dqlSubQuery)
->setMaxResults(1)
->getSingleScalarResult();
$dql = <<<SQL
SELECT
r
FROM
Acme\MyBundle\Entity\Release r
WHERE
:status_name IN ('Complete','Closed')
ORDER BY r.release_date ASC
SQL;
$q = $em->createQuery($dql)
->setParameters(array('status_name' => $statusName))
->setMaxResults(10);
return $q->getArrayResult();
}

update doctrine with join table

I am trying to update the field "note" in Note Entity which have a relation ManyToOne (bidirectionnel) with "ElementModule" and "Inscription", the entity "Inscription" have a relation ManyToOne with the "Etudiant" Entity
I tried this DQL Query:
$query = $this->_em->createQuery('update UaePortailBundle:Note u JOIN u.inscription i JOIN u.elementmodule e join i.etudiant et set u.note = ?3
where et.id = ?1 and e.id = ?2 ');
$query->setParameter(1, $etudiant);
$query->setParameter(2, $element);
$query->setParameter(3, $note);
$resultat = $query->execute();
i get this error
[Syntax Error] line 0, col 50: Error: Expected Doctrine\ORM\Query\Lexer::T_EQUALS, got 'i'
LEFT JOIN, or JOINs in particular are only supported in UPDATE statements of MySQL. DQL abstracts a subset of common ansi sql, so this is not possible. Try with a subselect or IDENTITY ( you must use the latest version of Doctrine 2.2.2 ) :
createQuery('update UaePortailBundle:Note u set u.note = ?3
where IDNETITY(u.inscription) = ?1 and IDENTITY(u.elementmodule) = ?2 ');
after searching and trying to find a solution for that using doctrine dql, i was not able to find it, so i used direct sql query to update the database.
$stmt = $this->getEntityManager()
->getConnection()
->prepare("update note set note = :note where `etudiant_id` like :etudiant_id and `elementmodule_id` like :elementmodule_id");
$stmt->bindValue('etudiant_id',$etudiant);
$stmt->bindValue('elementmodule_id' ,$element );
$stmt->bindValue('note', $note);
$stmt->execute();

Resources