Doctrine 2.5 Unexpected association fetch behavior [Symfony 3] - php

I have 3 entities associated this way:
Don't worry, I've set associations using annotations, but I thought the following mix would be lighter/cleaner to expose my issue
Post
#ORM\ManyToOne(targetEntity="User", fetch="EAGER")
- author
User
#ORM\OneToOne(targetEntity="Vip", mappedBy="user", fetch="EAGER")
- vip
Vip
# Notice that the primary key of vip is a foreign key on User primary
#ORM\id
#ORM\OneToOne(targetEntity="User", inversedBy="peliqan", fetch="EAGER")
#ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=false)
- user
As you can see, all is set to be eagerly fetched.
What do I need?
I would like to retrieve Posts sets along with both Users & Vip infos ONLY, using a single query. (see edit)
Right now, for every post entry I get one extra query:
SELECT t0.valid AS valid_1, ...
FROM vip t0
INNER JOIN user t10 ON t0.user_id = t10.id WHERE t0.user_id = ?
when:
I execute this
$results = $qb
->select('ps')
->leftJoin('ps.author','u')->addSelect('u')
->add('where', $qb->expr()->in('ps.id', $ids))
->getQuery()->getResult();
and even while I try to enforce FETCH_EAGER mode like this
->getQuery()
->setFetchMode('AppBundle\Entity\User', 'vip', ClassMetadata::FETCH_EAGER)
->getResult();
Note:
I managed to get rid of extra query by enforcingQuery::HYDRATE_ARRAY upon getResult() call.
Queries vanished which saved a third of the initial time.
The downside here is that while retrieving association as arrays, I could not take advantage of Symfony\Component\Serializer\Annotation\Groups any more to filter entities properties and had to manually edit result set in order to remove/transform some values.
EDIT
Wilt answer is okay for the original post. I did not expose my issue the right way. I told I want to retrieve Vip infos because I thought it was a good way to get rid of the extra query I talk above. Actually I do not need Vip infos but omitting ->leftJoin('u.vip','v')->addSelect('v') makes doctrine issue the extra query which gather Vip infos! Is there a way to prevent doctrine from executing this query?

There are two kinds of join queries in Doctrine2:
1) Regular joins
2) Fetch joins
Check the documentation chapter 14.2.2. Joins for more details.
So if you want to fetch join vips you should addSelect and leftJoin them inside your query as follows:
$results = $qb
->select('ps')
->addSelect('u')->leftJoin('ps.author','u')
->addSelect('v')->leftJoin('u.vip','v')
->add('where', $qb->expr()->in('ps.id', $ids))
->getQuery()->getResult();
UPDATE
Update after your comment:
I thought including vip in the result set would be the best way to get rid of the extra query
You cannot get rid of the extra query because you cannot lazy load the inverse side of a one-to-one relationship. Refer also to this post for more details:
This is expected behavior. Inverse sides of one-to-one associations can not be lazy, technically. There is no foreign key on the inverse side, hence it is impossible to decide whether to proxy it or not. We must query for the associated object or join it. Note that this only affects inverse sides of single-valued associations, that is, really only the inverse side of bidirectional one-to-one associations.
A solution could be to inverse the relationship so user becomes the owning side of the relationship. I that case you can at least lazy-load Vip inside your User entity. The lazy load problem would move to the Vip side, meaning you could not lazy-load your User in Vip any longer.
Otherwise you could make your query return a Partial object to prevent loading of Vip, but in general you should be very careful with this approach.

Related

How can I select all relations automatically in Doctrine?

