Model Relationships In Laravel

·

11 min read

The eloquent relationship provides a fluent way by which we define the relationship between databases and their respective models in Laravel. It is an important feature of the framework and one of its selling points, it is important to be familiar with Eloquent relationships as it is inevitable when building applications that interact with databases. So we will be looking at important eloquent model relationships in Laravel.

Prerequisite

  • Basic knowledge of PHP

  • basic Knowledge of Laravel

One To One

This type of model-model relationship is used when one instance of a model can be related to only one instance of another model.

An example is the relationship between a user and a profile. A user can only have a single profile and a profile can belong to a single user only.

The table structure for these models will look like this:

users
id - integer
name - string

profile
id - integer
user_id - integer
username - string

In the user model, the relationship is defined by creating a method named after the model the relationship is to be designated to, in this case, profile and the function definition returns a hasOne through the current class. The hasOne method takes the profile model through its class, Profle::class.

In the profile model, the profile model can access the user model by creating a user method and returning a belongsTo method that accepts the user class, User::class. Note that the inverse relationship is not compulsory for the relationship to work, but it helps us to retrieve a user from a profile and a profile from a user. Without the inverse relationship, we can not get a user from a profile but we can get a profile from a user.


namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * Get the profile associated with the user.
     */
    public function profile()
    {
        return $this->hasOne(Profile::class);
    }
}

class Profile extends Model
{
    /**
     * Get the user associated with the profile.
     */
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

The relationship can be used like so:

$user = User::first();
$user->profile;
//returns the profile through a user

$profile = Profile::first();
$profile->user->id;
//returns the user id through the profile

One To Many

In a one-to-many relationship, a single model can be related to one or more instances of another model. In a post and category model relationship, a category can have one or more posts but a post can only belong to a category.

The table structure for these models will look like this:

categories;
id - integer;
name - string;

posts;
id - integer;
category_id - integer;
name - string;

To create this relationship, a posts method returns a hasMany class method that accepts the Post::class is created in a category model. The inverse of hasMany is belongsTo, same as in a one-to-one relationship.

It is important to follow the naming conventions. Notice how we named the method in inverse relationship category and not categories, this is because one post can only have one category.


namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Category extends Model
{
    /**
     * Get the posts associated with the category.
     */
    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}

class Post extends Model
{
    /**
     * Get the category associated with the post.
     */
    public function category()
    {
        return $this->belongsTo(Category::class);
    }
}

The implemented models can be used this way:


$category = Category::first();
$category->posts;
//returns the posts through a category

$post = Post::first();
$post->category->name;
//returns a category name through a post

Has One Through

One has through relationship is used to express a one-to-one relationship indirectly between two models through a third model.

For Instance, a Teacher model can access a Parent model through a Student model even though the Teacher model is not directly related to the Parent model.

The table structure for these models will look like this:

students;
id - integer;
name - string;

parents;
id - integer;
student_id - integer;
name - string;

teachers;
id - integer;
name - string;
student_id - integer;

Let's create a one-to-one relationship between the Parent and Student model.


namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Parent extends Model
{
    /**
     * Get the student associated with the parent.
     */
    public function student()
    {
        return $this->hasOne(Student::class);
    }
}

class Student extends Model
{
    /**
     * Get the parent associated with the student.
     */
    public function parent()
    {
        return $this->belongsTo(Parent::class);
    }
}

Now we create the has-through relationship between a Teacher and a Student using the hasOneThrough class method that accepts two classes. First is the class of the model we want to create a relationship with, and the other is the model through which we are creating the relationship.

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Teacher extends Model
{
    /**
     * Get the parent associated with the teacher through the student.
     */
    public function parent()
    {
        return $this->hasOneThrough(Parent::class, Student::class);
    }
}

The syntax can be a bit confusing at first, but it reads: a Teacher model has one Parent model through a Student model.

The implemented models can be used this way:


$teacher = Teacher::first();
$teacher->parent

Has Many Through

This model relationship allows for multiple instances of one model to be accessed by another through an indirect relationship with a third model. It is like the has-one-through model relationship but now with can access multiple instances.

We will be using the same database structure as has-one-through. The Parent and Student model will have a typical one-to-many relationship but the has-many-through relationship will be defined in the Teacher model with parents method that returns hasManyThrough method which accepts both the Parent and student classes.

It reads, gets many parents through students.

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Parent extends Model
{
    /**
     * Get the students associated with the parent.
     */
    public function students()
    {
        return $this->hasMany(Student::class);
    }
}

class Student extends Model
{
    /**
     * Get the parent associated with the student.
     */
    public function parent()
    {
        return $this->belongsTo(Parent::class);
    }
}

class Teacher extends Model
{
    /**
     * Get the parents associated with the teacher's students.
     */
    public function parents()
    {
        return $this->hasManyThrough(Parent::class, Student::class);
    }
}

Usage:


$teacher = Teacher::first();
dump($teacher->parents()->get());

Many To Many

Many to many as the name implies describes a type of model relationship in which one or more instances of a model can be related to one or more instances of another model.

In a school management platform, one or more students can register for several courses and one or more courses can be studied by several students. Each database table does not take a foreign key of its relative model, rather a third pivot database table is created to store the ids of both models.

The table structure will look like this:

students;
id - integer;
name - string;

courses;
id - integer;
name - string;

course_student;
course_id - integer;
student_id - integer;

Both models define their relationship using the belongsToMany class method.


namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Student extends Model
{
    /**
     * The courses that belong to the student.
     */
    public function courses()
    {
        return $this->belongsToMany(Course::class);
    }
}

class Course extends Model
{
    /**
     * The students that belong to the course.
     */
    public function students()
    {
        return $this->belongsToMany(Student::class);
    }
}

Usage:


$student = Student::first();
dump($student->courses()->get());

$course = Course::first();
dump($course->students()->get());

What Are Polymorphic Relationships?

Laravel docs define the polymorphic relationship as a relationship that allows the child model to belong to more than one type of model using a single association. But what does that really means?

Imagine you have a Post, Profile, Comment and Page models and all these can be liked. One might think to store the likes, we would create a separate Like model for all these models, for instance, PostLike, ProfileLike, CommentLike and PageLike respectively. But this isn't only inefficient but also too much work and creates too many databases and models. imagine we need to like maybe a video or something else.

This is where polymorphic relationships come to the rescue, they allow you to put all the likes in one likes database and of course a single Like model is used. The child model, Like will contain a likeable_id and likeable_type in the database, these two columns store the id and class names of the parent models, with these two columns, we can differentiate between every parent model.

One To One Polymorphic Relationship

This polymorphic relationship is the simplest type. The child model Like belongs to a single instance of both Post and Profile model. That is, even though parent models could be more than these two, the post or profile can not be liked more than once.

The table structure will look like this:

posts;
id - integer;
title - string;

profiles;
id - integer;
picture - string;

likes;
id - integer;
likeable_id - integer;
likeable_type - string;

In the child model, the polymorphic relationship is defined by returning a morphTo that does not take any parameter optionally from the class instance in a likeable method. In the inverse, the parents return a morphOne that accepts the Like::class and the likeable.

The convection is: the child model adds an able to its model named for the relationship definition. If the child model were an image or email, the method name would be imageable or emailable.


namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Like extends Model
{
    /**
     * Get the parent likeable model (post or profile).
     */
    public function likeable()
    {
        return $this->morphTo();
    }
}

class Post extends Model
{
    /**
     * Get the post's like.
     */
    public function like()
    {
        return $this->morphOne(Like::class, 'likeable');
    }
}

class Profile extends Model
{
    /**
     * Get the profile's like.
     */
    public function like()
    {
        return $this->morphOne(Like::class, 'likeable');
    }
}

Usage:


$like = Like::first();

$like = $like->likeable;
//returns the parent, either a post or a profile

$post = Post::first();
$post->like

$profile = Profile::first();
$profile->like

One To Many Relationship

Similar to the typical one-to-many relationship, this polymorphic relationship defines a relationship where a child model can have one or more instances of its parents' model.

For example, Review model can have a polymorphic relationship with Phone and Laptop models.

The table structure will look like this:

phones;
id - integer;
price - float;
model - string;

laptops;
id - integer;
price - float;
model - string;

reviews;
id - integer;
reviewable_id - integer;
reviewable_type - string;

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Review extends Model
{
    /**
     * Get the parent reviewable model (laptop or phone).
     */
    public function reviewable()
    {
        return $this->morphTo();
    }
}

class Laptop extends Model
{
    /**
     * Get all of the laptop's reviews.
     */
    public function reviews()
    {
        return $this->morphMany(Review::class, 'reviewable');
    }
}

class Phone extends Model
{
    /**
     * Get all of the phone's reviews.
     */
    public function reviews()
    {
        return $this->morphMany(Review::class, 'reviewable');
    }
}

Usage:


$laptop = Laptop::find(1);
$reviews = $laptop->reviews;

$phone = Phone::find(2);
$reviews = $phone->reviews;

Many To Many Relationship

This is also similar to a typical many-to-many-relationship but in this case, the relationship is polymorphic. Many child models can belong to many parent models and vice versa.

A fourth table is created in this relationship to store the id and class of the parent model.

posts
id - integer
title - string
content - text

videos
id - integer
title - string
url - string

tags
id - intger
name - string

taggables
tag_id - intger
taggable_id - integer
taggable_type - string

The two parents model here, Video and Post return tags method that returns a morphToMany on its class which accepts the child model class, Tag::class and taggable. In the child model, it returns two methods, posts and videos relating to its two parents and these two methods return a morphByMany methods on the class which accepts the respective class referenced by the method name and a second taggable.

Note that there are no method names taggable, rather, the taggable refers to the fourth table here. Also, notice it is morphToMany and not morphMany in the parent models.

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

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

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

class Tag extends Model
{
    public function posts()
    {
        return $this->morphedByMany(Post::class, 'taggable');
    }

