22

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

5 Answers 5

17

Use hint HINT_FORCE_PARTIAL_LOAD to avoid lazy-loading.

...
$qb->getQuery()->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true);
2
  • 4
    This of course exposes another problem - if you want just to get a key value (id for instance) of entity which wasn't joined, it'll raise an exception. Commented Jun 9, 2013 at 10:00
  • But what if you DO want the lazy loading...
    – jave.web
    Commented Aug 21, 2023 at 9:05
9

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

2

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

0

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:

  1. "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".

  2. "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;
    }
}

1
  • Please, do not post only code. Try to explain your solution.
    – Fabrizio
    Commented Oct 26, 2021 at 14:26
-4

You can enable extra lazy loading of associations using fetch="EXTRA_LAZY" in your annotations.

* @ORM\OneToOne(targetEntity="Users", inversedBy="userSetting", fetch="EXTRA_LAZY")
7
  • 5
    still it joins the tables after adding extra_lazy. I had tried that before also but didnt work
    – sonam
    Commented Jan 21, 2013 at 3:08
  • Does your server use APC? After updating your annotation make sure your apc cache is cleared (restart apache apache if you have to). I've had issues with certain doctrine annotations not taking affect unless I restart apache.
    – lifo
    Commented Jan 21, 2013 at 3:13
  • i had previously enabled APC but became a headache during development so APC is not enabled
    – sonam
    Commented Jan 21, 2013 at 3:16
  • Well, another option would be to use DQL and create your query like this instead: SELECT u FROM TestBundle:users u WHERE ... That way you're sure not to get a join.
    – lifo
    Commented Jan 21, 2013 at 3:19
  • 5
    I had the exact same problem with a OneToOne association doing multiple separate queries for each associated entity (even when no property within that entity was accessed). Adding fetch="EXTRA_LAZY" did not solve the problem for me either (the docs explain why). See more details on what worked. Commented Nov 24, 2014 at 23:24

Not the answer you're looking for? Browse other questions tagged or ask your own question.