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.

explanation

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;
}