Eager Loading
When you load an entity with find, findBy, or findOneBy, ObjectQuel
automatically discovers inbound ManyToOne relationships and joins them into the query.
Joins are generated entirely from relationship declarations on the dependent entity.
Core Rule: ManyToOne Drives Joins, InverseOf Drives Hydration
ObjectQuel uses ManyToOne as the single source of truth for join generation. When you load a
UserEntity, the ORM examines entity metadata for ManyToOne declarations targeting it,
and joins each one that is not marked LAZY. InverseOf is the complement: it tells
the hydrator which property on the target entity should receive those joined results.
Example: Both Sides of the Relationship
Consider a user with multiple posts:
class UserEntity {
// ...
/**
* @Orm\InverseOf(targetEntity=PostEntity::class, relation="user")
*/
public CollectionInterface $posts;
}
class PostEntity {
// ...
/**
* @Orm\ManyToOne(
* targetEntity=UserEntity::class,
* referencedColumn="id", // primary key property on the target entity
* fetch="EAGER"
* )
*/
public ?UserEntity $user = null;
}
Note: referencedColumn names the primary key property on the target entity the foreign key
points to. Omit it to default to the target's primary key.
Loading a UserEntity now automatically joins PostEntity and populates
$user->posts:
$user = $entityManager->find(UserEntity::class, 1);
$posts = $user->posts; // already hydrated — no additional query
Generated SQL
The join is emitted as a LEFT JOIN, since a user may have no posts:
SELECT DISTINCT `main`.`id`, `main`.`username`, ...,
`r0`.`id`, `r0`.`title`, ...
FROM `users` AS `main`
LEFT JOIN `posts` AS `r0` ON `r0`.`user_id` = `main`.`id`
WHERE `main`.`id` = :id
Controlling Fetch Mode
Each ManyToOne and OneToOne relation has a fetch parameter
that controls whether it is included in the initial query or deferred until first access:
// Joined automatically — included in the initial query as a LEFT JOIN
/**
* @Orm\ManyToOne(targetEntity=UserEntity::class, referencedColumn="id", fetch="EAGER")
*/
public ?UserEntity $user = null;
// Deferred — resolved via a proxy on first property access
/**
* @Orm\ManyToOne(targetEntity=CategoryEntity::class, referencedColumn="id", fetch="LAZY")
*/
public ?CategoryEntity $category = null;
Because eager joins are discovered automatically, adding new dependent entities increases what gets loaded.
Use fetch="LAZY" for relationships that should not be included by default.
Retrieval Methods
Eager loading behavior is identical for all entity retrieval methods —
find(), findBy(), and findOneBy() all go through the same
query pipeline. See Core Classes for full method documentation.
OneToOne Relations
Owning-side OneToOne relations participate in eager loading exactly like ManyToOne.
The owning side holds the foreign key column, identified by referencedColumn on the annotation.
class PostEntity {
// ...
// Owning side — holds the FK column, generates a join when EAGER
/**
* @Orm\OneToOne(targetEntity=MetaEntity::class, referencedColumn="id", fetch="EAGER")
*/
public ?MetaEntity $meta = null;
}