Problem optimization with symfony 6 / doctrine : too many querys - symfony

The project is develop with symfony 6.0.8.
I have 2 entitys : utilisateur (user) & compte_client (customer) - link by OneToOne relation because an user may be not a customer. A user can be : an employee, a partner or a customer. The foreign-key is in the customer table (compte_client -> utilisateur_id)
Here i want to recover informations from users who are customers too. I use the queryBuilder, i recover user's informations with this :
$queryBuilder->andWhere('u.type_utilisateur = :val');
$queryBuilder->setParameter('val', $type);
$queryBuilder->join('u.compte_client','c')
->addSelect('c');
$queryBuilder->orderBy('u.login', 'ASC');
$query = $queryBuilder->getQuery();
$results = $query->getResult();
return $results;
I do a join to limit the number of querys but here, to recover 2 user's datas, it realized 7 querys.
If I delete the join request, it realise 3 querys, but i think it can be reduce to 1 query (as i see in online courses - that used join request too).
I tried to add some attributs on my entites like "fetch:EAGER" or "fetch:EXTRA_LAZY" but it's ineffective.
Before all of it, I had similar problem to recover data's from users that aren't customers. When i checked querys realized by doctrine for loading only users who are employee, their was so many useless querys to customer's table despite of I didn't try to recover any customer's information, and their was no result expected. To reduce the number of query's i had to add a join request like that :
$queryBuilder
->Where('u.type_utilisateur LIKE :type')
->setParameter('type', 'Entreprise%')
->leftjoin('u.compte_client','c')
->addSelect('c')
->orderBy('u.login', 'ASC');
$query = $queryBuilder->getQuery();
$results = $query->getResult();
return results;
This, reduce query at only 1.
So is there some good pratice to optimize the number of querys generat by doctrine ?

You have to think of Doctrine OneToOne as a form of inheritance: the inverse side "inherits" the owning side's properties (reminder: The owning side of a OneToOne association is the entity with the table containing the foreign key) so when you fetch the inverse side Doctrine always fetches the owning side.
That means the good practice here would be to change the owning side as it makes more sense for customer to inherit from user.

Related

Find the right DQL

I have some difficulties in DQL (and SQL !). I always write simple querys but now I need something more specific and I don't know how to do it.
I have an entity User in OneToMany with my entity Program which is in OneToMany with my entity Exercice which is in OneToMany with my Reps.
I would like to know for an specific User all the reps is done.
What would be the best way to do it ?
$this->createQueryBuilder('user')
->select('rep')
->join('user.programs', 'program')
->join('program.exercices', 'exercice')
->join('exercice.reps', 'rep')
->getQuery()
->getResult();
Explication:
This code must be in your repository. The user repository one.
$this->createQueryBuilder('user')
is equivalent to
$this->_em->createQueryBuilder()
->select('user')
->from('user')
You can override the select with the second line:
->select('rep')
Then do the join as described in your question.
programs must be the entity attribute you defined in user to join program
Same goes for exercices and reps. They correspond to the inversed side of the relationship.
The second paramter in join() function is the alias. It can be anything you want. You use aliases in select and in the next join()

Force 'fetch joined' relations to include IDENTITY of their ManyToOne relations using HYDRATE_ARRAY?

I have a query in which I'm joining a number of tables to my original Person entity. A Person may have multiple Child relations (OneToMany), and a Child may have a School they go to (ManyToOne). Problem is, I don't need the entire School entity that connects to each child, only their id, which is already stored on Child.
I'm using a Paginator to iterate through the results and I use HYDRATE_ARRAY to reduce overhead of the ORM parsing data to entity objects. But the id fields of unfetched relations are not returned this way, and thus, the School id isn't either.
I may join the School entity too, but since the identity is already stored on the Child records, I don't see why I should further reduce performance by having the database join another table. Fetching results as entity objects would also solve the problem, but also at the cost of performance. How can I get the id to apper the results without having to unnecessarily join the the School entity or having to hydrate the results as objects?
$query = $em->getRepository(Entity\Person::class)->createQueryBuilder('p');
$query
->select([
'p as person',
'w.name as workplace_name',
'c',
])
->leftJoin('p.children', 'c') //Entity\Child
->leftJoin('p.workplace', 'w') //Entity\Company
//...
;
$paginator = new Paginator($query);
$paginator->getQuery()
->setHydrationMode(\Doctrine\ORM\Query::HYDRATE_ARRAY);
You can use Doctrine\ORM\Query::HINT_INCLUDE_META_COLUMNS to include the foreign key column values in the result:
$paginator->getQuery()
->setHint(\Doctrine\ORM\Query::HINT_INCLUDE_META_COLUMNS, true)
->setHydrationMode(\Doctrine\ORM\Query::HYDRATE_ARRAY);
which:
The includeMetaColumns query hint causes meta columns like foreign keys and discriminator columns to be selected and returned as part of the query result.
Reference
Doctrine\ORM\Query documentation
How to get association foreign key IDs in Doctrine 2 without loading the associated object?
Getting only ID from entity relations without fetching whole object in Doctrine

