Doctrine - How to hydrate a collection when using query builder - doctrine-orm

A previous question I asked was to do with hydrating a result set when using Doctrine and query builder. My issue was how to return an array and their sub-sets:
This was for a single result set and the answer was quite simple:
$qb = $this->stoneRepository->createQueryBuilder('S');
$query = $qb->addSelect('A','P','I','C')
->leftJoin('S.attribute', 'A')
->leftJoin('A.category', 'C')
->innerJoin('S.product' , 'P')
->innerJoin('S.image' , 'I')
->where('S.id = :sid')
->setParameter('sid', (int) $stone_id)
->getQuery();
$resultArray = $query->getOneOrNullResult(\Doctrine\ORM\Query::HYDRATE_ARRAY);
return $resultArray;
My next question is how to do this exact same thing for a collection? This is what I have tried:
public function fetchAll()
{
$qb = $this->stoneRepository->createQueryBuilder('S');
$qb->addSelect('A','P','I','C')
->leftJoin('S.attribute', 'A')
->leftJoin('A.category', 'C')
->innerJoin('S.product' , 'P')
->innerJoin('S.image' , 'I')
->where('S.state=:state')
->setParameter('state' , 1 );
$adapter = new DoctrineAdapter( new ORMPaginator( $qb ) );
$collection = new StoneCollection($adapter);
return $collection;
}
The problem I am facing with this solution is that the join tables are not being populated and I am ending up with a collection of empty results.
The StoneCollection class simply extends paginator:
<?php
namespace Api\V1\Rest\Stone;
use Zend\Paginator\Paginator;
class StoneCollection extends Paginator
{
}
I am thinking that perhaps the best mehod is to get an array and to page the array?
EDIT::
I have this working although I am not keen on it as I hit the DB twice. The first time to build the array (Which is the entire result set which could be very big for some applications) and then the second time to page the results which is then returned to HAL in ApiGility for processing...
Ideally this should be done in one go however I am not sure how to hydrate the results in a single instance...
public function fetchAll( $page = 1 )
{
$qb = $this->stoneRepository->createQueryBuilder('S');
$qb->addSelect('A','P','I','C')
->leftJoin('S.attribute', 'A')
->leftJoin('A.category', 'C')
->innerJoin('S.product' , 'P')
->innerJoin('S.image' , 'I')
->where('S.state=:state')
->setParameter('state' , 1 );
$resultArray = $qb->getQuery()->getResult(\Doctrine\ORM\Query::HYDRATE_ARRAY);
$paginator = new \Zend\Paginator\Paginator(new \Zend\Paginator\Adapter\ArrayAdapter($resultArray));
$paginator->setCurrentPageNumber($page);
return $paginator;
}

The Answer to this is as I have above:
I have this working although I am not keen on it as I hit the DB twice. The first time to build the array (Which is the entire result set which could be very big for some applications) and then the second time to page the results which is then returned to HAL in ApiGility for processing...
Ideally this should be done in one go however I am not sure how to hydrate the results in a single instance...
public function fetchAll( $page = 1 )
{
$qb = $this->stoneRepository->createQueryBuilder('S');
$qb->addSelect('A','P','I','C')
->leftJoin('S.attribute', 'A')
->leftJoin('A.category', 'C')
->innerJoin('S.product' , 'P')
->innerJoin('S.image' , 'I')
->where('S.state=:state')
->setParameter('state' , 1 );
$resultArray = $qb->getQuery()->getResult(\Doctrine\ORM\Query::HYDRATE_ARRAY);
$paginator = new \Zend\Paginator\Paginator(new \Zend\Paginator\Adapter\ArrayAdapter($resultArray));
$paginator->setCurrentPageNumber($page);
return $paginator;
}

On the Doctrine documentation for Pagination they state to use $fetchJoinCollection = true, which I believe is the same as the HYDRATE you are trying to use.
Doctrine Pagination
On my pagination code for my QueryBuilder I use it like the following:
public function getAllPaginated($page, $limit){
$query = $this->createQueryBuilder('o')
->select('o')
->getQuery();
$paginator = new Paginator($query, $fetchJoinCollection = true);
$paginator->getQuery()
->setFirstResult($limit * ($page - 1)) // Offset
->setMaxResults($limit);
return $paginator;
}

