Entity Bridges are ObjectQuel's approach to many-to-many relationships. Instead of hiding join tables, ObjectQuel makes them explicit, first-class entities that you can query, extend, and control.
An Entity Bridge is a regular entity that connects two other entities in a many-to-many relationship. While some ORMs hide join tables, ObjectQuel makes them visible and manageable:
/**
* @Orm\Table(name="product_tags")
* @Orm\EntityBridge
*/
class ProductTagEntity {
/**
* @Orm\Column(name="product_id", type="integer", primary_key=true)
*/
private int $productId;
/**
* @Orm\Column(name="tag_id", type="integer", primary_key=true)
*/
private int $tagId;
/**
* @Orm\ManyToOne(targetEntity="ProductEntity")
*/
private ProductEntity $product;
/**
* @Orm\ManyToOne(targetEntity="TagEntity")
*/
private TagEntity $tag;
}
The @Orm\EntityBridge annotation marks the entity as a bridge, helping ObjectQuel optimize queries.
Making bridge entities explicit provides several advantages:
Bridge entities can store relationship-specific data:
/**
* @Orm\Table(name="product_tags")
* @Orm\EntityBridge
*/
class ProductTagEntity {
/**
* @Orm\Column(name="product_id", type="integer", primary_key=true)
*/
private int $productId;
/**
* @Orm\Column(name="tag_id", type="integer", primary_key=true)
*/
private int $tagId;
/**
* @Orm\ManyToOne(targetEntity="ProductEntity")
*/
private ProductEntity $product;
/**
* @Orm\ManyToOne(targetEntity="TagEntity")
*/
private TagEntity $tag;
/**
* @Orm\Column(name="assigned_at", type="datetime")
*/
private \DateTime $assignedAt;
/**
* @Orm\Column(name="assigned_by", type="integer")
*/
private int $assignedBy;
/**
* @Orm\Column(name="priority", type="integer")
*/
private int $priority = 0;
// Business logic on the relationship
public function isRecentlyAssigned(): bool {
return $this->assignedAt > new \DateTime('-7 days');
}
public function wasAssignedBy(int $userId): bool {
return $this->assignedBy === $userId;
}
}
Bridge entities are used like any other entity:
// Creating a relationship
$bridge = new ProductTagEntity();
$bridge->setProduct($product);
$bridge->setTag($tag);
$bridge->setAssignedAt(new \DateTime());
$bridge->setAssignedBy($userId);
$bridge->setPriority(5);
$entityManager->persist($bridge);
$entityManager->flush();
// Removing a relationship
$entityManager->remove($bridge);
$entityManager->flush();
// Finding relationships
$assignments = $entityManager->executeQuery("
range of pt is App\\Entity\\ProductTagEntity
range of p is App\\Entity\\ProductEntity via pt.product
range of t is App\\Entity\\TagEntity via pt.tag
retrieve (pt, p.name, t.name)
where pt.assignedBy = :userId
sort by pt.assignedAt desc
", ['userId' => 123]);
You can query bridge entities directly to analyze relationships:
// Find all products tagged with a specific tag
$results = $entityManager->executeQuery("
range of pt is App\\Entity\\ProductTagEntity
range of p is App\\Entity\\ProductEntity via pt.product
retrieve (p)
where pt.tagId = :tagId
", ['tagId' => 5]);
// Find recent tag assignments
$results = $entityManager->executeQuery("
range of pt is App\\Entity\\ProductTagEntity
retrieve (pt)
where pt.assignedAt > :date
sort by pt.assignedAt desc
", ['date' => '2024-01-01']);
// Find all tags for a product
$results = $entityManager->executeQuery("
range of pt is App\\Entity\\ProductTagEntity
range of t is App\\Entity\\TagEntity via pt.tag
retrieve (t)
where pt.productId = :productId
", ['productId' => 123]);