How does Doctrine2 One-To-Many fetch=EAGER work?

I'm using Symfony 2.8 / Doctrine ORM 2.5.2.
I have 2 entities, Gallery OneToMany File
class Gallery
{
/**
* #var File[]
*
* #ORM\OneToMany(targetEntity="File", mappedBy="gallery", fetch="EAGER")
*/
private $files;
}
I see 2 things in the documentation.
First, now the OneToMany relationship does have the fetch=EAGER option (specified here). It was not there in previous versions.
Second, the manual setting for this fetch method per query seems not available for OneToMany but I don't know if the documentation is up-to-date as it states:
Changing the fetch mode during a query is only possible for one-to-one
and many-to-one relations.
I have anyway tried both, here is my query:
public function findWithEager()
{
$qb = $this->createQueryBuilder('g');
$query = $qb->getQuery();
$query->setFetchMode("CommonBundle\\Entity\\Gallery", "files", ClassMetadata::FETCH_EAGER);
return $query->getResult();
}
But when I do:
foreach ($galleryRepository->findWithEager() as $gallery) {
foreach ($gallery->getFiles() as $file) {
$file->getId();
}
}
Then I got 1+n queries. The first is SELECT * FROM Gallery and the n following ones are SELECT * FROM File WHERE id = :galleryId
I would like Doctrine to do 1+1 queries, the second one being SELECT * FROM File WHERE id IN (:galleryListIds)
Did I miss something? Is this behavior implemented in Doctrine?
The latest doctrine changelog states:
When marking a one-to-many association with fetch="EAGER" it will now
execute one query less than before and work correctly in combination
with indexBy.
It is not clear at all what is the expected behavior.
Any insight is welcome, thanks!
After much searching and some testing (using Doctrine ORM 2.5.6 on PHP 5.6) I have some results.
At this time it is not possible to get your Gallery entities with one query and all related File entities with a second query.
You have two options
Get Gallery and File entities in one query using Left Join.
This is what the ->find* methods do when you set fetch="EAGER" on your annotation.
You can do this manually with DQL: SELECT g, f FROM Gallery g LEFT JOIN g.files f
As noted in the docs, you cannot call ->setFetchMode('Gallery', 'files', ClassMetadata::FETCH_EAGER) on a DQL query to achieve the same result
... For one-to-many relations, changing the fetch mode to eager will cause to execute one query for every root entity loaded. This gives no improvement over the lazy fetch mode which will also initialize the associations on a one-by-one basis once they are accessed.
This will result in n additional queries being run immediately after your first query to fetch your Gallery entities.
Get Gallery entities with one query and lazy-load the File entities.
This is Doctrine's default behaviour (fetch="LAZY") and will result in 1 query plus an additional query for each set of Gallery#$files you access (1+n queries if you access them all).
Possible Future Option
There is a PR to add an EAGER_BATCHED fetch option which would do exactly what you want (fetch Gallery entities with one query then fetch all File entities with a second query) but there doesn't seem to be much happening with it, unfortunately.
Conclusion
If you're using the ->find* methods, fetch="EAGER" annotations will be respected. Doctrine will do this with a LEFT JOIN. Depending on your data set this could be ok or very expensive.
If you're writing DQL manually or using the query builder you must LEFT JOIN one-to-many relations AND remember to add any entities you want fetched to the select clause.
My preference is to write DQL whenever you want a one-to-many relation fetched eagerly because it makes your intention clear.

Doctrine2 (Doctrine 2.1) eager loading in Symfony2