Related

Symfony Error: Invalid PathExpression. Must be a CollectionValuedAssociationField

I'm using a query builder to set up a list of documents who have different relationships to other entities. The list also includes a filter form for all of the entities in that query builder.
It worked fine for all of them so far, but as soon as I added the $products entity, it doesn't work any more.
My code:
$qb = $em->createQueryBuilder();
$query = $qb->select('d', 'p')
->from('DocumentBundle:Document', 'd')
->innerJoin('d.products', 'p')
->orderBy('d.id', 'DESC')
->join('d.type', 'dt')
->join('d.status', 's');
if(count($type) > 0){
$query->andwhere($query->expr()->in('d.type', ':type'))
->setParameter('type',$type);
}
if(count($status) > 0) {
$query->andWhere($query->expr()->in('d.status', ':status'))
->setParameter('status', $status);
}
if(count($markets) > 0){
$query->andWhere(':marketIds MEMBER OF d.markets')
->setParameter('marketIds',$markets);
}
else{
$query->andWhere(':marketIds MEMBER OF d.markets')
->setParameter('marketIds',$userMarkets);
}
if(count($airlines) > 0){
$query->andWhere(':airlineIds MEMBER OF d.airlines')
->setParameter('airlineIds',$airlines);
}else{
$query->andWhere(':airlineIds MEMBER OF d.airlines')
->setParameter('airlineIds',$userAirlines);
}
if(count($products) > 0){
$query->andWhere(':productIds MEMBER OF p.id')
->setParameter('productIds',$products);
}
// else{
// $query->andWhere(':productIds MEMBER OF p.id')
// ->setParameter('productIds',$currentuser->getProducts());
// }
$query->andWhere('d.active = ?1')
->setParameter(1, $archive ? 0 : 1);
return $query
->setFirstResult($page * $maxRows - $maxRows)
->setMaxResults($maxRows)
;
So for type and status, I have a ManyToOne relationship, for markets, airlines and (the troublemaker) products, it's a ManyToMany relationship.
The current code throws the exception:
[Semantical Error] line 0, col 237 near 'id AND d.active': Error:
Invalid PathExpression. Must be a CollectionValuedAssociationField.
The strange thing about this, is that I have an other list for another entity which also has a ManyToMany relationship to products, and for that list it is working. Also strange about this: for that other list my query looks like that:
$qb = $em->createQueryBuilder();
$query = $qb->select('a', 'p')
->from('AppBundle:Agency', 'a')
->innerJoin('a.products', 'p')
->orderBy('a.id', 'ASC');
if(count($markets) > 0){
$query->andWhere('a.market IN (:marketIds)')
->setParameter('marketIds',$markets);
}
else{
$query->andWhere('a.market IN (:marketIds)')
->setParameter('marketIds',$currentUser->getMarkets());
}
if(count($products) > 0){
$query->andWhere('p.id IN (:productIds)')
->setParameter('productIds',$products);
}
else{
$query->andWhere('p.id IN (:productIds)')
->setParameter('productIds',$currentUser->getProducts());
}
It's a ManyToOne for markets and a ManyToMany for products here.
I tried to user the same code (p.id in (:productIds)...) for my documents and this is kind of working, so I at least don't get an error any more. But when I then filter on something (like markets, airlines, products etc.) it's not working any more, so the list is just empty. Filtering for that second list is working though, so I don't know where this is coming from.
Code update
$qb = $em->createQueryBuilder();
$query = $qb->select('d', 'p', 'm', 'a')
->from('DocumentBundle:Document', 'd')
->innerJoin('d.products', 'p')
->innerJoin('d.markets', 'm')
->innerJoin('d.airlines', 'a')
->orderBy('d.id', 'DESC')
->join('d.type', 'dt')
->join('d.status', 's');
if(count($type) > 0){
$query->andwhere($query->expr()->in('d.type', ':type'))
->setParameter('type',$type);
}
if(count($status) > 0) {
$query->andWhere($query->expr()->in('d.status', ':status'))
->setParameter('status', $status);
}
if(count($markets) > 0){
$query->andWhere('m.id IN (:marketIds)')
->setParameter('marketIds',$markets);
}
if(count($airlines) > 0){
$query->andWhere('a.id IN (:airlineIds)')
->setParameter('airlineIds',$airlines);
}
if(count($products) > 0){
$query->andWhere('p.id IN (:productIds)')
->setParameter('productIds',$products);
}
$query->andWhere('d.active = :archiveParam')
->setParameter("archiveParam", $archive ? 0 : 1);
dump($query->getQuery());
Filtering is working for status, type and markets but not for airlines and products. Any ideas?
Reproducing issue
So this is my filterform (unnecessary filters are black). they are all multiselect dropdowns. When filtering for markets and status, the list is reducing to only the documents who have that certain status or are assigned to the selected markets. For all of the ManyToMany relationships(markets, airlines and products) I have own database tables and they all contain data.
Sample data would be:
Document Nr. 42 is assigned to airlines LH and LX, to markets CA, MX and US and to product Nr. 1. So when filtering for one of the markets, the document always appear in the list. But when filtering for one of the airlines or the product, the list stays empty.
Edit
I just recently added the product filter and before I had that one, the filtering for airlines actually worked fine. Even though I used the 'wrong' query with MEMBER OF d.airlines.
This part
if(count($products) > 0){
$query->andWhere(':productIds MEMBER OF p.id')
->setParameter('productIds',$products);
}
should look like this
if(count($products) > 0){
$query->andWhere('p.id IN (:productIds) ')
->setParameter('productIds',$products);
}
You are also mixing positional and named parameters.
$query->andWhere('d.active = :archiveParam')
->setParameter("archiveParam", $archive ? 0 : 1);

