When I execute something like this in Doctrine:
$qb = $doctrine
->getRepository('EntityA')
->createQueryBuilder('a')
->addSelect('b')
->join('EntityB', 'b', 'WITH', 'a.b = b')
->getQuery()
->getResult()
I get an array that looks like this:
array(0 => EntityA,
1 => EntityB,
2 => EntityA,
4 => EntityB)
In fact, I get 2 result rows, but an array which has a size of 4. This makes iterating over it for displaying in templates nearly impossible.
I would like a result like this:
array(0 => array(EntityA, EntityB),
1 => array(EntityA, EntityB))
Of course I could create a mapping on EntityA that references EntityB. But even with the possibility to change the loading behavior, LAZY, EAGER etc., it could be desirable to have the possibility to create such relations on the fly.
For example:
In overviews with large amounts of Entities, EAGER loading is needed to prevent excessive amounts of queries. But when I want to display only one Entity and do not need the extra data, LAZY loading is more desirable.
Since you have EntityB mapped as EntityA.b, as indicated by this relation:
...join('EntityB', 'b', 'WITH', 'a.b = b')...
Thus you do not need in fact to add b to the select as you are loading it either eagerly or lazily.
Remove ->addSelect('b') from your query builder and use EntityB trough your EntityA.b mapping.
Example:
$as = $doctrine
->getRepository('EntityA')
->createQueryBuilder('a')
->join('EntityB', 'b', 'WITH', 'a.b = b')
->getQuery()
->getResult();
foreach($as as $a){
echo "EntityA property".$a->id;
echo "EntityB property".$a->b->id;
}
This query will return an ArrayCollection of EntityA.
Note: The example above assumes that EntityA.b is a public property.
Update:
After some digging I found this older post: Doctrine 2 QueryBuilder add multiple select elements /parameters?. According to it you can get the result you wan by directly separarating the entities with commas instead of using the ->addSelect() method.
Try the following DQL:
$query = $em->createQuery("SELECT a, b FROM EntityA a JOIN a.b b")
The query above will dynamically eagerly fetch b.
Related
I have the following code in an entity repository:
$rows = $this->createQueryBuilder('t')
->select(['t.idColumn', 't.valueColumn'])
->where('t.foo = :foo')
->orderBy('t.idColumn', 'ASC')
->setParameter('foo', $foo)
->getQuery()
->getArrayResult(); // This returns [[idColumn => ..., valueColumn => ...], ...]
$data = [];
foreach ($rows as $row) {
$data[$row['idColumn']] = abs($row['valueColumn']); // Remapping to [id => value]
}
return $data;
Is there any way to get rid of the custom remapping natively? I know that you can use the indexBy parameter, but that only gets me the correct keys but not values.
P.S. I know of array_column(), but that's an extra step that I have to make every time, not to mention it doesn't work on methods that entities have.
P.P.S. This is not using Symfony.
It does not appear this is a feature implemented in the QueryBuilder, however fetchAllKeyValue was added in DBAL 2.11 to the Connection object.
Commit, usage
I have a similar problem and DB structure:
Doctrine2 association mapping with conditions
But, I need collections of Article with approved comments:
How to do assotiation mapping, that do without N+1 ?
$articles = $repository->findAllArticlesWithApprovedComments();
Foreach($articles as $article){
$article->getTitle();
foreach($article->getAprovedComments() as $comment){
$comment->getText();
}
}
Criteria worked, if i use lazy load, but its N+1 problem. If I use Eager load (join and addSelect ) - Criteria not work.
If I use this code:
$articles = $em->createQueryBuilder()
->from(Article::class,'article')
->leftJoin('article.comments','comments')
->addSelect('article')
->addSelect('comments')
->andWhere('comments.approved= :ap ')
->setParameter('ap',true)
->getQuery()->getResult();
I will receive articles with approved comments, but if the article has 0 comments, it will not fall into the collection of articles.
How to get articles with approved comments, but if there are no comments in the article, the article remains in the collection?
Example:
I have in DB:
Article1: [approvedComment, nonAprovedComment]
Article2: [nonAprovedComment]
Article3: [approvedComment]
I need result (with doctrine, non filter in code):
Article1: [approvedComment]
Article2: []
Article3: [approvedComment]
You can use a join instead of a where condition to filter your collection on the database level.
Your query would then look like this:
$articles = $em->createQueryBuilder()
->from(Article::class, 'article')
->leftJoin('article.comments', 'comments', 'WITH', 'comments.approved = :ap')
->addSelect('article')
->addSelect('comments')
->setParameter('ap', true)
->getQuery()->getResult();
Since this is a left join, it will return all articles, even if they do not have any approved comment.
I am new in Symfony, and I have a problem with an existing application I maintain.
In one of the repositories, there's is a method, that selecting the failed transactions, and the related payment.
Now, they have asked me, to allow filter the transactions, based on the total amount of failed transactions which could be either 1 failed transaction or 2.
What I am trying to do in the query builder, is something like that:
$this
->createQueryBuilder('t')
->join('t.payment', 'p')
->leftJoin( Transaction::class, 'tr', Query\Exprt\Join::WITH, 'p.id = tr.payment')
->groupBy('tr.id');
Until that point everything is fine. The query is executed normally, and I can see the transactions I need.
Now the problem is that I cannot use the following statement:
$this
->createQueryBuilder('t')
// This is the column I need to insert
->addSelect('COUNT(tr.id) AS TotalRecords')
->join('t.payment', 'p')
->leftJoin( Transaction::class, 'tr', Query\Exprt\Join::WITH, 'p.id = tr.payment')
->groupBy('tr.id');
Because the output looks like that:
array:1 [▼
0 => array:2 [▼
0 => Transaction {#1221 ▶}
"TotalRecords" => "1" <- This is the total transactions number I need
]
]
Instead of the output above, I need to have the TotalRecords inside the Transaction Object.
So, Is there a way to achieve that with the query builder? Do you think I do something wrong?
you can just loop over your result set and set TotalRecords on all Transaction objects... and return an array of Transactions, as you probably have hoped. The overhead is minimal but the standard doctrine hydration isn't smart enough
// the following is your query:
$qb = $this
->createQueryBuilder('t')
->addSelect('COUNT(tr.id) AS TotalRecords')
->join('t.payment', 'p')
->leftJoin( Transaction::class, 'tr', Query\Exprt\Join::WITH, 'p.id = tr.payment')
->groupBy('tr.id');
// fetch the results, and instead of straight returning them, "merge"
$results = $qb->getQuery()->getResult();
$return = [];
foreach($result as $row) {
$row[0]->totalCount = $row['TotalCount'];
$return[] = $row[0];
}
return $return; // <-- now an array of Transaction
you also could just not use addSelect but instead having and just use the number of transactions you want to filter by, as a parameter (unless the filtering is done later, in which case that approach won't work)
I have two tables in DB with OneToMany relations. Using 'createQueryBuilder()' method in 'EntityRepository' i try to select some objects with conditions. There is my method:
$query = $this->getEntityManager()->createQueryBuilder();
$query->select("parent")
->from('TestAppBundle:Parent', 'parent')
->leftJoin("parent.children", 'child', 'WITH', 'child.IdP = parent.id')
->where('child.date < :date')
->andWhere('child.status = :null')
->setParameter('date', new DateTime())
->setParameter('null', 0);
And it works almost good. I get Parent Objects with Children Objects in ArrayCollections. Methods select the Parent Objects with conditions but the problem is I get also Children Objects which don't keep the conditions.
I want to get only Parent Objects which keep conditions and Children Objects which also keep conditions. At this time a have to filter results after query and remove Children Object manualy.
I hope you will understand the problem :)
Basically, if you don't select the entities at query time you'll lazy load all children for the parent when you call getChildren(). Select both children and parents like this to avoid lazy loading:
$query->select("parent, child")
For more information please see my answer to a similar question.
Could you try something like this:
$query = $this->getEntityManager()->createQueryBuilder();
$query->select("parent")
->from('TestAppBundle:Parent', 'parent')
->leftJoin("parent.children", 'child', 'WITH', 'child.status = 0');
As far as I understand your needs you should not use leftJoin - with this field you will get parents that don't have children. Use innerJoin instead.
Assuming that child.IdP refers to the parent.id in your model definition you don't have to use WITH clause in this way. So the queryBuilder would be:
$query = $this->getEntityManager()->createQueryBuilder();
$query->select("parent")
->from('TestAppBundle:Parent', 'parent')
->innerJoin("parent.children", 'child')
->where('child.date < :date and child.status = :null')
->setParameter('date', new DateTime())
->setParameter('null', 0);
Regards.
EDIT:
Further research: Looks like the answer lies on changin default Hydrator for a customized. Doctrine2 allows you to change it just by sending his name as a parameter:
$query->getResult('CustomHydrator');
Dont forget to register it first, in your config.yml file:
doctrine:
orm:
hydrators:
CustomHydrator: \your\bundle\Hydrators\CustomHydrator
My relationship between Blog and Comment entity is one to many. 1 Blog has N Comments
After researching about how to add an extra field to a fetched object in Doctrine 2 I found Aggregate Fields, it is a good article but just talk about to get balance from a single Account, it never says what we should do when working with an array of Accounts, it may sound silly but let me explain my situation.
In my case is not about accounts and entries, is about blogs and comments.
What I'm trying to do is to list a number of blogs and just show how many comments it has without loading any comment information, in other words I want to translate this query to the Doctrine2 World.
'SELECT b.*, COUNT( b.id ) AS totalComments FROM `blogs` b LEFT JOIN comments c ON b.id = c.blog_id GROUP BY b.id LIMIT 8'
and the result that I expect is an array of Blog Objects with a totalComments attribute setted correctly, like this:
array (size=8)
0 =>
object Blog
'id' => int 330
'title' => string 'title blog'
// Added field, not visible in table DB. Came through query COUNT() statement
'totalComments' => int 5
// ... more attributes
1 => ...
//more object blogs
);
I just can't achieve this, the best I could do was this:
Creating and fetching Query:
$qb = $this->createQueryBuilder('b')
->select('b, c')
->addSelect('count(b.id) as nComments')
->leftJoin('b.comments', 'c')
->groupBy('b.id')
return $qb->getQuery()->getResult();
and the result I'm getting is an array of arrays, where position 0 has Blog Object and position "totalComments"
// var_dump($result)
array (size=8)
0 =>
array(2) =>
0 =>
object Blog
'id' => int 330
'title' => string 'title blog'
// ... more attributes
"totalComments" => int 5
1 => ...
);
I also tried to make my own Hydrator but I just started using Doctrine2 and found myself kinda lost.
I hope been enough clear. I can give any other information if needed.
Thanks in advance!
You either have to name the fields you want, or have a mixed result ( like your 2nd example). So for a flat array:
$qb = $this->createQueryBuilder('b')
->select('b.title, b.author')
->addSelect('count(c.id) as nComments')
->leftJoin('b.comments', 'c')
->groupBy('b.id')
return $qb->getQuery()->getArrayResult();
Or a mixed result:
$qb = $this->createQueryBuilder('b')
->select('b')
->addSelect('count(c.id) as nComments')
->leftJoin('b.comments', 'c')
->groupBy('b.id')
return $qb->getQuery()->getResult();
After few days I came up with this solution.
I had to add a totalComments attribute to my Blog Entity Class and his get/set methods, and tweak a bit my getLatestBlogs function:
function getLatestBlogs(){
$qb = $this->createQueryBuilder('b')
->select('b, c')
->addSelect('count(b.id) as totalComments')
->leftJoin('b.comments', 'c')
->groupBy('b.id');
$result = $qb->getQuery()->getResult();
//tweaking original result
foreach($result as $row){
$row[0]->setTotalComments($row['totalComments']);
$blogList[] = $row[0];
}
return $blogList;
}
Doing it this way I finally get a simple array of Blog Objects, and it just took an extra loop.
After this I realized that would be nice to have a general function who can work with any Entity, so I made the next function:
function fixResult($qResult){ //Receives $qb->getQuery()->getResult();
if(is_array($qResult)){
$list = array();
$keys = array_keys($qResult[0]); //Getting all array positions from first row
$object = $qResult[0][0]; //Getting the actual object fetched
foreach($keys as $key){ //Searching for existing set methods in the Object
$method = "set".ucfirst($key);
if(method_exists($object,$method))
$methods[$key] = $method;
}
foreach($qResult as $row){ //Calling set methods for every row fetched and storing into a new array
foreach($methods as $key => $met){
$row[0]->$met($row[$key]);
$list[] = $row[0];
}
}
return $list;
}
else return false;
}
I hope somebody else find it useful.