I have a rather big number of classes related to each other via various relations (OneToOne, OneToMany, ManyToOne as well as Class- and Joined Inheritance which in turn again have multiple relations.
When I want to select all of a parent class via a simple ->findAll(), I get around 10 extra queries per Entity. I can reduce this number by a limited extend by unsing the a QueryBuilder with ->leftJoin() and ->addSelect(). Some relations do not join because reasons. Still, it gets a pretty nasty long list of addSelect()s. Is there a way to join and select all relations automatically?
Try using "JoinTable" or "JoinColumn"
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/annotations-reference.html#jointable
The automatical join can be achieved by setting the fetch-mode of the associations to eager (e.g. on ManyToOne:
/**
* #ManyToOne(targetEntity="Cart", cascade={"all"}, fetch="EAGER")
*/
, see the docs)
To set the fetch mode programatically, refer to another StackOverflow question.

How to populate additive field via JOIN with Doctrine

Suppose i have an entity with a ManyToOne relation (with EXTRA_LAZY) and obviously a join column.
eg. Article (main entity) -> Author (external entity)
suppose i add a field author_name to the Article that NOT mapped to the ORM and that in certain contexts this field should contain the article->author->name value (usually it can stay null).
When i query a list of articles, i would like to populate automatically that article-author_name without implementing a getter that perform a select on each element of the query result. I would like Doctrine to fetch and hydrate that value in with a simple JOIN in the original query...
I don't want to set fetch mode to EAGER for performance as in my project Author is a really complex entity.
Is this possible?
Thanks
Probably i've found a solution. Don't know if it is the best way to do it but it works.
I've added to the main class a field getter
public getAuthorName() {
return $this->author->name
}
in my context this getter will only be called by a serializer in certain conditions.
In my repository code i have a special method (that i refactored so that any method could implicitly call it) to impose the population of the Article->author field when queried. The method simply use the querybuilder to add LEFT JOIN to the Author class and temporarily set FetchMode to EAGER on Article->author.
At the end a simple repository method could be this
public findAllWithCustomHydration() {
$qb = $this->createQueryBuilder('obj');
$qb->leftJoin("obj.author", "a")
-> addSelect("a"); //add a left join and a select so the query automatically retrive all needed values to populate Article and Author entities
//you can chain left join for nested entities like the following
//->leftJoin("a.address", "a_address")
//-> addSelect("a_address");
$q = $qb->getQuery()
->setFetchMode(Article::class, "author", ClassMetadata::FETCH_EAGER);
//setFetchMode + EAGER tells Doctrine to prepopulate the entites NOW and not when the getter of $article->author is called.
//I don't think that line is strictly required because Doctrine could populate it at later time from the same query result or maybe doctrine automatically set the field as EAGER fetch when a LEFT JOIN is included in the query, but i've not yet tested my code without this line.
return $q->getResult();
}
The con is that you have to customize each query or better use a DQL / SQL / QueryBuilder for each method of the repo but with a good refactoring, for simple inclusion cases, you can write a generic method that inject that join on a field_name array basis.
Hope this help and add your answer if you find a better way.
PS. i've wrote the above code on the fly because now i'm not on my notebook, hope it works at first execution.

Doctrine isDeletable method in entity

I have a Entity in my database (say Member) which has many relationships with other tables (6 relationships to be exact). Some of them I don't want mapped with the ORM (I mean linked to this Entity) because they may have many records (like MemberAccessLogs for example) and some other load many other entities.
Now I want this Member Entity to have an isDeletable method so I can disable exclude button in administration page.
If I where to do this the traditional way, I would have to declare the associations with all the other tables in the entity class, including MemberAccessLogs and I would put the method in it so I could test if these associations are empty.
But AFAIU, I would have to make a fetch (or at least a count) to the association's tables in order check for empty.
Another way would be to fetch the Members I want shown and then make a separate query to check for empty with a low cost exists(select * from table limit 1) in these sub-tables and then populate the isDeletable method in Member programmatically before pass it to Twig.
But I found this solution cumbersome. Anyone has a better way to do this ?
Just for the record: Some people may think this is "premature optimization". I maintain (contrary to some), that you should think ahead when you are programming and don't this this is bad. But I really think this isn't the place to discuss it. Please let's focus on the question asked ok ? :)
Edit
To easily prove that limit 1 is increadibly faster than count, I did a small test in a table in my database that has more than 20 million lines. Here are the results:
select count(*) from loga [20 million+ table]
20678473
1 row(s) fetched - 27023ms
select exists(select null from loga limit 1)
true
1 row(s) fetched - 2ms
I guess 13511,5 times faster is conclusive enough. :D
Extra lazy
You could look into extra-lazy associations.
Basically you map all associations as you normally would, and add fetch="EXTRA_LAZY":
/**
* #Entity
*/
class CmsGroup
{
/**
* #ManyToMany(targetEntity="CmsUser", mappedBy="groups", fetch="EXTRA_LAZY")
*/
public $users;
}
Now Doctrine will not load the complete collection into memory the first time it's accessed, but performs specialized queries to load the parts you actually need at that moment.
So $users->count() (or count($users)) on the collection would trigger a simple count-query in stead of loading the complete collection into memory.
PostLoad
You could use an postLoad event to determine if such an entity is deletable. This postLoad event is called after an entity is constructed by the EntityManager, so when the entity is loaded.
Add an unmapped property ($isDeletable) to the entity that stores whether the entity can be deleted or not.
Create an entity listener that listens to the postLoad event. The listener can have the EntityManager, DBAL Connection, or anything else injected. With that dependency you could perform whatever query you want and use the result to set $isDeletable.
The result is a single additional query when the entity is loaded, after which the entity "knows" whether it's deletable or not.
An example of using the postLoad event can found in a Cookbook entry on the Strategy Pattern
Do note that when the conditions that determine whether it's deletable or not change, the value of $isDeletable could become incorrect. To resolve this issue, you could keep track of those conditions:
Keep track
Add a mapped property ($isDeletable) to the entity that stores whether the entity can be deleted or not. It would probably start with true.
When something is added to an association which would mean that the entity is no longer deletable, set $isDeletable to false.
When something is removed from an association that which would mean that the entity is deletable again, set $isDeletable to true.
In other words: with every change you keep track of whether the entity is deletable or not.
This way you won't need any additional queries at all.
There's a Cookbook entry on aggregate fields that explains this concept very well.

best way inner join symfony2

I have a lot of times 2 or more entities who are related to.
For example:
entity avance(): with attributes: id, userId, questionnaireId, questionId
entity questionnaire: id, name, questionsNbres
entity questions: id, question, responseA, responseB, responseC, correctResponse
When I call an avance entity, it is complicated to find the questionnaire name attribut, I have to do a lot of foreach to find the correct one. The same for all the questions that are related to.
I'm sure that a best solution exist, for example with a inner join, but I don't know how to do this in symfony.
You get two tools that responds to theses problems.
You can setup annotations in yours models to automate data fetching. This is the JoinColumn annotation
However, because the JoinColumn usually over-fetching, my recommandation would be to put in place a custom repository, it allow to create complex query with joins.
In your case, I think you need a left join aka "extends my datas, with another data set".

Doctrine many-to-one association keeps pulling all rows from associated table

I'm attempting to set up a many-to-one relationship between a series of sales memos and transaction records.
/**
* #var TransactionInterface
*
* #ORM\ManyToOne(targetEntity="Twb\Common\Model\Broker\TransactionInterface")
* #ORM\JoinColumn(name="FormNoSeller", referencedColumnName="Form")
*/
private $formnoseller;
/**
* #var TransactionInterface
*
* #ORM\ManyToOne(targetEntity="Twb\Common\Model\Broker\TransactionInterface")
* #ORM\JoinColumn(name="FormNoBuyer", referencedColumnName="Form")
*/
private $formnobuyer;
They are split between two different bundles at the moment ('SalesBundle' and 'BrokerBundle'), and with that in mind I am using interfaces from the SalesMemo entity in SalesBundle to the Transaction entity in BrokerBundle.
For some reason, when I reference either or both of $formnoseller and $formnobuyer in my forms, I notice in dev.log that, after selecting all Transaction rows matching the $formnoseller and/or $formnobuyer fields in the SalesMemos, Doctrine tries to SELECT all rows in the Transaction table (the entity for which TransactionInterface references). This is a bit of a problem, since there is an innumerable amount of rows in the DB, which takes up a lot of memory.
Is there any way to have Doctrine avoid selecting all rows with associations? Or am I even understanding properly how Doctrine does associations? Many thanks for any help.
My understanding of your problem is that you're using an Entity Field Type for $formnoseller and $formnobuyer (or you don't specify the type). Giving the choice to select any élément from the underlying table is the expected behaviour for the Entity Field Type (Used by default for OneToMany relationships)
If you don't whant a select list of all the elements of your table for those Fields, you should use an other form field type. You should also have a look at data transformers in the documentation.
If it were me, I would write a stored procedure and do an inner or outer join as appropriate.
Once upon a time, they called this "client server" code. About 15 years ago, it created such a mess the whole industry moved to n-tier development. I'd like to know how the table joins got placed back into the presentation tier again? ORMs and LINQ-to-SQL are a return to client/server".
If you have to do it this way, do the join in LINQ on the Models. Do not do it with the ORM language.

Categories