Doctrine QueryBuilder - Struggling with table 'state' variable

I have the following query and the last part of it is to check the state of the item which will be 1 or 0;
My api calls:
http://example.com/api/search?keyword=someword&search_for=item&return_product
The query works as expected, except for one thing. Some of the stone items are disabled and I need to ignore where:
->where('S.state=:state')
->setParameter('state' , 1 )
I am not quite sure where to add this to the current query to get it to work:
$qb = $this->stoneRepository->createQueryBuilder('S');
//Get the image for the item
$qb->addSelect('I')
->leftJoin('S.image' , 'I');
//Check if we want products returned
if ( $return_product )
{
$qb->addSelect('P','PI')
->leftJoin('S.product' , 'P')
->leftJoin('P.image' , 'PI');
}
//Check is we want attributes returned
if ( $return_attribute )
{
$qb->addSelect('A','C')
->leftJoin('S.attribute' , 'A')
->leftJoin('A.category' , 'C');
}
//Check the fields for matches
$qb->add('where' , $qb->expr()->orX(
$qb->expr()->like('S.name' , ':keyword'),
$qb->expr()->like('S.description' , ':keyword')
)
);
//Set the search item
$qb->setParameter('keyword', '%'.$keyword.'%');
$qb->add('orderBy', 'S.name ASC');
Just after the createQueryBuilder call:
$qb = $this->stoneRepository
->createQueryBuilder('S')
->where('S.state = :state')
->setParameter('state', 1);
With the query builder the order is not important: you can add SQL pieces in different order and even override pieces already added.

Symfony2 Doctrine2 : querybuilder bad query

