JOIN in Symfony Doctrine with QueryBuilder between 2 entities - php

I am relatively new to Doctrine and am having an absolute nightmare trying to get a join between two entities returned correctly. I don't really understand the DRM that Doctrine uses as much as I would like at the moment so it really is just a case of trial and error at the moment.
I have two entities, one is document and one is document-sow-detail. I need to join the two where the document_id exists in the document-dow-detail table/entity as to get a couple of fields that exist in the document-dow-detail entity returned.
So, within my document repository, I have attempted the following code (with not a lot of understanding of what is happening). Can someone point me as to what is happening here and why it is not working?
$db = $this->createQueryBuilder($this->alias);
$db->addSelect("dsd");
$db->leftJoin("doc_sow_detail",'dsd','ON',"id_document");
When I breakpoint and evaluate the $db->getQuery() function in my IDE this is the _dql that has been generated.
SELECT d, dsd FROM BillingBundle\Entity\Document d LEFT JOIN doc_sow_detail dsd ON id_document
Can anyone give me a clue as to what I am doing wrong here before I chuck my computer out the window?
Thanks!

A simple example of doctrine join which joins product category on category:
$qb = $this->_em->createQueryBuilder();
$qb->select('p')
->from('BRBProductBundle:Product', 'p')
->join('p.category', 'c')
->where('1 = 1');
Hope this may help you.

I thought I'd post my solution.
I had to change my entity structures due to the one-way relationship between the two entities.
I moved the required entity properties onto the document entity which negated my need for a join. This is probably where these properties needed to be in the first place.

Related

Correct way to handle loading doctrine entities with multiple associations

I'm currently building an eCommerce site using Symfony 3 that supports multiple languages, and have realised they way I've designed the Product entity will require joining multiple other entities on using DQL/the query builder to load up things like the translations, product reviews and discounts/special offers. but this means I am going to have a block of joins that are going to be the same in multiple repositories which seems wrong as that leads to having to hunt out all these blocks if we ever need to add or change a join to load in extra product data.
For example in my CartRepository's loadCart() function I have a DQL query like this:
SELECT c,i,p,pd,pt,ps FROM
AppBundle:Cart c
join c.items i
join i.product p
left join p.productDiscount pd
join p.productTranslation pt
left join p.productSpecial ps
where c.id = :id
I will end up with something similar in the SectionRepository when I'm showing the list of products on that page, what is the correct way to deal with this? Is there some place I can centrally define the list of entities needed to be loaded for the joined entity (Product in this case) to be complete. I realise I could just use lazy loading, but that would lead to a large amount of queries being run on pages like the section page (a section showing 40 products would need to run 121 queries with the above example instead of 1 if I use a properly joined query).
One approach (this is just off the top of my head, someone may have a better approach). You could reasonably easily have a centralised querybuilder function/service that would do that. The querybuilder is very nice for programattically building queries. The key difference would be the root entity and the filtering entity.
E.g. something like this. Note of course these would not all be in the same place (they might be across a few services, repositories etc), it's just an example of an approach to consider.
public function getCartBaseQuery($cartId, $joinAlias = 'o') {
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select($joinAlias)
->from('AppBundle:Cart', 'c')
->join('c.items', $joinAlias)
->where($qb->expr()->eq('c.id', ':cartId'))
->setParameter('cartId', $cartId);
return $qb;
}
public function addProductQueryToItem($qb, $alias) {
/** #var QueryBuilder $query */
$qb
->addSelect('p, pd, pt, ps')
->join($alias.'product', 'p')
->leftJoin('p.productDiscount', 'pd')
->join('p.productTranslation', 'pt')
->join('p.productSpecial', 'ps')
;
return $qb;
}
public function loadCart($cartId) {
$qbcart = $someServiceOrRepository->getCartBaseQuery($cartId);
$qbcart = $someServiceOrRepository->addProductQueryToItem($qbcart);
return $qbcart->getQuery()->getResult();
}
Like I said, just one possible approach, but hopefully it gives you some ideas and a start at solving the issue.
Note: If you religiously use the same join alias for the entity you attach your product data to you would not even have to specify it in the calls (but I would make it configurable myself).
There is no single correct answer to your question.
But if I have to make a suggestion, I'd say to take a look at CQRS (http://martinfowler.com/bliki/CQRS.html) which basically means you have a separated read model.
To make this as simple as possibile, let's say that you build a separate "extended_product" table where all data are already joined and de-normalized. This table may be populated at regular intervals with a background task, or by a command that gets triggered each time you update a product or related entity.
When you need to read products data, you query this table instead of the original one. Of course, nothing prevents you from having many different extended table with your data arranged in a separate way.
In some way it's a concept very similar to database "views", except that:
it is faster, because you query an actual table
since you create that table via code, you are not limited to a single SQL query to process data (think filters, aggregations, and so on)
I am aware this is not exactly an "answer", but hopefully it may give you some good ideas on how to fix your problem.