    public function videos()
    {
        return $this->morphedByMany(Video::class, 'taggable');
    }
}
    use App\Models\{Post, Tag, Video}

    Post::find(2)->tags()->saveMany([Tag::find(1), Tag::find(2), Tag::find(3)]);

    Tag::find(5)->posts()->saveMany([Post::find(1), Post::find(2), Post::find(3)]);

    Video::find(2)->tags()->saveMany([Tag::find(1), Tag::find(2), Tag::find(3)]);

    Tag::find(5)->videos()->saveMany([Video::find(1), Video::find(2), Video::find(3)]);

    dump(Post::find(2)->tags()->get())
    dump(Tag::find(5)->posts()->get())
    dump(Video::find(2)->tags()->get())

Conclusion

Building and managing relationships between database tables is an essential aspect of any Laravel application. Laravel's Eloquent ORM makes it easy to define and work with relationships between models, whether it's a one-to-one, one-to-many, or many-to-many relationship. By using the appropriate methods and conventions provided by Laravel, developers can quickly create and maintain complex relationships between their data.

In this article, we explored the different types of relationships that can exist between models in Laravel, including how to define and use them effectively. We saw that Laravel provides a variety of tools to simplify the process of working with relationships.

Understanding how to model relationships in Laravel is an important skill for any developer working with the framework. By following the best practices outlined in this article, developers can create robust and efficient applications that make use of the full power of Laravel's ORM.