New to Symfony2 and Doctrine2, i have a function in my entity repository to search entities after form submission. Input is array $get that contain form fields like $get['name'] = 'aname'.
My problem is that when i request with an id, or an id and a name, it's ok by with only a name, all my entities are matched because the query that has been build have no where clause.
Here is my code :
public function search(array $get, $flag = False){
/* Indexed column (used for fast and accurate table cardinality) */
$alias = 'd';
/* DB table to use */
$tableObjectName = 'mysiteMyBundle:DB';
$qb = $this->getEntityManager()
->getRepository($tableObjectName)
->createQueryBuilder($alias)
->select($alias.'.id');
$arr = array();
//Simple array, will grow after problem solved
$numericFields = array(
'id');
$textFields = array(
'name');
while($el = current($get)) {
$field = key($get);
if ( $field == '' or $field == Null or $el == '' or $el == Null ) {
next($get);
}
if ( in_array($field,$numericFields) ){
if ( is_numeric($el) ){
$arr[] = $qb->expr()->eq($alias.".".$field, $el);
}
} else {
if ( in_array($field,$textFields) ) {
$arr[] = $qb->expr()->like($alias.".".$field, $qb->expr()->literal('%'.$el.'%') );
}
}
next($get);
}
if(count($arr) > 0) $qb->andWhere(new Expr\Orx($arr));
else unset($arr);
$query = $qb->getQuery();
if($flag)
return $query;
else
return $query->getResult();
}
The query generated with only a name (ex "myname") input is :
SELECT d0_.id AS id0 FROM DB d0_
It should be:
SELECT d0_.id AS id0 FROM DB d0_ WHERE d0_.name LIKE '%myname%'
What's wrong with my code ?
Thanks !
I don't know if it's related, but do not use "OR" or "AND" operators, because they have a different meaning that the classic "&&" or "||". cf http://php.net/manual/en/language.operators.logical.php
So, first, replace "AND" by "&&", and "OR" by "||".
you should use the setParameter method
$query->where('id = :id')->setParameter('id', $id);

How do I use LIMIT in a codeigniter/doctrine query?

I am using CodeIgniter2 + Doctrine2, and have the following query:
$query = $this->doctrine->em->createQuery("
SELECT u
FROM ORM\Dynasties2\Characters u
WHERE u.fathersId = $key
AND u.deathDate IS NULL
AND u.isRuler = '0'
AND u.isFemale = '0'
AND u.useAI = '1'
AND u.bornDate <= $of_age
");
$sons_of_age = $query -> getResult();
And I only want to get ONE result, assuming there are any hits.
I've looked at Doctrine documentation about using ->LIMIT(1) but I have tried putting this into my query in various places, and only get errors.
Codeigniter has some functions builtin to do $query->row() but this does not seem to work - I wager because of the Doctrine integration.
Thanks!
You're looking for method Query::setMaxResults($number); Then you can use Query:getSingleResult();, but method Query:getSingleResult(); throws error if there's no record.
$query = $this->doctrine->em->createQuery("
SELECT u
FROM ORM\Dynasties2\Characters u
WHERE u.fathersId = $key
AND u.deathDate IS NULL
AND u.isRuler = '0'
AND u.isFemale = '0'
AND u.useAI = '1'
AND u.bornDate <= $of_age
");
$query->setMaxResults(1);
try {
$sons_of_age = $query->getSingleResult();
} catch (\Doctrine\ORM\NoResultException $e) {
$sons_of_age = null;
}

doctrine2 - querybuilder, empty parameters

what can i do if the parameter has no value?
my query:
$query = $this->_em->createQueryBuilder()
->select('u')
->from('Users', 'u')
->where('u.id = ?1')
->andWhere('u.status= ?2')
->setParameter(1, $userid)
->setParameter(2, $status)
->getQuery();
return $query->getResult();
if theres no $status, then it doesnt display anything.
i tried putting a condition before the query to check if its null but what value can i set $status iif theres no status set
The query builder is exactly there for building conditional queries. You could do:
$qb = $this->_em->createQueryBuilder();
$query = $qb->select('u')
->from('Users', 'u')
->where('u.id = ?1')
->setParameter(1, $userid);
if ($status) {
$qb->andWhere('u.status = ?2')
->setParameter(2, $status);
}
return $qb->getQuery()->getResult();
On a side note, it is best practice to use named placeholders e. g. like this:
$qb->andWhere('u.status = :status')
->setParameter('status', $status);
You could write:
->andWhere('(u.status= ?2 or ?2 is null)')