Matching NOT IN with two entities using QueryBuilder

I have two entities ServeApp and App. They both have a relation to AppPage. I'm trying to get ServeApps that have AppPages which App does not have.
So ServeApps.appPages should not contain the same AppPages that Apps.appPages contains.
I'm trying to write a DQL query that would be something similar to
SELECT ServeApps WHERE ServeApps.appPages NOT IN Apps.appPages
but I'm at a loss how to go about it. I tried $queryBuilder->whereNotIn() but I guess that method doesn't exists?
Any help would be appreciated :)
EDIT:
I ended up changing my approach. Since the AppPages had a many to many relationship to both ServeApps and Apps, I did the following:
SELECT sa FROM ServeApps sa WHERE sa.id NOT IN (
SELECT ssa.id FROM AppPage ap JOIN ap.serveApps ssa WHERE ap.id = :apppage
)
I changed my logic to take out the Apps from the equation and then I checked it differently. I also had a version where I resulted to raw SQL for counting the ServeApps.
Your where-not-in statement should look like this
$queryBuilder->where($queryBuilder->expr()->notIn('alias.field', $arrayOfValues));
You can find a reference of all expressions in Doctrine's documentation.
As a side note, a where-in statement should look like this
$queryBuilder->where($queryBuilder->expr()->in('alias.field', $arrayOfValues));

Doctrine 2.1: Getting and assigning COUNT(t.id) from a subquery?

I have two entities in Doctrine 2.1: Category and Site each category has many sites and each site has a parent category.
I would like to make a single update query (in DQL) which will update a field called count of the Category entity with the number of related sites.
So in SQL I would do something like this:
UPDATE categories c SET c.count = (SELECT COUNT(s.id) FROM sites s WHERE s.category_id = c.id);
This would work beautifuly, in DQL it might something like this:
UPDATE PackageNameBundle:Category c SET c.count = (SELECT COUNT(s.id) FROM PackageNameBundle:Site s WHERE s.category = c)
Such attempt raises [Syntax Error] line 0, col 61: Error: Expected Literal, got 'SELECT'.
Subqueries DO work in DQL, but the problem here (as far as I see it) is that Doctrine cannot assign the returned value from the subquery, to the c.count. This is understandable since I might fetch more than 1 field in the subquery and even more than one row. It magicaly works in MySQL since it sees one row, one field and for convenience returns a single integer value. Doctrine on the other hand has to be object oriented and has to work with different engines where such convertions might not be supported.
Finally, my question is:
What is the best way to do this in Doctrine, should I go with Native SQL or it can be done with DQL and how?
Thanks in advance!
EDIT: I just found this quote in the DQL Docs:
References to related entities are only possible in the WHERE clause and using sub-selects.
So, I guess assigning anything but a scalar value is impossible?
The main question remains though..
You can use native sql queries in Doctrine also, for that kind of specific queries. DQL is powerful in its own way, but it's also limited due to performance constraints. Using native sql queries and mapping the results will achieve the same thing, and there is no disadvantage in doing that.
The documentation explains it in detail.

Doctrine DQL, class table inheritance and access to subclass fields

