Symfony2/Doctrine2 innerJoin using QueryBuilder - php

I'm trying to build a innerJoin query using Doctrine2/QueryBuilder.
$repo = $this->getDoctrine()
->getRepository('MyBundle:Models');
$query = $repo->createQueryBuilder('m')
->where('m.id = :id')
->setParameter('id', $id);
Doctrine says:
A join always belongs to one part of the from clause. This is why you
have to specify the alias of the FROM part the join belongs to as the
first argument.
As a second and third argument you can then specify the name and alias
of the join-table and the fourth argument contains the ON clause.
Ex.
$queryBuilder
->innerJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id');
What I can't understand is that 'phonenumbers' table is referencing to Entity Name or DB Table Name.
What I actually want is, is there any way to explicitly refer to entity like
innerJoin('u', 'MyBundle:phonenumbers', 'p', 'u.id = p.user_id')?
It's a bit confusing when it joins just like that. Can please someone explain that to me?
Help!!

You are working on a DQL level with tables, which means that you actually joining a table, so you just need the table name, not the entity name. The table "phonenumbers might not even had an entity to begin with, this is why Doctrine requests a table name and not an entity name.
Edit
It is actually possible to work with entity names as well as follows (taken from my own code which is working as a charm):
$builder = $this->createQueryBuilder('m');
$builder->innerJoin(
'YourBundle:Category',
'c',
Join::WITH,
$builder->expr()->eq('m.id', 'c.mdl_id')
);
To use the constants from Join you should first:
use Doctrine\ORM\Query\Expr\Join;
But this should also work (taken from documentation which means should work like a charm):
$queryBuilder
->select('id', 'name')
->from('users', 'u')
->innerJoin('u', 'phonenumbers', 'p', 'u.id = p.user_id');
This is taken form: http://doctrine-orm.readthedocs.org/projects/doctrine-dbal/en/latest/reference/query-builder.html#join-clauses

In fact, you're already referencing explicitely your entity phonenumbers in your second argument of your innerJoin function.
You read the doc and i'll retake it point by point with your request :
Argument 1 : alias of the FROM part, here your m (alias of your models)
Argument 2 : The full name of your entity you want join with your models
Argument 3 : an alias to access it (just like the 'm' of models)
Argument 4 : The join condition. (like the ON part of a sql request)
But with symfony and if you have a relation between your two entities like that :
//AppBundle/Entity/Models
class Models {
/**
* ...
**/
private $id;
/**
* ...
* #ORM\ManyToOne(targetEntity="\AppBundle\Entity\Phone", inversedBy="models")
**/
private $phonenumbers;
...
}
you want to join you just can do :
$repo = $this->getDoctrine()
->getRepository('MyBundle:Models');
$query = $repo->createQueryBuilder('m')
->innerJoin('m.phonenumbers', 'p')
->where('m.id = :id')
->setParameter('id', $id);
To explain that : You just have to pass as first argument, the entity property you want to join (here the phone number of your model) and define it as alias (p for phonenumber to access it in a select)

Related

Create a group by query with inner join using Doctrine Query Builder

I struggle to create a query to get information from two tables. I want to count rows and group it by category and type.
My normal PostgresSQL query looks like this:
SELECT c.name AS category_name, i.type, count(i) AS number_of_items
FROM item i
INNER JOIN category c
ON i.category_id = c.id
GROUP BY c.name, i.type
How do I build this query using Doctrine Query Builder?
What I have tried:
$qb->select(['c.name', 'i.type', 'count(i)'])
->from('AppBundle:Item', 'i')
->innerJoin('i', 'AppBundle:Category', 'c', 'i.category_id = c.id')
->groupBy('c.name')
->groupBy('i.type');
return $qb->getQuery()->getResult();
But this give me an error:
[Semantical Error] line 0, col 80 near 'i AppBundle:Category':
Error: Class 'i' is not defined.
I'm trying to follow the principle in the documentation found here: http://doctrine-orm.readthedocs.io/projects/doctrine-dbal/en/latest/reference/query-builder.html#join-clauses
Any help would be appreciated.
Using doctrine and without defining any mapping between your related entities is not a good practice you should start from Association Mapping
Once you have defined mappings in your entities you can simply join your main entity using the properties which holds the reference of linked entities, doctrine will automatically detects the join criteria you don't need to specify in query builder, sample mapping for your entities can be defined as
class Item
{
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="items")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id")
*/
private $category;
}
-
use Doctrine\Common\Collections\ArrayCollection;
class Category
{
/**
* #ORM\OneToMany(targetEntity="Item", mappedBy="category")
*/
private $items;
public function __construct()
{
$this->items = new ArrayCollection();
}
}
And then your query builder will look like
$qb->select('c.name', 'i.type', 'count(i)'])
->from('AppBundle:Category', 'c')
->innerJoin('c.items','i')
->groupBy('c.name')
->addGroupBy('i.type');
Relationship Mapping Metadata
Or if you still don't want to have mappings and use the other approach you have to use WITH clause in doctrine
$qb->select(['c.name', 'i.type', 'count(i)'])
->from('AppBundle:Item', 'i')
->innerJoin('AppBundle:Category', 'c', 'WITH' , 'i.category_id = c.id')
->groupBy('c.name')
->addGroupBy('i.type');
you don't need to use array inside select try this:
$qb->select('c.name', 'i.type', 'count(i)')
->from('AppBundle:Item', 'i')
->innerJoin('i', 'AppBundle:Category', 'c', 'i.category_id = c.id')
->groupBy('c.name')
->groupBy('i.type');
return $qb->getQuery()->getResult();
Tried to use full expression for your entity Like this :
->from('AppBundle\Entity\Item','i')

