Courses

Structuring Databases in Laravel 11

BelongsTo vs BelongsToMany vs Polymorphic: 3 Examples

To understand how to structure the DB in Laravel or in whatever language, one of the most important concepts is relationships and understanding the differences between them. How do you choose whether it should be a belongsTo, belongsToMany, or polymorphic?

In this lesson, I will show three examples of all those relationships within the same project. The project is a typical blog or a portal of articles, and we will add additional tables to the articles with relationships. We will see which relationship is suitable for which situation.


Example 1: Photos

Scenario 1. BelongsTo Relationship

The first situation is about photos. The most typical scenario is that each article has many photos. In this case, we have a hasMany and a belongsTo relationship.

First, the articles are a simple table of title and text.

database/migrations/xxx_create_articles_table.php:

Schema::create('articles', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->string('body');
$table->timestamps();
});

We have a foreign ID for the articles table in the migration for the photos table.

database/migrations/xxx_create_photos_table.php:

Schema::create('photos', function (Blueprint $table) {
$table->id();
$table->string('filename');
$table->foreignId('article_id')->constrained();
$table->timestamps();
});

Then, in the Model, every photo belongs to some article.

app/Models/Photo.php:

use Illuminate\Database\Eloquent\Relations\BelongsTo;
 
class Photo extends Model
{
protected $fillable = [
'filename',
'article_id',
];
 
public function article(): BelongsTo
{
return $this->belongsTo(Article::class);
}
}

And every article has many photos.

app/Models/Article.php:

use Illuminate\Database\Eloquent\Relations\HasMany;
 
class Article extends Model
{
protected $fillable = [
'title',
'body',
];
 
public function photos(): HasMany
{
return $this->hasMany(Photo::class);
}
}

This is the most simple and the most typical example.


Scenario 2. BelongsToMany Relationship

What if you want each photo to be used in multiple articles? For example, some photos are suitable for many different articles. Then you need a belongsToMany relationship. A photo may belong to many articles, but each article still has many photos. Then, it's a two-way relationship.

You create a pivot table article_photo with foreign key columns for both tables.

Laravel has a naming convention for creating a pivot table. It should be singular from both tables and in alphabetical order. So, not photo_article and not article_photos.

database/migrations/xxx_create_article_photo_table.php:

Schema::create('article_photo', function (Blueprint $table) {
$table->foreignId('article_id')->constrained();
$table->foreignId('photo_id')->constrained();
});

Then, in the Photo Model, instead of the belongsTo(Article) relationship, we have belongsToMany(Article).

app/Models/Photo.php:

use Illuminate\Database\Eloquent\Relations\BelongsToMany;
 
class Photo extends Model
{
protected $fillable = [
'filename',
'article_id',
];
 
public function articles(): BelongsToMany
{
return $this->belongsToMany(Article::class);
}
}

In the Article Model, it's also a belongsToMany relationship instead of hasMany.

app/Models/Article.php:

use Illuminate\Database\Eloquent\Relations\BelongsToMany;
 
class Article extends Model
{
protected $fillable = [
'title',
'body',
];
 
public function photos(): BelongsToMany
{
return $this->belongsToMany(Photo::class);
}
}

That's the second scenario. Do you feel the difference? Now, let's add a third scenario.


Scenario 3. Polymorphic Relationship

What if there's a separate table called videos in addition to articles? The blog project has two types of entities: articles and videos. The photo and thumbnail may belong to either an article or a video.

One way of doing that in the photos table is to add another foreign ID to video_id. Both should be nullable because only one will be present each time.

database/migrations/xxx_create_photos_table.php:

Schema::create('photos', function (Blueprint $table) {
$table->id();
$table->string('filename');
$table->foreignId('article_id')->nullable()->constrained();
$table->foreignId('video_id')->nullable()->constrained();
$table->timestamps();
});

Then, you should add a second belongsTo relationship in the Photo Model for the video.

app/Models/Photo.php:

use Illuminate\Database\Eloquent\Relations\BelongsTo;
 
class Photo extends Model
{
protected $fillable = [
'filename',
'article_id',
];
 
public function articles(): BelongsTo
{
return $this->belongsTo(Article::class);
}
 
public function video(): BelongsTo
{
return $this->belongsTo(Video::class);
}
}

However, a polymorphic relationship should be considered for this type of scenario. Polymorphic relationships are used when an entity can belong to anything.

In the migration, you can use a morphs() method, which will create two columns:

  • {column}_id (the ID of the record)
  • {column}_type (the model like App\Models\Article or App\Models\Video).

The name for the morph columns is the table name with an able suffix, like photoable_id and photoable_type.

