Doctrine paginator count is not correct - php

In my Symfony project I have a User entity with the following properties to define followers relation
#[ORM\ManyToMany(targetEntity: self::class, inversedBy: 'followers')]
#[ORM\JoinTable(name: "follows")]
#[ORM\JoinColumn(name: "follower_id", referencedColumnName: "id")]
#[ORM\InverseJoinColumn(name: "followed_id", referencedColumnName: "id")]
private $followedUsers;
#[ORM\ManyToMany(targetEntity: self::class, mappedBy: 'followedUsers')]
private $followers;
I am attempting to get the paginated list of a User's followers with the following query in my UserRepository
public function getPaginatedFollowersByUser(User $user, int $offset, int $limit): Paginator
{
$qb = $this->createQueryBuilder('u')
->select('u')
->innerJoin('u.followers', 'f')
->andWhere('f.id = :userId')
->setParameter('userId', $user->getId())
->setFirstResult($offset)
->setMaxResults($limit);
$paginator = new Paginator($qb, true);
return $paginator;
}
where Paginator is an instance of Doctrine\ORM\Tools\Pagination\Paginator.
This works fine and now I want to know how many items are there in the result. In the DB there is only 1 follower defined for the user I am querying, yet $paginator->count() and count($paginator) both return the value 2. When I iterate the paginator I find only 1 result, as expected.
I am not sure what I am missing or doing wrong. Is the counting of the result done in a different way?
Thank you!
NOTE: The workaround I've found so far is to use
$count = count($paginatedUsers->getIterator()->getArrayCopy());
instead of
$count = count($paginatedUsers);
It is not very elegant but it does output the expected 1.

I am embarrassed to say that the paginator was working perfectly fine and it was my mistake that this failed.
The DB in fact had 2 entries corresponding to this query, meaning that the result given by the paginator was in fact correct. The $offset parameter in my query was 1 instead of the 0 that I expected, which made the first element be ignored and therefore my iterator only found 1 result but there were 2 results for the query.

Related

Query builder cannot associate with category entity