How to JOIN without a relationship in Doctrine?

I have an entity Log (table log) with members resourceType and resourceId (with columns resource_log and resource_id). The resourceType can e.g. Order (for actions on orders, e.g. status changes ) or Whatever (for Whatever related actions). There is no relationships (neither in Doctrine, nor in the database) between the Log and the "resource" entities/tables.
Now I want to select only the Logs, that are related to Orders with myOrderProperty = "someValue". That means:
SELECT
*
FROM
`log`
JOIN
`order` ON `order`.`id` = `log`.`resource_id` AND `log`.`resource_type` = 'order'
WHERE
`order`.`my_order_property` LIKE '%my_order_property_value%'
But the code
$queryBuilder = $this->entityManager->createQueryBuilder();
$queryBuilder->select('l')->from(Log::class, 'l');
$queryBuilder->join('l.order', 'o');
...
$queryBuilder
->where('o.myOrderProperty = :myOrderProperty')
->setParameter('myOrderProperty', $myOrderProperty);
doesn't work, since the entity Log doesn't have any relationship with the Order (though it has a property order):
[Semantical Error] line 0, col 102 near 'o WHERE o.myOrderProperty': Error: Class MyNamespace\Log has no association named order
How to JOIN without a relationship defined between two entities?
I know, I could use inheritance. But semantically it's not an inheritance case. So is there another way for solving the problem?
The JOIN without relationship can be implemented like this:
$queryBuilder = $this->entityManager->createQueryBuilder();
$queryBuilder->select('l')->from(Log::class, 'l');
$queryBuilder->join(
Order::class,
'o',
\Doctrine\ORM\Query\Expr\Join::WITH,
'o.id = l.resourceId'
);
...
$queryBuilder
->where('o.myOrderProperty = :myOrderProperty')
->setParameter('myOrderProperty', $myOrderProperty);
The only problem is, that the joined entities are not added to the main entity, so that $myLog->getOrder(...) returns null.

Doctrine 2 - Joined entity is still a proxy and generating two queries after request

$news = \DB::getEntityManager()->createQueryBuilder()
->select('n')
->from('Model\News', 'n')
->join('n.category', 'c')
->join('n.locales', 'l')
->where('l.language = :lang AND n.id = :id AND n.published = 1')
->setParameter('id', $news_id)
->setParameter('lang', $lang)
->getQuery()
->getResult();
$news->category->getName();
This code is generating TWO queries instead of ONE. Why?
After getting my $news entity, the category property is still a Doctrine proxy class (Application\Proxies\__CG__\Model\News\Category). Instance of \Model\News\Category will be loaded only after getting some of the category data (with, for example, $news->category->getName()) and it's generating additional SQL query.
How to make Doctrine to download \Model\News and related to it \Model\News\Category with only one query?
Below is a configuration for category field from \Model\News with annotations:
/**
* #ManyToOne(targetEntity="\Model\News\Category", inversedBy="news", fetch="EAGER")
*/
public $category;
As You can see... EAGER is not working...
Change your first line to ->select('n, c') so that Doctrine will hydrate both your News and Category entities.

Symfony joining DQL expressions with "NOT IN"