database/migrations/xxx_create_photos_tables.php:

Schema::create('photos', function (Blueprint $table) {
$table->id();
$table->string('filename');
$table->foreignId('article_id')->nullable()->constrained();
$table->foreignId('video_id')->nullable()->constrained();
$table->morphs('photoable');
$table->timestamps();
});

In the Photo Model, you only need to define the morphTo relation instead of a relationship for every Model.

app/Models/Photo.php:

use Illuminate\Database\Eloquent\Relations\MorphTo;
 
class Photo extends Model
{
protected $fillable = [
'filename',
'article_id',
];
 
public function photoable(): MorphTo
{
return $this->morphTo();
}
}

On the other side, in each parent model, you call morphMany with the name of a relationship. In this case, it's photoable.

app/Models/Article.php:

use Illuminate\Database\Eloquent\Relations\MorphMany;
 
class Article extends Model
{
protected $fillable = [
'title',
'body',
];
 
public function photos(): MorphMany
{
return $this->morphMany(Photo::class, 'photoable');
}
}

app/Models/Video.php:

use Illuminate\Database\Eloquent\Relations\MorphMany;
 
class Video extends Model
{
protected $fillable = [
'title',
'video_url',
];
 
public function photos(): MorphMany
{
return $this->morphMany(Photo::class, 'photoable');
}
}

When you want to get those child records by article, for example, in the ArticleController, you treat them like they would be a hasMany relationship.

use App\Models\Article;
 
class ArticleController extends Controller
{
public function index(Article $article)
{
foreach ($article->photos as $photo) {
echo $photo->filename;
}
}
}

So that's the first example of belongsTo, belongsToMany, and Polymorphic relationships with photos.


Example 2: Comments

Let's say we have a comments table in the same blog project, and comments belong to an article. However, in the future, comments may also belong to a video.

Again, for the comments table, there could be two foreign nullable columns for each model to which a comment belongs.

database/migrations/xxx_create_comments_table.php:

Schema::create('comments', function (Blueprint $table) {
$table->id();
$table->foreignId('article_id')->nullable()->constrained();
$table->foreignId('video_id')->nullable()->constrained();
$table->string('body');
$table->timestamps();
});

Or you may choose a polymorphic relationship.

database/migrations/xxx_create_comments_table.php:

Schema::create('comments', function (Blueprint $table) {
$table->id();
$table->foreignId('article_id')->nullable()->constrained();
$table->foreignId('video_id')->nullable()->constrained();
$table->morphs('commentable');
$table->string('body');
$table->timestamps();
});

From the practical point of view, I want you to understand the difference between a regular belongsTo and a polymorphic belongsTo.


Example 3: Tags

Finally, let's look at the belongsToMany example with tags. Let's add tags to all those articles, videos, and photos in the same project. What would be their relationship to articles? It's probably belongsToMany.

When deciding whether a relationship belongs to one article or many, my tip is to try to pronounce what makes more sense in your head. Does the tag belong to one article, or does it belong to many articles? If every tag belongs to only one article, then what's the point of the tags? The tagging system should have many tags for many articles.

database/migrations/xxx_create_tags_table.php:

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

In the Tag Model, we have a belongsToMany relationship to articles.

app/Models/Tag.php:

use Illuminate\Database\Eloquent\Relations\BelongsToMany;
 
class Tag extends Model
{
protected $fillable = [
'name',
];
 
public function articles(): BelongsToMany
{
return $this->belongsToMany(Article::class);
}
}

We also have a separate pivot table for the article_tag.

database/migrations/xxx_create_article_tag_table.php:

Schema::create('article_tag', function (Blueprint $table) {
$table->foreignId('article_id')->constrained();
$table->foreignId('tag_id')->constrained();
});

What about the video situation? A tag may belong to many articles or many videos. Should it be a polymorphic relationship? In my opinion, it shouldn't. There is a way to have polymorphic relationships with many to many, but then it gets very complex.

Personally, I would add another belongsToMany relationship for the videos and another tag_video pivot table in the 'Tag' model.


I hope this short introduction to relationships makes it a bit clearer to you when to choose which. This is one of the typical tasks or jobs when trying to come up with a database structure: how to structure the relationships.

This is an introduction to most typical scenarios. Let's dive deeper with more examples in the following lessons.

avatar

I don't really understand why you went for "many to many" realtionships instead of "polymorphic relationship" for the tags example

avatar

Why would it be a polymorphic, if the tags are only used on the articles? It doesn't make sense. Polymorphic relations are slower than many to many and there is no need for anything else to be tagged

avatar
Alexander Zinchenko

I think it's depends on your project. You need to feel it :)