Draftsman
Draftsman
Toggle sidebar

Eloquent Relationships in Draftsman

How Draftsman draws every Eloquent relationship type, illustrated with a small team-management application.

Edges connect the actual columns involved: the foreign key row on one card to the key row it references on the other. With crow's-foot notation enabled, the end caps carry the semantics — a bar for one, a fork for many, a circle for optional. Pivot and intermediate tables are drawn as first-class cards, and Draftsman suppresses shortcut edges whenever the path is already visible through such a card — so a many-to-many reads as what it is at the schema level: two one-to-many relationships meeting at a pivot.

Each section links to the relevant chapter of the Laravel documentation.

One to One — hasOne

A User has one Profile; the profiles table carries the user_id foreign key.

public function profile(): HasOne
{
    return $this->hasOne(Profile::class);
}
A hasOne relationship: User connected to Profile via user_id

Laravel docs: One to One

Inverse — belongsTo

The child side of a one-to-one or one-to-many: the model holding the foreign key points back at its parent. Here a User belongs to their current Team through the current_team_id column — note the optional (circle) cap, since the column is nullable.

public function currentTeam(): BelongsTo
{
    return $this->belongsTo(Team::class, 'current_team_id');
}
A belongsTo relationship: User pointing at Team via current_team_id

Laravel docs: One to Many (Inverse) / Belongs To

One to Many — hasMany

A Team has many TeamInvitations. The fork cap on the invitation side reads "many": one team row, any number of invitation rows.

public function invitations(): HasMany
{
    return $this->hasMany(TeamInvitation::class);
}
A hasMany relationship: Team connected to many TeamInvitations

Laravel docs: One to Many

Many to Many — belongsToMany

A Team has many members and a User belongs to many teams, joined through the team_members pivot. This app promotes the pivot to a real model (Membership, via using()), so Draftsman draws it as a card of its own — and the many-to-many appears as what it is in the schema: each pivot row pointing at one user and one team.

public function members(): BelongsToMany
{
    return $this->belongsToMany(User::class, 'team_members', 'team_id', 'user_id')
        ->using(Membership::class)
        ->withPivot(['role']);
}
A belongsToMany relationship: User and Team joined through the Membership pivot

Laravel docs: Many to Many

Has One Through — hasOneThrough

A Membership reaches one Profile through the User it belongs to. Draftsman shows the path itself: membership to user, user to profile.

public function userProfile(): HasOneThrough
{
    return $this->hasOneThrough(Profile::class, User::class, 'id', 'user_id', 'user_id', 'id');
}
A hasOneThrough relationship: Membership reaching Profile through User

Laravel docs: Has One Through

Has Many Through — hasManyThrough

A User reaches the teams they own through their Membership rows. The picture is the same two-hop path a many-to-many makes — the through model in the middle carries a foreign key to each end.

public function ownedTeams(): HasManyThrough
{
    return $this->hasManyThrough(
        Team::class,
        Membership::class,
        'user_id',
        'id',
        'id',
        'team_id',
    )->where('team_members.role', TeamRole::Owner->value);
}
A hasManyThrough relationship: User reaching Teams through Membership

Laravel docs: Has Many Through

One to One (Polymorphic) — morphOne

A Team has one Note, but notes can belong to more than one kind of parent: the notable_type column (marked with a tilde) records which model each note belongs to, and notable_id points at it.

public function note(): MorphOne
{
    return $this->morphOne(Note::class, 'notable');
}
A morphOne relationship: Team connected to Note via notable_id and notable_type

Laravel docs: One to One (Polymorphic)

One to Many (Polymorphic) — morphMany

The same notes table serves a one-to-many from User: a user has many notes through the identical notable columns.

public function notes(): MorphMany
{
    return $this->morphMany(Note::class, 'notable');
}
A morphMany relationship: User connected to many Notes

Laravel docs: One to Many (Polymorphic)

Polymorphic Parent — morphTo

From the child's side, Note::notable() resolves to whichever model the type column names. On the canvas that is one child with an edge to every parent it can reach — here both User and Team.

public function notable(): MorphTo
{
    return $this->morphTo();
}
A morphTo relationship: Note connected to both of its possible parents, User and Team

Laravel docs: Polymorphic Relationships

Many to Many (Polymorphic) — morphToMany

Tags attach to teams (or anything else) through the taggables pivot, whose taggable_type column records what each row is attached to. Draftsman draws the generic pivot as a card, so the polymorphic many-to-many reads just like the plain one — two paths meeting at a pivot. The inverse side is declared with morphedByMany.

public function tags(): MorphToMany
{
    return $this->morphToMany(Tag::class, 'taggable');
}

// The inverse, on Tag:
public function teams(): MorphToMany
{
    return $this->morphedByMany(Team::class, 'taggable');
}
A morphToMany relationship: Team and Tag joined through the taggables pivot

Laravel docs: Many to Many (Polymorphic)