I'm using Symfony2 and i'm trying to get an array of languages not associated to a specified client.
I have a Client entity indicating the Client, ClientLanguage that has the following structure:
id_menu_language PRIMARY KEY
language the association with the Language entity
client the association with the Client entity
sequence tells the order the language should be shown (not used here)
and Language Entity.
To get an array of languages not associated to the client i want to proceed in the following way:
Get the languages that the client has already associated (and I'm getting the correct DQL in the $clientLanguagesDQL variable)
Retrieve a list of all the available languages
Exclude from that list all the languages already associated to the client (by using NOT IN (...) ).
This is the function I wrote to accomlish that:
<?php
namespace AppBundle\Repository;
use AppBundle\Entity\Client;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query\Expr\Join;
class ClientRepository extends EntityRepository
{
/**
* #param $client Client to check
* #return array
*/
public function getLanguagesNotAssociatedToClient($client)
{
$qb = $this->getEntityManager()->createQueryBuilder();
$clientLanguagesDQL = $qb
->select('lang')
->from('AppBundle:Language', 'lang')
->join('AppBundle:ClientLanguage', 'languages_assoc', Join::WITH, 'languages_assoc.language = lang')
->join('AppBundle:Client', 'client', Join::WITH, 'languages_assoc.client = client')
->where('client.idClient = :client_id')
->getQuery()
->getDQL();
$languages = $qb->select('language')
->from('AppBundle:Language', 'language')
->where($qb->expr()->notIn('language', $clientLanguagesDQL))
->setParameter('client_id', $client->getIdClient())
->getQuery()
->getResult();
return $languages;
}
}
However, when I run this, Symfony complains about a : [Semantical Error] line 0, col 293 near 'lang INNER JOIN': Error: 'lang' is already defined. It also tells me that there is a QueryException, and it shows me the following query:
SELECT language
FROM AppBundle:Language lang
INNER JOIN AppBundle:ClientLanguage languages_assoc
WITH languages_assoc.language = lang
INNER JOIN AppBundle:Client client
WITH languages_assoc.client = client, AppBundle:Language language WHERE language NOT IN(
SELECT lang
FROM AppBundle:Language lang
INNER JOIN AppBundle:ClientLanguage languages_assoc
WITH languages_assoc.language = lang
INNER JOIN AppBundle:Client client
WITH languages_assoc.client = client
WHERE client.idClient = :client_id
)
And this is definitely not what I want to do. Why there appeared to be two joins with AppBundle:ClientLanguage and AppBundle:Client? I use this association only in my first subquery.
If it can help, when I run this:
$clientLanguagesDQL = $qb
->select('lang')
->from('AppBundle:Language', 'lang')
->join('AppBundle:ClientLanguage', 'languages_assoc', Join::WITH, 'languages_assoc.language = lang')
->join('AppBundle:Client', 'client', Join::WITH, 'languages_assoc.client = client')
->where('client.idClient = :client_id')
->getQuery()
->getDQL();
This is the returned DQL stored in $clientLanguageDQL:
SELECT lang
FROM AppBundle:Language lang
INNER JOIN AppBundle:ClientLanguage languages_assoc
WITH languages_assoc.language = lang
INNER JOIN AppBundle:Client client
WITH languages_assoc.client = client
WHERE client.idClient = :client_id
What's wrong with this query?
My mistake was very stupid, all I had to do is creating a new query builder for each query instead of reusing the first one:
$qb1 = $this->getEntityManager()->createQueryBuilder();
$clientLanguagesDQL = $qb1
->select('lang')
->from('AppBundle:Language', 'lang')
->join('AppBundle:ClientLanguage', 'languages_assoc', Join::WITH, 'languages_assoc.language = lang')
->join('AppBundle:Client', 'client', Join::WITH, 'languages_assoc.client = client')
->where('client.idClient = :client_id')
->getQuery()
->getDQL();
$qb2 = $this->getEntityManager()->createQueryBuilder();
$languages = $qb2->select('language')
->from('AppBundle:Language', 'language')
->where($qb2->expr()->notIn('language', $clientLanguagesDQL))
->setParameter('client_id', $client->getIdClient())
->getQuery()
->getResult();
return $languages;

Doctrine2 fetching rows that have manyToMany association by QueryBuilder

everyone.
I have 2 entities City and POI. Mapping looks like this:
class City {
/**
* #ORM\ManyToMany(targetEntity="POI", mappedBy="cities")
* #ORM\OrderBy({"position" = "ASC"})
*/
protected $pois;
and
class POI {
/**
* #ORM\ManyToMany(targetEntity="City", inversedBy="pois")
* #ORM\JoinTable(name="poi_cities")
*/
protected $cities;
I would like to fetch all POIs that have at least 1 association with some City using QueryBuilder. I should probably use exists() function but I don't quiet know how.
You'd have to Left join them and check if cities is null.
$qb->select('p', 'c')
->from('AcmeDemoBundle:POI', 'p')
->leftJoin('p.cities', 'c')
->where('c IS NOT NULL');
I haven't tested it, but I hope it gives you the general direction. You can read more about the QueryBuilder from here.
Docrine2 was changed in 2013, so the other solution displays error Error: Cannot add having condition on a non result variable. Now we cannot use joined alias just as a condition variable. We should use any of its properties like c.id
So you should fix the code to
$qb->select('p', 'c')
->from('AcmeDemoBundle:POI', 'p')
->leftJoin('p.cities', 'c')
->where('c.id IS NOT NULL');
$results = $qb->getQuery()->execute();
If you want to select entities that does not have any cities, use IS NULL.
$qb->leftJoin('p.cities', 'city')
->where('city.id IS NULL')
->getQuery()
->execute();
Description of a problem and link to the commit that responsible for that - http://www.doctrine-project.org/jira/browse/DDC-2780

Categories