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
Related
Two entities, A and B, Bextends A. I'm interested in the field B.isOk.
I have this query that joins A with other entities(and because B extends A, if I look at the pure sql, a join is done between A and B even though it is not specified in the query builder-because of the inheritance, great-).
How can I then access that B.isOk if I only have A my query builder? I tried using INSTANCE OF but to no success.
In my yml for A there's specified :
inheritanceType: joined
discriminatorColumn:
name: type
type: text
discriminatorMap:
internal-b: \C\BBundle\Entity\B
Any other suggestions on how could I use that property? I could add an extra join but what's the point since the join I need is already happening behind the curtains.
One alternative to this would be to give up DQL and write my own SQL query.
since this can not be done because OOP principles, it means something might be wrong with my model so another solution would be to refactor the model and design a correct one that fits the current needs.
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.
Using Doctrine's QueryBuilder, how can you specify a where condition on one of the attributes of a derived type when using single table inheritance.
Say I have a type AbstractBillingEntity which has a derived type ComplexBiller. ComplexBiller itself has a protected Doctrine controlled attribute called organisationCode.
How can I use the QueryBuilder to search for all billing entities that are of type ComplexBiller and have ComplexBiller.organisationCode > 5?
$queryBuilder->andWhere("billingEntity INSTANCE OF ComplexBiller")
$queryBuilder->andWhere(??)
Using Doctrine 2.4, PHP 5.6
It turns out this is an old problem that the Doctrine team currently won't fix (DCC-16)
Apparently the internals of the Doctrine DQL language make it an exhaustive effort to try and implement a form of casting. While it may be considered in the future, the workout was to perform a sub-query with explicit requirements on the child class:
$subExpr = $this->em->createQueryBuilder()
->select("anEntity")
->from("ChildClass", "anEntity")
->where("orgEntity.derivedTypeAttr = :someParam");
$mainExpr = $this->em->createQueryBuilder()
->where($queryBuilder->expr()->in("billingEntity.id", $subExpr->getDQL()))
->setParameter("someParam", $myVal);
Or, in native SQL (taken from the very useful source here)
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'
)
Say I had the following query that returns a User entity with the associated Post entities that have been flagged (this is just for demonstration):
SELECT u, p
FROM User u
LEFT JOIN u.posts p
WHERE p.isFlagged = true
Once I've used this, I then want to access all the posts for that user, regardless of isFlagged. Is there a simple way to refresh the $user->posts collection so that it is then a complete collection of all the users posts?
I don't want to just pull out all of the posts in the query (as they may not be needed), and the code that needs the complete collection isn't going to be aware of the query this entity came from.
What you are doing here is broken:
SELECT
u, p
FROM
User u
JOIN
u.posts p
WITH
p.isFlagged = true
This will hydrate `User#posts" collection with wrong values, which results in broken logic, broken object graph, broken everything.
You should solve this at DQL level with something like following:
SELECT
u, p
FROM
User u
JOIN
u.posts p
JOIN
u.posts j
WITH
j.isFlagged = true
This will basically hydrate the correct posts collection in the user object, without any intermediate (broken) state.
EDIT: I misunderstood the question because I've based my thoughts on #Athlan's answer, which is fundamentally wrong (flushing a broken collection is really wrong). This is my previous answer, which I still believe is interesting because it solves the problem, but not in a really correct way.
This question actually made me curious to see if the ORM refreshed collections as I expected it to do.
I wrote the test in this branch.
Basically, what you need to do is simply:
$entityManager->refresh($entity);
Here's the relevant part of the test:
$foo = new DDC2666Foo();
$this->_em->persist($foo);
$this->_em->flush();
$this->_em->clear();
$fetchedFoo = $this->_em->find(__NAMESPACE__ . '\DDC2666Foo', $foo->id);
$fetchedFoo->bars->add(new DDC2666Bar());
$this->assertCount(1, $fetchedFoo->bars);
$this->_em->refresh($fetchedFoo);
$this->assertCount(0, $fetchedFoo->bars, 'The collection was reset');
There's just the disadvantage that this will also re-fetch your entity, but the ORM itself does not provide a facade to refresh single collections.
That is also a good thing anyway, since this way, you won't break encapsulation, which may lead to unexpected (and hard to debug) behaviour in your code.
I had this same issue and ended up here. Refreshing isn't an option for me because I have a list and I don't want to make those extra queries.
What you really need here is to add a "fake" join to do the filtering, and leave the original one without a condition... to make this clear:
SELECT u, p
FROM User u
LEFT JOIN u.posts p
LEFT JOIN u.posts p2
WHERE p2.isFlagged = true
Note that I'm leaving p2 out from the select clause, it's just for filtering purposes.
I find the other solutions very non-satisfying because many entities will get hydrated that I'm not at all interested in. If the vast majority of the posts have isFlagged = 0 then the performance impact is huge.
Here's an alternate solution which involves swapping around the relationship between the entities. However it is not entirely equivalent to what the original question asked for. Yet it might be useful to others having this issue:
SELECT p, u
FROM Post p
JOIN p.user u
WHERE p.isFlagged = true
The differences between this and the other solutions is that it will no longer return users that do not have any posts or any flagged posts. This may or may not be what you want.
I have three tables A, B, C. A and B have a one-to-many relationship. B and C have another one -to-many relationship. In another word, each A can have multiple Bs, while each B can have multiple Cs.
Now I want to do a query for a given record of A, to get all the related Bs which have related Cs. In another word, for a given a, which is a record in table A, I want to get all of the related Bs from table B, providing that each of the Bs also has more than zero related Cs in table C.
How to write the statement in PHP doctrine? I have some code which doesn't work:
Doctrine_Query::create()->from('B b')->leftJoin('C c')
->andWhere('b.A_id = ?', a.id)->andWhere('c.b_id = b.id');
Why don't you just use an innerJoin ?
With Foo, Bar and Baz (respectively A, B and C) :
Doctrine_Query::create()
->from('Bar bar')
->where('bar.foo_id = ?', $foo->id)
->innerJoin('bar.Baz baz');
This way you'll only get Bar which belongs to Foo, and which have one or many Baz.
From my workings with Doctrine, this is not possible (Doctrine 1 is what I am speaking to).
The work around, which I know sucks, is to do multiple queries. IE take all of the B id's and use them in the whereIN clause and pull them up in a separate query. If someone else has a better method I would be interested in it :)
As premiso noted you need to write at least 2 queries. I would also do it the way he suggested (take all of B id's and use an IN).
To make it more Doctrine-line, look at DQL Subqueries. They already show an example that uses IN in connection with selecting ID`s.
edit: Reading DuoSRX's answer, I think you may have meant what he shows with Inner Joins but not quite sure if I understood the question correct.