in

Understanding Model Relationships in Laravel Eloquent

default image

Laravel‘s Eloquent ORM makes it easy to define relationships between models, allowing you to query and manipulate related data efficiently. However, Eloquent relationships can be confusing at first, especially when coming from a background without prior ORM experience.

In this comprehensive guide, we‘ll cover the fundamentals of Eloquent relationships and walk through examples of the main relationship types. By the end, you‘ll have a solid grasp of how to effectively model relational data with Eloquent.

Why Learn Eloquent Relationships?

Before diving in, let‘s review why mastering Eloquent relationships is so valuable:

  • Simplified code – Relate models together to avoid repetitive queries and take advantage of Eloquent conveniences like eager loading. This keeps your code DRY and clean.

  • Powerful querying – Easily fetch related model data with helpful methods like with(), has(), and more. Build complex queries without raw SQL.

  • Better performance – Eager load related data efficiently in a single query using with(), avoiding the N+1 query problem.

  • Better modeling – Structure your models and relations properly to reflect real-world relationships and enforce data integrity.

  • Full ORM capabilities – Fully leverage Eloquent as an ORM by effectively relating and querying model data.

In summary, proficiency with Eloquent relationships allows you to write simpler, more readable code while efficiently querying your database. Let‘s get started!

Relationship Fundamentals

Before defining Eloquent relationships, it‘s important to understand how relational databases represent relationships at a fundamental level.

In relational databases like MySQL, relationships are represented by foreign keys that reference the primary key of related rows. For example:

CREATE TABLE posts (
  id INT PRIMARY KEY,
  title VARCHAR(255)
);

CREATE TABLE comments (
  id INT PRIMARY KEY, 
  post_id INT,
  FOREIGN KEY(post_id) REFERENCES posts(id)
);

Here, the post_id foreign key in the comments table references the primary key id of related rows in the posts table. This enforces referential integrity and defines the one-to-many relationship between posts and comments.

Eloquent relationships build on this foundation by allowing you to define relationship methods on your models that retrieve related model instances using the foreign key mappings. Let‘s look at some examples.

One To One

A one-to-one relationship is where a single model instance is related to a single instance of another model.

For example, imagine a User model that has a related Phone model representing the user‘s phone number. Each User has one Phone and each Phone belongs to one User.

Here‘s how we can define this in Eloquent:

// User.php

public function phone() 
{
  return $this->hasOne(Phone::class); 
}
// Phone.php

public function user()
{
  return $this->belongsTo(User::class); 
} 

Some key points:

  • The hasOne and belongsTo relationship methods define the one-to-one relationship.
  • The foreign key is expected to be on the phones table referencing the users table.
  • Eloquent will look for the foreign key user_id by convention on the phones table.

Now we can fetch a user‘s phone record using $user->phone and fetch the owning user of a phone record via $phone->user.

One To Many

A one-to-many relationship refers to when a single model instance can be related to multiple instances of another model.

For example, think of a blog Post model that has many related Comment models. A single post can have unlimited comments.

Here‘s how this relationship can be defined in Eloquent:

// Post.php

public function comments()  
{
  return $this->hasMany(Comment::class);
} 
// Comment.php

public function post()
{
  return $this->belongsTo(Post::class);
}

To define a one-to-many relationship, the hasMany and belongsTo methods are used again. The foreign key is expected on the comments table referencing the posts table.

We can now fetch a post‘s comments via $post->comments and fetch the parent post of a comment via $comment->post.

Many To Many

A many-to-many relationship refers to when multiple instances of a model can be related to multiple instances of another model.

For example, imagine a User model that has a many-to-many relationship with a Role model. A user can have multiple roles, and a role can be assigned to multiple users.

Many-to-many requires a pivot table to define the relationship, containing foreign keys referencing the primary keys of both models:

CREATE TABLE role_user (
  user_id INT,
  role_id INT  
);

In Eloquent, we would define this using the belongsToMany method on both models:

// User.php

public function roles() 
{
  return $this->belongsToMany(Role::class);
}
// Role.php

public function users()
{
  return $this->belongsToMany(User::class);
} 

Some additional points:

  • The pivot table is assumed to be named alphabetically by singular model names like role_user.
  • Custom pivot table names can be specified on the belongsToMany definition.
  • By default, additional pivot table columns are accessible as attributes when fetching related models.

With this relation defined, we can fetch a user‘s roles via $user->roles and a role‘s users via $role->users.

Has One Through

The "has one through" relationship allows defining a one-to-one relationship through a pivot table.

For example, imagine users make orders, and each order record is related to exactly one order status:

users        orders          order_statuses
------       -------         --------------
id           id              id
name         user_id         name
                           order_status_id

We can relate the User and OrderStatus models through the pivot orders table using the hasOneThrough relation:

// User.php

public function orderStatus()  
{
  return $this->hasOneThrough(OrderStatus::class, Order::class);
}

Now we can fetch a user‘s order status via $user->orderStatus, by going "through" the Order model to reach the related OrderStatus.

Has Many Through

The "has many through" relationship allows defining a one-to-many relationship through a pivot table.