I have 2 entities, MyItem and MyItemCategory. When I try to create a query with query builder I get the following error:
Error: Class App\Entity\MyItem has no
field or association named myitem_category_id (500 Internal Server Error)
This is the where part of my query builder:
$queryBuilder = $this->getDoctrine()
->getRepository('App\Entity\MyItem')
->createQueryBuilder('m');
// adds where for category_id:
$queryBuilder->where('m.myitem_category_id = :category_id')->setParameter('category_id',$category_id);
Here're first entities:
/**
* #ManyToOne(targetEntity="MyItemCategory")
* #JoinColumn(name="myitem_category_id", referencedColumnName="id")
*/
private $myItemCategory;
...and my category entity:
/**
* #ORM\OneToMany(targetEntity="MyItem", mappedBy="myItemCategory")
*/
private $myItemCategories;
The querybuilder actually doesn't care about database fields, but instead uses the object mapper, so there is no field my_item_category_id on your Entity, but instead a field myItemCategory
So, you can either do:
$querybuilder
->where('m.myItemCategory = :category')
->setParameter('category', $category) // <-- an actual MyItemCategory object
or you can join it in and check for the id:
$querybuilder
->leftJoin('m.myItemCategory', 'mic')
->where('mic.id = :micid')
->setParameter('micid', $category_id)
(I actually don't know if m.myItemCategory.id = :micid might work ........ you could try ;o))

Doctrine queryBuilder: return object not array

I have this query created with doctrine querybuilder, the return i get is an array of arrays.
I would like to get a return that is an array of objects, is this possible?
I know that normally Doctrine returns objects of an entity, bit since i have an inner join to get the name from another table it returns arrays.
Thanks in advance.
$qb->select('u', 'h.name')
->from('AppBundle:UserHose', 'u')
->innerJoin('AppBundle:Hose', 'h', 'WITH', 'u.hoseId = h.id')
->where('u.userId = :userId')
->orderBy('u.id', 'DESC')
->setParameter('userId', $userId);
return $qb->getQuery()->getResult();
you can use this:
return $qb->getQuery()->getResult(Query::HYDRATE_ARRAY);
Or this:
return $qb->getQuery()->getArrayResult();
This isn't possible this way. In other words, you are doing it wrong.
You are telling Doctrine to return a collection of collections containing an entity and a string so this is what you get. Doctrine won't make an object out of that since it does not know how to hydrate such result.
[
[entity, string],
[entity, string],
....
]
If you wish to receive a collection of objects only, you would need to create a new entity that has both fields (related entity and a string property), then use a ResultSet mapping to hydrate that.
if you want array of objects you have to set relation betwen Entities, and create a query by the owning side of relation.
example:
Tourney entity , Invite entity
Invite
/**
* #ORM\ManyToOne(targetEntity="Tourney", inversedBy="invites")
*/
protected $tourneys;
Tourney
/**
* #ORM\OneToMany(targetEntity="Invite", mappedBy="tourneys", cascade={"persist", "remove"})
* #ORM\JoinColumn(nullable=true, onDelete="CASCADE")
*/
protected $invites;
now you have to make query to the owning side of relation (Invite)
and it will be holding all your join object data with Tourneys in field $invites
and it gives you array of objects. based on your query
remeber of setter $invites as setInvites(Tourney $invites) and by inverse side of relation setTourneys(Invite $tourneys)
Just add \Doctrine\ORM\Query::HYDRATE_ARRAY on getResult() like this
$qb->select('u', 'h.name')
->from('AppBundle:UserHose', 'u')
->innerJoin('AppBundle:Hose', 'h', 'WITH', 'u.hoseId = h.id')
->where('u.userId = :userId')
->orderBy('u.id', 'DESC')
->setParameter('userId', $userId);
return $qb->getQuery()->getResult(\Doctrine\ORM\Query::HYDRATE_ARRAY);

Doctrine load relations from an Arraycollection

I'm looking for an elegant way to do the following:
Lets say I have an Entity with a OneToMany Relation, e.g.
class Parent
{
/**
* #ORM\OneToMany(targetEntity="Child")
*/
private $children;
public function __construct()
{
$this->children = new ArrayCollection();
}
}
class Child
{
/**
* #ORM\ManyToOne(targetEntity="Parent")
*/
private $parent;
}
Now in my logic I have to filter those parents which can't be done by queries only. So I end up with an ArrayCollection of parents, e.g:
$parents = new ArrayCollection([
$parent1,
$parent2,
$parent3
]);
Now from all those parents, I would like to join the children with one query.
How could I do this with doctrine.
I know I could just loop over the collection and call ->getChildren() on each parent.
But I possibly have hundreds of parents (which would mean hundreds of queries). Which I would like to avoid.
Before anyone says 'do a LEFT JOIN on the parents when you get them', I cannot filter the parents I need with only queries. So this is why I cannot just LEFT JOIN.
Cookies for thoughs !
In order to avoid the N + 1 selects problem, I would suggest the following solution which doesn’t need JOINs and uses two separate queries. This is the most efficient solution.
First, retrieve all parents:
$parents = $em->createQueryBuilder()
->select("p")
->from("YourFoobarBundle:Parent", "p")
->where(/*...*/)
->setParameter(/*...*/)
->indexBy("p.id")
->getQuery()->getResult();
Now load all children of those parents:
$children = $em->createQueryBuilder()
->select("c")
->from("YourFoobarBundle:Child", "c")
->where("IDENTITY(child.parent) IN (?1)")
->setParameter(1, array_keys($parents))
->getQuery()->getResult();
The great thing about Doctrine is that now all needed entities are stored in memory. So when you do a $parent->getChildren(), you don’t trigger a new DB query (unless the children have additional relations themselves).
NOTE: If you always need all children of the selected parents, you should mark the $children for eager loading:
/**
* #ORM\OneToMany(targetEntity="Child", fetch="EAGER")
*/
private $children;
In this case, Doctrine will always (!) fetch the needed children automatically.
You can make a sub query and use it inside a clause (a where in expression) of your other query. It is very much like #lxg describes, but you can do all this in one single query for increased performance (You don't have to execute the queries separately).
$qb = $entityManager->createQueryBuilder();
$sub = $qb->select('p')
->from('Application\Entity\Parent', 'p')
->where(/*...*/)
->setParameter(/*...*/)
$children = $qb->select('c')
->from('Application\Entity\Child', 'c')
->where($qb->expr()->in('c.parent', $sub->getDQL()))
->getQuery()
->getResult();

How to optimize number of Doctrine queries when using an inner Join?

I have to simple Entity: Log and User.
Log has a ManyToOne relationship with Entity.
Log:
type: entity
repositoryClass: LogRepository
id:
id:
type: integer
generator:
strategy: AUTO
fields:
message:
type: string
manyToOne:
user:
targetEntity: User
joinColumns:
user_id:
referencedColumnName: id
My use case is to show the list of the logs and one or two information about the user (like his name and his mail for example)
If I use the findall method, Symfony debug toolbar shows me that Doctrine performs a lot of queries. One query gives me the logs and one query is performed for each user! It is not good of course because I can have thousand logs in my view. I don't want to overload my database server. This problem seems very simple to solve. But I'm searching for a while and the results seems to be "bad practices".
So I began by writing a new method in the LogRepository class using the querybuilder:
public function getLog(){
$qb = $this->createQueryBuilder('l')
->select('l')
->innerJoin(
'ApplicationSonataUserBundle:User', 'u',
Expr\Join::WITH,'l.user = u.id')
;
return $qb->getQuery()->getResult();
}
I still had the same problem. I have changed the select parameters on my method to :
public function getLog(){
$qb = $this->createQueryBuilder('l')
->select('l','u')
->innerJoin('ApplicationSonataUserBundle:User','u',
Expr\Join::WITH,'l.user = u.id')
;
return $qb->getQuery()->getResult();
}
Eureka? OK, I only have one query but my method didn't return only Log, BUT User too... So my Twig template crashes because my loop contains User, not only Log. When this is a User, my view crash because I want to write message fields. (Log.message exists. But User.message is not a valid field, of course)
It works pretty good, if I change one more time my method with a loop to filter my results :
public function getLog(){
$qb = $this->createQueryBuilder('l')
->select('l','u')
->innerJoin('ApplicationSonataUserBundle:User','u',
Expr\Join::WITH,'l.user = u.id')
;
//THE STRANGE LOOP
$results = array();
foreach ($qb->getQuery()->getResult() as $result){
if ($result instanceof Log){
$results[] = $result;
}
};
return $results;
}
I have only one query, it is what I'm searching for. My twig template doesn't crash, because my array contains only Log.
So what's the matter? It works, but I think this is not the good/best practices.
Someone can explain me a better way, a better practice to use an inner join query, to minimize the performed query and have an ArrayCollection result which contains only instance of Log?
It should not be necessary to use the loop. Try like this:
public function getLog(){
$qb = $this->createQueryBuilder('l')
->select('l','u')
->innerJoin('l.user', 'u');
$logs = $qb->getQuery()->getResult();
return $logs;
}
It should only return $logs with a populated (fetch joined) association user.

Elegant way to walk backward through OneToOne table entities with Doctrine

I have a very simply structured entity that contains a simple association
Database_Entity_Tenant
id (primary key)
parentId (id of the parent entry)
code (a simple identifier for the tenant, unique)
I defined parentId in my entity accordingly:
/**
* #Column(type="integer")
* #OneToOne(targetEntity="Tenant")
* #JoinColumn(name="parentTenantId", referencedColumnName="id")
* **/
protected $parentId;
This works fine - the generated database schema resembles my choices and its good.
Now i am writing my first method which basically has to return an array of all the tenants that are chained together, in reverse order (i use this for walking backward through a chain of tenants).
In order to do that i came up with the idea to use a while() loop.
$currentTenant = {DATABASE_ENTITY_TENANT}; // In my real code i fetch the entity object of the current tenant
$chain[] = $currentTenant;
$repository = Database::entityManager()->getRepository('Database_Entity_Tenant');
while(!$currentTenant->getParentId()){
$currentTenant = $repository->findOneBy(array(
'id' => $currentTenant->getParentId()
));
$chain[] = $currentTenant;
}
Any tenant that has no parent (such as the base tenant) will have no parent id (or null), so that would end the while loop.
Now all this may work, but it seems really rough to me. I am fairly new to Doctrine so i don't know much about it but i am sure there is some way to do this more elegantly.
QUESTION
Does Doctrine 2 provide me with any set of functions i could use to solve the above problem in a better way?
If not, then is there any other way to do this more elegantly?
If I'm not getting your problem wrong, you just need to find all the entries in your association table ordered by the parentId. In Doctrine2 you can do the following:
$currentTenant = {DATABASE_ENTITY_TENANT}; // assuming a valid entity
$repository = Database::entityManager()
->getRepository('Database_Entity_Tenant')
->createQueryBuilder('t')
->where('t.parentId IS NOT NULL')
->andWhere('t.parentId < :current') /* < or > */
->setParameter('current', $currentTenant->getParentId()->getId())
->orderBy('t.parentId', 'ASC') /* ASC or DESC, no array_reverse */
->getQuery()
->getResult();
/* At this point $repository contains all what you need because of Doctrine,
* but if you want a chain variable: */
$chain = array();
foreach ($repository as $tenant) {
$chain[] = $tenant->getCode(); // your tenant entity if your entity is mapped correctly
}
Hope this helps!

Categories