Skip to content

Relationships

This content is for v1.0. Switch to the latest version for up-to-date documentation.

Define associations between mapped entities, from simple ownership to polymorphic links.

#[OneToOne]

Maps one entity to exactly one related entity.

#[OneToMany]

Maps one parent to many children.

#[ManyToOne]

Maps many children to one parent.

#[ManyToMany]

Maps both sides through a pivot table.

Articulate also exposes polymorphic relation attributes: MorphTo, MorphOne, MorphMany, MorphToMany, MorphedByMany.

use Articulate\Attributes\Relations\MorphedByMany;
use Articulate\Attributes\Relations\MorphToMany;
use Articulate\Attributes\Relations\MorphTypeRegistry;
use Articulate\Modules\EntityManager\Collection;
MorphTypeRegistry::register(TaggableOrder::class, 'order');
MorphTypeRegistry::register(TaggableCustomer::class, 'customer');
#[Entity(tableName: 'orders')]
class TaggableOrder
{
#[PrimaryKey]
public int $id;
#[MorphToMany(targetEntity: Tag::class, name: 'taggable', targetIdColumn: 'tag_id')]
public Collection $tags;
}
#[Entity(tableName: 'tags')]
class Tag
{
#[PrimaryKey]
public int $id;
#[MorphedByMany(targetEntity: TaggableOrder::class, name: 'taggable', targetIdColumn: 'tag_id')]
public array $orders;
}
$order->tags->add($tag);
$em->flush(); // inserts into taggables using taggable_type = 'order'

The pivot table uses {name}_type, {name}_id, and the target id column:

taggables(taggable_type, taggable_id, tag_id)

Registered morph aliases are used for owning and inverse relation loading. If no alias is registered, Articulate falls back to storing and loading the full entity class name. When generating a polymorphic pivot schema, Articulate uses the composite key (taggable_type, taggable_id, tag_id) as the relation identity — a separate technical id column is not required for collection loading or persistence.

Relations can be loaded during hydration depending on metadata and query path. Use loadRelation($entity, $relationName) when a relation needs to be loaded explicitly.

#[ManyToOne(targetEntity: Customer::class, column: 'customer_id', nullable: false)]
public ?Customer $customer = null;
#[OneToMany(ownedBy: 'order', targetEntity: OrderItem::class, lazy: true)]
public array|Collection $items = [];
  • Map a foreign key column either as a relation or as a scalar property on the same entity, not both.
  • Prefer explicit loadRelation() when relation loading behavior is the concept being shown or when lazy proxies aren’t safe to flush — see Known Limitations.
  • Polymorphic many-to-many relation loading via loadRelation() currently has gaps for MorphToMany / MorphedByMany — query the pivot table directly as a workaround.