Reputation: 3760
I am having a hard time finding the correct result for this.
I have one to one mapping. There are two tables:
/**
* @ORM\Table(name="users")
* @ORM\Entity
*/
class Users {
/**
* @ORM\OneToOne(targetEntity="UsersSettings", mappedBy="user",cascade={"persist"})
*/
private $userSetting;
//getters and setters
}
/**
* @ORM\Table(name="notifications_settings")
* @ORM\Entity
*/
class UsersSettings {
/**
* @var Users
*
* @ORM\OneToOne(targetEntity="Users", inversedBy="userSetting")
* @ORM\JoinColumns({
* @ORM\JoinColumn(name="user_id", referencedColumnName="id")
* })
*/
private $user;
}
Whenever I fetch entity one as below:
$q = $this
->createQueryBuilder('u')
->select('u, r')
->leftJoin('u.roles', 'r')
->where('u.username = :username OR u.email = :email')
->setParameter('username', $username)
->setParameter('email', $username)
->getQuery();
Doctrine immediately performs join to usersSettings entity which I dont want:
SELECT t0.id AS id1, t0.username AS username2, t0.email AS email3, t0.password AS password4, t29.id AS id30, t29.is_notify_by_email AS is_notify_by_email31, t29.user_id AS user_id32 FROM users t0 LEFT JOIN users_settings t29 ON t29.user_id = t0.id WHERE t0.id = ?
Other types of mapping like OneToMany
and ManyToOne
performs lazy loading but in case of one to one mapping, I could not configure to lazy load. How can I lazy load this relation? I am using doctrine 2.3 and Symfony 2.1
Upvotes: 23
Views: 26492
Reputation: 11
This problem has been known at least since 21 Feb 2010 - https://github.com/doctrine/orm/issues/4389. And we have 7 ways to solve, first 6 described here - https://stackoverflow.com/a/34353840/5735549
The cons of most popular solutions:
"Change the owning and inverse sides of the relationship" (https://developer.happyr.com/choose-owning-side-in-onetoone-relation) - will produce extra id at the owning side. For example user`s table will have non necessary columns like user_setting_id, user_cart_id, user_notification_settings_id etc. And it will look even more wrong if you decide to use the same id strategy for user and user_settings table. The row at users table will be look like "id=123, user_setting_id=123, user_cart_id=123, ...=123".
"Change inverse side to be OneToMany instead of OneToOne" - if you perform this way doctrine schema validator (console doctrine:schema:validate) will produce error like "If association UserSettings#user is one-to-one, then the inversed side User#userSettings has to be one-to-one as well" and symfony profiler will show this error at "doctrine / entities mapping" section for every request it make you more tolerance to the errors and learn to ignore them.
Another solution for bi-directional OneToOne association with lazy loading support is "OneToMany/ManyToOne trick" - allows you lazy load related entities, store id at the expected side and have no schema validation errors.
Here is an example for user and userSetting with same id strategy:
class User
{
#[ORM\OneToMany(mappedBy: 'user', targetEntity: UserSettings::class, cascade: ['persist', 'remove'], fetch: 'LAZY', orphanRemoval: true)]
#[ORM\JoinColumn(name: 'id', referencedColumnName: 'id', onDelete: 'CASCADE')]
private Collection $userSettings;
public function __construct()
{
$this->userSettings = new ArrayCollection;
}
public function setUserSettings(UserSettings $setting): void
{
$this->userSettings = new ArrayCollection([$setting]);
}
public function getUserSettings(): ?UserSettings
{
return $this->userSettings->isEmpty() ? null : $this->userSettings->first();
}
}
class UserSettings
{
#[ORM\Id]
#[ORM\ManyToOne(targetEntity: User::class, inversedBy: 'userSettings')]
#[ORM\JoinColumn(name: 'id', referencedColumnName: 'id', onDelete: 'CASCADE')]
private User $user;
public function __construct(User $user)
{
$this->user = $user;
$this->user->setUserSettings($this);
}
public function getUser(): User
{
$this->user->setUserSettings($this);
return $this->user;
}
}
Upvotes: 1
Reputation: 79
Solution 1: you can change the owning and inverse sides of the relationship
Solution 2: you can declare the mapping in inverse side as oneToMany instead of oneToOne and then in setter(if you need a setter) replace old collection with a new one containing only one element and in getter return the first element of collection(which is the only element), something like this:
class Users {
/**
*
@ORM\OneToMany(targetEntity="UsersSettings", mappedBy="user")
*/
private $userSetting;
public function setUserSettings(UserSettings $setting): void
{
$setting->setUser($this);
$this->userSettings = new ArrayCollection([$setting]);
}
public function getUserSettings(): UserSettings
{
return $this->userSettings->first();
}
}
in this case UserSettings entity remains intact
Upvotes: 2
Reputation: 91
Inverse sides of one-to-one associations can not be lazy.
Explanation:
This is expected behavior. 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.
Why we cannot create proxy without FK?
It is pretty simple, in a one-to-many association where the one-side is the inverse side, it holds a collection. The collection may be empty or not but there can always be a collection so it easy and correct to always put a proxy collection there.
If you have a single-valued side that is the inverse side, how can you decide whether to put a proxy object there or not? Without seeing the foreign key you have no way to distinguish between: There is no associated object (and thus putting a proxy object in place would by simply wrong) or there is one and of which type it is, if inheritance is involved (since putting a proxy of the wrong type in is also wrong).
See discusion on https://github.com/doctrine/orm/issues/4389
Upvotes: 9
Reputation: 2893
You can enable extra lazy loading of associations using fetch="EXTRA_LAZY"
in your annotations.
* @ORM\OneToOne(targetEntity="Users", inversedBy="userSetting", fetch="EXTRA_LAZY")
Upvotes: -4
Reputation: 200
Use hint HINT_FORCE_PARTIAL_LOAD to avoid lazy-loading.
...
$qb->getQuery()->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true);
Upvotes: 18