I have a problem with a DQL query and entity specialization.
I have an Entity called Auction, which is OneToOne relation with Item. Item is a mappedSuperclass for Film and Book. I need a query that could back a search engine, allowing the user to look for auctions with different properties AND selling items with different properties (it is the AND part that makes it challenging).
The problem is that even though Auction has an association pointing to Item as such, I need to have access to Film- and Book-specific fields. The users will specify the Item type they're looking for, but I don't see any way of using this information other than using INSTANCE OF in my DQL query.
So far, I have tried using a query like:
SELECT a FROM Entities\Auction a
INNER JOIN a.item i
INNER JOIN i.bookTypes b
WHERE i INSTANCE OF Entities\Book
AND b.type = 'Fantasy'
AND ...".
Such a query results in an error saying that:
Class Entities\Item has no field or association named bookTypes
which is false for Book, yet true for Item.
I have also tried
SELECT a FROM Entities\Book i
INNER JOIN i.auction a ...
but I reckon Doctrine requires that I refer to the same Entity in SELECT and FROM statements.
If that's of importance, I am using class table inheritance. Still, I don't think switching to single table inheritance would do the trick.
Any ideas?
As Matt stated, this is an old issue that Doctrine Project won't fix (DDC-16).
The problem is that doctrine's DQL is a statically typed language that comes with a certain amount of complexity in its internals.
We thought about allowing upcasting a couple of times, but the effort to get that working is simply not worth it, and people would simply abuse the syntax doing very dangerous things.
As stated on DDC-16, it is also indeed not possible to understand which class the property belongs to without incurring in nasty problems such as multiple subclasses defining same properties with different column names.
If you want to filter data in subclasses in a CTI or JTI, you may use the technique that I've described at https://stackoverflow.com/a/14854067/347063 . That couples your DQL with all involved subclasses.
The DQL you would need in your case is most probably (assuming that Entities\Book is a subclass of Entities\Item):
SELECT
a
FROM
Entities\Auction a
INNER JOIN
a.item i
INNER JOIN
i.bookTypes b
WHERE
i.id IN (
SELECT
b.id
FROM
Entities\Book b
WHERE
b.type = 'Fantasy'
)
That is the pseudo-code for your problem. It is not nice, but keep in mind that SQL and DQL are very different and follow different rules.
Updated:
I've discovered a solution for this. See my answer for this related question:
Doctrine2: Polymorphic Queries: Searching on properties of subclasses
You can easily solve this by left-joining your base entity with your inheritance class using the id:
SELECT a FROM Entities\Auction a
INNER JOIN a.item i
INNER JOIN Entities\Book b WITH b.id = i.id
INNER JOIN b.bookTypes bt
WHERE bt.type = 'Fantasy'
AND...
or with a queryBuilder:
$queryBuilderb->select('a')
->from('Entities\Auction', 'a')
->innerJoin('a.item', 'i')
->innerJoin('Entities\Book', 'b', 'WITH', 'b.id = i.id')
->innerJoin('b.bookTypes', 'bt')
->where('bt.type = :type')
->andWhere(...
->setParameter('type', 'Fantasy');
This is based on the answer given by Ian Philips in the question here
The Doctrine team has stated that they're not going to add support for this:
https://github.com/doctrine/orm/issues/2237
Pertinent comments from that page:
Thats indeed tricky. That syntax alone can, however, never work,
because there might be several subclasses that have a field named "d",
so Doctrine would not know which field you mean.
I am closing this one.
The requirement of this issue is basically violating OO principles.
If you really need to filter across multiple child-entities in your
inheritance, then try something as following instead:
SELECT
r FROM
Root r WHERE
r.id IN (
SELECT
c.id
FROM
Child c
WHERE
c.field = :value
)
I had the same issue, and didn't find a solution without using separate queries for each subclass and merging them later on the application level.
One thing I'm sure, single table inheritance will not solve this, completely the same thing.
There is another alternative, although being logically dirty.
Define all the fields (the ones you need) in the superclass. If the record logically doesn't have that field it will be empty. Not a pretty sight, but hey, more optimized than 2-3-4-... queries. Also in this scenario single table inheritance is definitely the better way to go

Doctrine2: Polymorphic Queries: Searching on properties of subclasses

I've got a project where I deal with customer orders. Some of those orders are made via Amazon.com. So I've got an Order entity, and an AmazonOrder entity that extends it. One thing added by AmazonOrder is the AmazonOrderId.
I've a requirement to implement a broad search feature. The user can enter some stuff into a text box, and be used in a bunch of expressions in one big where-clause. So, for example, if the user searched for "111", the results include any orders with an ID starting with 111, any order being shipped to zip codes that begin with 111, any order being shipped to "111 Main St", etc.
That stuff is implemented with a query-builder-created query that has a big orX() expression.
Now, I'd like to match against all Orders, but if they're an AmazonOrder, also match against AmazonOrderId.
And I'm stuck -- I suspect it may not be possible
Here's how I'm building up the query:
$qb->select('o,s')->from('PMS\Entity\Order', 'o');
$qb->leftJoin('o.shippingInfo','s');
$qb->andWhere('o.status = :status');
$qb->setParameter('status',$status);
$qb->andWhere(
$qb->expr()->orX(
$qb->expr()->like('o.id',':query')
$qb->expr()->like('s.address',':query')
$qb->expr()->like('s.city',':query')
)
);
$qb->setParameter('query',$userQuery .'%');
$orders = $qb->getQuery()->getResult();
And I can't figure out how to add a condition that says, roughly, "OR (Order is an AmazonOrder AND AmazonOrderId LIKE '$userQuery%')"
Anyone have any insight? Either a way to handle this, or at least a confirmation that it's not doable this way?
Here's another solution that works for me with Doctrine 2.4:
$qb->select('o')
->from('Order', 'o')
->leftJoin('AmazonOrder', 'ao', 'WITH', 'o.id = ao.id')
->andWhere('o.id like :query or ao.amazonOrderId like :query')
->setParameter('query', $someQuery);
You just left-join the entity on the specific subclass of itself. (You can adapt my simple query to your use case.)
I've tried this exactly once, but it seems to work.
Hm, I had similiar problems in my last doctrine project.
One time it was just a single field, so I moved it to the parent class – not the nicest solution, but worked. In some other case there where too many properties so these would have cluttered the parent class. I did a native sql query for searching and fetching me the record ids and then used a WHERE IN (...) dql in order to fetch the entities.
A compromise might be the doctrine ResultSetMapping which can map a native sql query to entities directly, although every time I worked with it I found it quite clumsy to use and the overhead for two queries (fetch ids & fetch entites) as outlined above to be neglectable.
Maybe you could accomplish something with the INSTANCEOF operator in your WHERE clause, although I dont think doctrine would be smart enough to recognize it the way you want.
If you need to join a subclass to get a fields only present into this subclass, you may want to use this syntax :
...->from('From', 'X')->leftJoin('App\Entity\Class\Name', 'Y', \Doctrine\ORM\Query\Expr\Join::WITH, 'Y.id = X.id')
Note that, as it's left join, it comes with a performance hit.
Also note that doctrine will automaticaly join all child entity of a mapped superclass no matter what you're doing.
There's a github issue about this behaviour : https://github.com/doctrine/orm/issues/5980
I would like to add that if you don't need to access any fields of the subclasses, you can just join the superclass and filter with a where INSTANCE OF subclass.
I suggest you adding the discriminator columns and the superclass id into an index as :
* #ORM\Table(indexes={#ORM\Index(name="idx_partition_type", columns={"id", "type"})})
* ...
* #ORM\DiscriminatorColumn(name="type", columnDefinition="CHAR(1) NOT NULL")
abstract class superclass{
...
}
This would be usefull only for non-instantiable, abstract class. Else, if your class isn't abstract, doctrine will leftJoin any related child entity no matter what happens.
I just wanna share all of this because it helped me.

Categories