For example, imagine users can have multiple posts, and each post can have multiple tags. We can relate users to tags through the pivot posts table:

users        posts           tags
------       -------         ----          
id           id              id
name         user_id         name
                           tag_id

The relationship would be defined like:

// User.php

public function tags()
{
  return $this->hasManyThrough(Tag::class, Post::class); 
}

Now $user->tags will retrieve all the user‘s tags by joining through the pivot posts table.

Polymorphic Relations

Polymorphic relations allow a model to be related to multiple other models through a single association.

For example, imagine a tagging system with photos and videos that can be tagged:

posts                    tags
--------                 -------
id                       id  
title                    name
body        
                         taggable_id          
                         taggable_type

videos
-------   
id
name

Here, the tags table contains polymorphic columns to relate to posts and videos in a single relation.

We can define this in Eloquent as:

// Tag.php

public function taggable()
{
  return $this->morphTo(); 
}

Now $tag->taggable will return either a Post or Video instance based on the polymorphic columns. The consuming application code doesn‘t need to know or care what model type is returned.

Eager Loading

When fetching model instances, related data can be "eager loaded" to avoid running additional queries.

For example:

$books = Book::with(‘author‘)->get();

This will fetch all books and their related authors in a single query. Without eager loading, an additional query would run for each book to fetch the author.

Eager loading helps avoid the N+1 query problem and can significantly improve performance.

Demo Project

Let‘s explore some relationship examples in a simple demo project.

Imagine we are building an admin portal for a blog. We‘ll have Users, Posts, Comments, and Tags models with the following relationships:

  • User 1:M Post
  • Post 1:M Comment
  • Post M:M Tag through pivot table post_tag
  • Comment 1:1 Status

First, the migrations:

// users
Schema::create(‘users‘, function (Blueprint $table) {
  $table->id();
  $table->string(‘name‘);
});

// posts
Schema::create(‘posts‘, function (Blueprint $table) {
  $table->id();
  $table->foreignId(‘user_id‘)->constrained();
  $table->string(‘title‘);
});

// comments
Schema::create(‘comments‘, function (Blueprint $table) {
  $table->id();
  $table->foreignId(‘post_id‘)->constrained();
  $table->text(‘content‘);  
});

// statuses
Schema::create(‘statuses‘, function (Blueprint $table) {
  $table->id();
  $table->string(‘name‘); 
});

// tags
Schema::create(‘tags‘, function (Blueprint $table) {
  $table->id();
  $table->string(‘name‘);   
});

// post_tag pivot
Schema::create(‘post_tag‘, function (Blueprint $table) {
  $table->foreignId(‘post_id‘)->constrained();
  $table->foreignId(‘tag_id‘)->constrained();
});

Next, the model relationships:

// User 
public function posts()
{
  return $this->hasMany(Post::class);
}

// Post
public function user() 
{
  return $this->belongsTo(User::class);
}

public function comments()
{
  return $this->hasMany(Comment::class);  
}

public function tags()
{
  return $this->belongsToMany(Tag::class);
}

// Comment
public function post()
{
  return $this->belongsTo(Post::class);
}

public function status()
{
  return $this->hasOne(Status::class);
}

// Tag 
public function posts()
{
  return $this->belongsToMany(Post::class);
}

// Status
public function comment()
{
  return $this->belongsTo(Comment::class);
}

Now let‘s test them out with some sample data:

// Create a user
$user = User::create([‘name‘ => ‘John Doe‘]);

// Create a post by the user 
$post = $user->posts()->create([‘title‘ => ‘My First Post‘]);

// Create a comment on the post
$comment = $post->comments()->create([‘content‘ => ‘Great post!‘]); 

// Create an approved status for the comment
$status = $comment->status()->create([‘name‘ => ‘approved‘]);

// Tag the post 
$tag = Tag::firstOrCreate([‘name‘ => ‘Laravel‘]);
$post->tags()->attach($tag);

// Fetch the post user 
$post->user->name; // "John Doe"

// Fetch the comment‘s post
$comment->post->title; // "My First Post" 

// Fetch the comment‘s status 
$comment->status->name; // "approved"

// Fetch the post‘s tags
foreach ($post->tags as $tag) {
  echo $tag->name; // "Laravel"
} 

// Fetch the user‘s post 
foreach ($user->posts as $post) {
  echo $post->title; // "My First Post"
}

This confirms that our relationships are correctly defined and working as expected!

Recap

The key points about Eloquent relationships:

  • They allow efficiently querying and manipulating related model data
  • Foreign keys define relationships at the database level
  • Eloquent relations build on top of these foreign keys
  • Main relations: hasOne, belongsTo, hasMany, belongsToMany, hasOneThrough, hasManyThrough
  • Pivot tables define many-to-many mappings
  • with() preloads related data via eager loading
  • Solid relationship knowledge is key to effectively using Eloquent

I hope this guide has shed light on how Eloquent model relationships work and how to leverage them in your Laravel applications! Correctly structuring your models and relations is critical for efficiently interacting with your database.

Written by