Let's say I have two entities in my Symfony2 project : Category and Article (a category having many articles).
In my CategoryRepository, I have this method:
findAllDummy(){
return $this->createQueryBuilder('c')
->leftJoin('c.Articles a')
->getQuery()->getResult();
}
If I remember well, in Symfony1.4 (and the corresponding version of Doctrine), the returned objects would have their 'articles' attribute filled by the corresponding Article objects.
Now, in Symfony2, Proxy objects are returned.
So if I loop through a specific category's articles, As many queries as iterations will be executed.
foreach($category->getArticles() as $article){
echo $article->getDoctrine()
->getRepository('')getTitle();
}
I understand this is Doctrine2.1's default lazy loading behavior.
Question 1: how is this a better solution?
N queries instead of 1.
I tried to force eager loading by doing the following:
findAllDummy(){
return $this->createQueryBuilder('c')
->leftJoin('c.articles a')
->getQuery()
->setFetchMode('Category', 'articles', 'EAGER')
->getResult();
}
But the result remains the same.
Question 2: how to force eager loading in Doctrine2?
You're joining a table but you're not selecting anything from it. Add ->addSelect('a') to your query builder. Consider two following SQL queries to understand the difference:
SELECT a.id, a.title
FROM article a
JOIN category c ON a.category_id = c.id
WHERE a.id = 123;
SELECT a.id, a.title, c.id, c.name
FROM article a
JOIN category c ON a.category_id = c.id
WHERE a.id = 123;
Eager/lazy joining has nothing to do with DQL queries. It defines what should be loaded when you use $articleRepository->find(123).
In the part where you try to "force eager loading" the problem might be that you use the fetchMode method with the wrong variable type for the $fetchMode argument. You pass a string 'EAGER' but the method doesn't expect a string but an integer.
The method expects constants from the ClassMetadata class:
/**
* Specifies that an association is to be fetched when it is first accessed.
*/
const FETCH_LAZY = 2;
/**
* Specifies that an association is to be fetched when the owner of the
* association is fetched.
*/
const FETCH_EAGER = 3;
In the Doctrine documentation chapter 14.7.6.6. Temporarily change fetch mode in DQL you can see an example on how to use this:
$query->setFetchMode("MyProject\User", "address", \Doctrine\ORM\Mapping\ClassMetadata::FETCH_EAGER);
So pass either a reference to the constant or an integer that corresponds to the mode you want to use.
As it says in the doctrine docs, eager loading in this case won't make any difference because you have one-to-many relationship between Category and Article.
For one-to-many relations, changing the fetch mode to eager will cause to execute one query for every root entity loaded. This gives no improvement over the lazy fetch mode which will also initialize the associations on a one-by-one basis once they are accessed.
So contrary to what #Crozin has said, you can still do eager loading in DQL.
If you have a one-to-one or many-to-one relationship, eager loading will solve the problem of making extra queries. However to solve your problem in this case you should use ->addSelect('a') as #Crozin mentioned.
It is a better solution because joins are a much more expensive process than a simple query. While it may seem inefficient, it isn't much of a waste, and quickly becomes more efficient when you aren't loading every bit of every related object.

How to fetch random row via Doctrine2 querybuilder?

So far I have:
$qb1 = $this->getEntityManager()->createQueryBuilder();
$qb1->select('s')
->from('\My\Entity\Song', 's')
->where('s.id <> ?1')
->orderBy('RAND()', '')
->setMaxResults(1)
->setParameters(array(1=>$current->id));
But doctrine2 doesn't understand that:
Error: Expected end of string, got '('
Not even their querybuilder page has anything on it. Do you want to tell me that the best ORM for php doesn't have a random function?
The orderBy method should accept a field of Song for sorting purposes (such as 's.author' or 's.title'), and not a random value. Even if you chose a random field for ordering, such as selecting one randomly in php, this will not be very random at all, because you are always going to get the first result for the current sort criteria. If your songs have 8 fields, you would only get 8 different songs in your search results ever, even if you have thousands stored.
Here is a suggestion:
$qb1->select('s')
->from('\My\Entity\Song', 's')
->where('s.id <> ?1')
->setMaxResults(1)
->setParameters(array(1=>$current->id))
->setFirstResult($offset);
Here, $offset can be a random value you obtain in php via rand() or mt_rand() functions. Of course, $offset should be smaller than the total number of songs. This is just a suggestion, there are many ways you can accomplish this.
IMHO I think Doctrine2 is an extraordinary ORM, and there is nothing so advanced like it. I assume you read the Query Builder section of the reference guide, but I also suggest you read the DQL section, which explains what are the available functions within Doctrine query system, and how you can make your own (!).
You need to add custom DQL function RAND. For symfony2 framework you could simply add in config:
doctrine:
orm:
entity_managers:
default:
dql:
numeric_functions:
rand: DoctrineExtensions\Query\Mysql\Rand
And add to you dependencies in composer.json:
composer require beberlei/DoctrineExtensions
Then, the solution for getting 100 random AcmeBundle:Item entities would be as simple as:
$em = $this->getContainer()->get('doctrine')->getManager();
$messages = $em->createQueryBuilder()
->select('i, RAND() AS HIDDEN r')
->from('AcmeBundle:Item', 'i')
->orderBy('r', 'ASC')
->setMaxResults(100)
->getQuery()
->getResult();
Note: this assumes you're useing MySQL or MariaDB backend. For SQLite or PostGreSQL you may need a diffrent implementation class.

Resources