NOTE: Topic is lengthy but detailed and may come in handy if you use Doctrine2 and oneToOne relationships.
Recently I came across a problem in Doctrine:
I created User and UserData objects with oneToOne bidirectional relationship:
User:
...
oneToOne:
userdata:
targetEntity: UserData
mappedBy: user
UserData:
...
oneToOne:
user:
targetEntity: User
inversedBy: userdata
So UserData was the owning side with user_id column in it:
user: id, ...
userdata: id, user_id, ...
This created a problem, where every time you fetch a User object (Single user, collection of user or collection of other object with user joined on it) Doctrine would lazy load a UserObject for each User.
Issue described here:
How to prevent Doctrine from lazy loading one to one relationsip?
http://groups.google.com/group/doctrine-user/browse_thread/thread/7e421a2b189f0ea7
https://github.com/doctrine/doctrine2/issues/4389
Proposed solution described here:
https://github.com/doctrine/doctrine2/issues/2364
So there are 3 ways around this:
Wait and see if proposed solution is addressed in Doctrine and fixed in future releases (may not happen)
Manually left join UserData to User in every query (still waste of resources, dont need UserData)
Switch inverse side and make User the owning side.
I decided to go with #3. So my schema relationship now looks like this:
User:
...
oneToOne:
userdata:
targetEntity: UserData
inversedBy: user
UserData:
...
oneToOne:
user:
targetEntity: User
mappedBy: userdata
This means that my tables now look like this:
user: id, userdata_id, ...
userdata: id, ...
I decided that instead of having Userdata.id autoincremented, I'll set it manually and match it with user.id. This means that UserData.id will always match user.id.
Question Can I use user.id (a primary autoincremented key) as joinColum instead of userdata_id since they will always have the same value? Do you see any potential issues with this way of doing things?
Any other tips or opinions about this issue is greatly welcomed and appreciated.
You could also force partial objects, to get rid off lazy-loading:
use Doctrine\ORM\Query;
//...
$query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true);
This is a known issue for OneToOne associations. There is a github discussion about this that is worth reading. A solution (pull request) was proposed and rejected.
Recommendation
Your question suggests the same solution proposed by the contributors to Doctrine: change the owning side of the relationship.
Other Options Explored
I had a similar problem with an entity called Version that had a OneToOne bidirectional relationship with Settings. Every time I queried Version (say for 10 specific version records), Doctrine would do additional queries for the joined Settings (as if it was Lazy Loading these entities). This happened, even though I did not reference Settings anywhere, e.g. $Version->getSettings()->getSomeProperty().
Manual JOIN
The only "solution" (hack) that works for me is to manually included a JOIN for this Settings entity every time I did a query on Version. But since I don't need the extra entity (in this case), that would still be a single extra unnecessary query, every time I query this table in different ways.
Extra Lazy
Based on other suggestions, I thought that if I explicitly specified this relationship as "extra lazy" it would work, e.g.
/**
* #ManyToMany(targetEntity="Settings", mappedBy="version", fetch="EXTRA_LAZY")
*/
But this doesn't affect hydration. See the Doctrine Docs for more details about what EXTRA_LAZY does.
Hydration Type: HYDRATE_ARRAY
What helped in my case (when I didn't need an entity), was to specify that I wanted to fetch as an array (rather than object).
$query = $queryBuilder->getQuery();
$query->getResult(Query:HYDRATE_ARRAY);
This returns an array, and as a result it doesn't lazy load the OneToOne associations. However, in other contexts where I need the entity object, I had to explicitly JOIN the entity (despite not wanting it).
My workaround was to turn the OneToOne relationship into a ManyToOne (with an associated ArrayCollection), with a custom getter and setter which only sets and returns the 0-th element of the collection:
/**
* Set pref
*
* #param \MyBundle\Entity\Pref $pref
* #return Employee
*/
public function setPref(\MyBundle\Entity\Pref $pref = null) {
$this->pref[0] = $pref;
return $this;
}
/**
* Get pref
*
* #return \MyBundle\Entity\Pref
*/
public function getPref() {
return $this->pref[0];
}
These methods can (seemingly) be used the same as the ones created by a OneToOne relationship.
doctrine:generate:entities still creates the normal "add" and "remove" methods, but they can be ignored.
Note: I only recently started using these and currently only to read from the database. I don't know of any side effects from this workaround. I'll update this answer if I notice any.
This is admittedly an ugly hack. I hope Doctrine fixes this soon so I can go back to using proper OneToOne relationships.
I came across this same problem and remember that the symblog tutorial gave an example of how to reduce the lazy loading by explicitly add left joins on the tables that you do not need. It seems strange to include tables on a join when you do not even want that data at all, but this way you will reduce all of those extra queries down to 1 and it does run faster.
Search for lazy loading - about 1/5 of the way down
http://tutorial.symblog.co.uk/docs/customising-the-view-more-with-twig.html
To fix this for the user/userdata issue try adding this to the user repository and use to whenever you need to get all users even if you do not want userdata. It can be further enhanced by selecting partial:
->select('partial p.{user_id,name,}')
public function getAll($limit = 500) {
$qb = $this->createQueryBuilder('u')
->select('u', 'd')
->leftJoin('p.userdata', 'd')
if (false === is_null($limit))
$qb->setMaxResults($limit);
return $qb->getQuery()->getResult();
}
Related
In my app, I have a few entities that share some common properties. Example entities:
Image
Video
Event
ForumPost
I want these classes to share some common functions, such as:
Have a list of comments, as well as keep counts of comments
Voting
List of subscribers and subscriber counts
View counts
etc..
I really would not want duplicate these functions for each of the entities above. So... I've been reading about Doctrine inheritance mapping and I've come up with an idea that "Class Table Inheritance" is the most elegant way to solve this. The plan was as follows:
Create a parent Post entity
/**
* #ORM\Entity
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="type", type="string")
*/
class Post
{
protected $likeCount;
protected $subscriberCount;
protected $subscribers;
// ...
}
Have many-to-one relations of likes, comments, votes, etc to Post entity:
class PostLike
{
/**
* #ORM\ManyToOne(targetEntity="Post")
* #ORM\JoinColumn(name="post_id", referencedColumnName="id", nullable=false)
*/
protected $post;
}
Have the Image, Video, etc entity extend Post:
class Video extends Post
{
// Post properties become available here
}
All worked like charm until I looked at the actual queries Doctrine is executing. The problem is: whenever I select a PostLike, doctrine will eager-load Post entity as well, which will in turn join all the child tables as well. So for example, a simple PostLike remove operation triggers a join on 4 tables and will actually join even more tables as my hierarchy grows.
If I have 10 entities extending Post, that would make a join on 10 tables. I am not a fan of premature optimization, but this just doesn't sound like a good plan.
In Doctrine documentation, this behavior is actually mentioned at "6.3.2. Performance impact".
Finally, my question: What are my other options to create reusable tables with doctrine?
I was thinking to do a One-To-One mapping (Post entity being a property of the Video, as well as the Image, etc), but I've read in a few blogs that One-to-One should be avoided with doctrine also for performance reasons.
thanks!
Edit and ansewer to Raphaƫl MaliƩ regarding Mapped Superclasses:
I am aware that I can use Mapped Superclass and Trait features to avoid code duplication. However, these features allow me to reuse the code but don't allow me to reuse sql tables. Here is an example:
Say I want Image, Video and Event entities to have comments. I can create one Mapped Superclass AbstractComment and then extend it in ImageComment, VideoComment, EventComment. Next, I want to add comment votes. Since I can't reuse same sql table, I will need to create ImageCommentVote, VideoCommentVote, EventCommentVote, etc. Then I want users to be able to report abusive comments. There goes again: ImageCommentReport, VideoCommentReport, EventCommentReport. You get the idea. Every feature that I want to add to comment will need a separate table for each entity like image, video, etc. Lots of tables.
The reasons why I prefer to use centralized approach instead of using traits with many tables:
Easier administration. I can easily search/edit/delete comments of the entire app by sending a query to one table.
The same controller can handle actions for multiple entities, no need to create one abstract and many concrete controllers
Showing user notifications about new comments/likes etc is MUCH easier when actions are centralized.
I am also aware of the drawbacks of such system, but I can live with them for now. My question is how to get Doctrine to do what I want without actually joining everything when not necessary.
In my opinion you are doing wrong. Your entities have nothing in common, except some functionalities, so there is no reason to use single table or class table inheritance.
You should use Traits: http://php.net/manual/en/language.oop5.traits.php
It works with Doctrine. You define one or several traits with some properties / methods with annotations, and then you import these traits in your entities.
You can also use a Mapped Superclass : http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html#mapped-superclasses which is quite close to your example, but with a different entity annotation.
After some thought, your decision really depends on the size of your database. If it is big the performance hit of the Class Tabel approach might be unacceptable. But if your tables are small, the performance hit is minor and you can use all the advantages of Class Table Inheritance, which you discussed yourself in your post.
I am trying to understand Symfony2, but there is something that is not making sense to me. I reversed engineered an existing database to produce my entities, so maybe thats the problem.
I have a table called availability_alert, nothing special, a few fields including an id (which is the primary key). This table has no link to anything else.
I then have a second table called booking_class, once again nothing special, but it does have the field $availabilityAlert which links to the availability_alerts tables id.
In essence, an Availability Alert can have one or many Booking Class.
Now in my booking class entity, I have the link
/**
* #var \AlertBundle\Entity\AvailabilityAlert
*
* #ORM\ManyToOne(targetEntity="AlertBundle\Entity\AvailabilityAlert")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="availability_alert_id", referencedColumnName="id")
* })
*/
private $availabilityAlert;
So this all looks ok. The setter for it though came out like so
public function setAvailabilityAlert(\AlertBundle\Entity\AvailabilityAlert $availabilityAlert = null)
{
$this->availabilityAlert = $availabilityAlert;
return $this;
}
So that appears to take an AvailabilityAlert Object as a parameter, not the AvailabilityAlert id?
So with the above, I am presuming doing something like this in my controller will not work?
$alert = new AvailabilityAlert();
$bookingClass = new BookingClass();
$bookingClass->setAvailabilityAlert($alert->getId());
Could someone give me some advice on whether things are correct here, or if I should be doing something else? Essentially AvailabilityAlert should be a static table, other tables link to this.
Any advice appreciated.
Thanks
Yes this is correct.
In the Doctrine world, you are working with objects, not with integers or strings, when it comes to relationships between Entities.
You can read more about Doctrine 2 relationships here: http://symfony.com/doc/current/book/doctrine.html#entity-relationships-associations
That's correct. You don't use integers, strings. It's because the relationships are given in entity annotations and Doctrine uses them to figure out what is used exactly to reference the one object from the other. This even let you change how objects reference themselves - for example you change the id to a compound primary key in AvailabilityAlert class and your code wouldn't change much except for the annotations.
I have 2 entities, User and Profile. Profile has in-symfony relation with User, but there is no in-database relation (no foreign key, no cascade) - only simple int column named user_id and nothing more.
Problem is obvious: when i delete user - associated profiles persists, but their user_id points to non-existing user row.
Since I use in-symfony relations when i fetch profile from database it fetches also related user entity. I expected that if there is no row with specific ID, it would just leave null or at least throw an exception or something.
Problem is that symfony creates empty User entity object with only id set. rest of its fields are null.
I know solution would be to create FK, constraints etc ... but I'm not allowed to touch anything in database schema.
How can I manage this problem ? I could even leave those empty object if only i had simple way to determine if they exist in database inside TWIG - so i would know if i can display {{ profile.user.email }} for example.
Fast and dirty solution, as you ask, is to use this test: http://twig.sensiolabs.org/doc/tests/defined.html
But I strongly recommend to rework your entity relations.
Found solution: its fetch: EAGER set to problematic mapping in doctrine.
By default doctrine uses LAZY fetching what results in using Proxy classes generated by doctrine for related entity. That class is almost same as real entity class. Difference is inside getter methods that before returning value performs fetching entity from database.
At this point, when you call getter on such proxy, doctrine tries to find entity in database using its ID, and since it doesn't find anything it throws exception.
When using EAGER fetching doctrine performs fetching of related entities on the same time when it fetches main entity and if it doesn't find it then sets null on relation field.
Hi Folks i have a question regarding implementing One-To-One in Entity FosUserBundle.
User Entity Has One To One Mapping With Profile Entity. I have override the basic RegistrationFormType as per shown in the FOSUserBundle's documentation. record is also saved in both table. but mapping entities show me blank data. Please find respected gist file for same.
UserEntity Gist- https://gist.github.com/ajaypatelbardoli/2f0c81cbdf3b0d136785
ProfileEntity Gist - https://gist.github.com/ajaypatelbardoli/fd02025fd338ed90545e
ProfileFormType gist - https://gist.github.com/ajaypatelbardoli/18ef99a3d0bd1198debc
RegistratonFormType Gist - https://gist.github.com/ajaypatelbardoli/09c047425032391c2445
The problem with your implementation is that you do not update the owning side of the bidirectional association. The Doctrine documentation explicitly states:
See how the foreign key is defined on the owning side of the relation, the table Cart.
In your case the owning side is Profile which you can update automatically in setUserId() as folows:
public function setUserId(\XXXX\Bundle\UserBundle\Entity\User $userId = null)
{
$this->userId = $userId;
$userId->setProfile($this);
return $this;
}
You can access the data from both sides of the relation without problems, Doctrine will look up the corresponding entries.
I have a site with many Users and many Designs, each of which was created by one User. Users can also select other Designs as a favorite, and can go to a page that shows their favorites. So, there's a many-to-many relationship between Users and Designs termed "Favorite".
I'm wondering what the best way is to implement this Favorite relationship. I'm only ever looking for Favorites for a particular user, and only on one page. It seems that Doctrine defaults to returning an object with all its related objects, so I'm concerned that by adding this relationship I'll suddenly get a ton of extra objects added to all my Design list API calls.
Advice on how to set this up? I have repositories in place for all entities.
I'll share an implementation I have between Tags and Posts (Posts can have multiple tags, and each tag can be associated with more than one post). I think it's similar enough to get you started. It's super simple to setup:
In the Post entity, I have this:
/**
* #ManyToMany(targetEntity="Tag", inversedBy="posts")
* #JoinTable(name="activity_relationship",
* joinColumns={#JoinColumn(name="object", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="subject", referencedColumnName="id")}
* )
*/
protected $tags;
In the Tag entity, I have this:
/**
* #ManyToMany(targetEntity="Post", mappedBy="tags")
*/
protected $posts;
There no entity necessary for the joining table.
In the case of posts carrying tags, it's enough for me to just access the tags property since there will always be a small amount, but each tag is likely to have tons and tons of posts, so I want some LIMIT and OFFSET controls over this. For this reason, as Cerad mentioned above, you don't need to use the $posts variable directly, instead I query that separately using dql.
SELECT t
FROM Post p
JOIN p.tags t
<add whatever other stuff you want>
Whichever way you prefer to execute that query, you will probably have a query object from Doctrine, to apply the LIMIT and OFFSET, just do
$query->setMaxResults($limit);
$query->setFirstResult($offset);