January 14, 2023 · 3 minutes

Quick Wins With Dynamic Relationships in Laravel

Prerequisets

To get the most out of this article, please first read Jonathan Reinink's excellent article Dynamic relationships in Laravel using subqueries

This post builds upon his idea and offers two simple use cases for his technique.

Loading Dynamic Relationship Conditionally

Say you have a website that you allow guests to browse, and then offer some additional functionality to authenticated users. In this hypothetical site you can follow other users.

If the user is signed in, we could identify all of the users that the visitor is already following.

Here is the migration for the Follow model:

public function up(): void
{
Schema::create('follows', function (Blueprint $table) {
$table->id();
$table->foreignId('follower');
$table->foreignId('following');
$table->timestamps();
});
}

Here is the dynamic relationship on the User model:

public function follow(): BelongsTo
{
return $this->belongsTo(Follow::class);
}
 
public function scopeWithFollow(Builder $query, User $user): void
{
$query->addSelect(['follow_id' => Follow::select('id')
->whereColumn('following', 'users.id')
->where('follower', $user->id)
->take(1),
])->with('follow');
}

This example shows how you could conditionally add the relationship, depending if there is a logged in user:

$users = User::query()
->when(auth()->check(), fn ($query) => $query->withFollow(auth()->user()))
->get();

With this code in place you can check if the relationship is present or not and render the appropriate buttons in your blade view.

@foreach($users as $user)
@auth
@if($user->follow)
{{-- auth user is following $user --}}
@else
{{-- auth user is not following $user --}}
@endif
@endauth
@endforeach

Working with Polymorphic Dynamic Relationships

This technique can work for polymorphic relationships as well.

Here is the migration for the polymorphic Like model:

public function up(): void
{
Schema::create('likes', function (Blueprint $table) {
$table->id();
$table->morphs('likable');
$table->foreignId('user_id');
$table->timestamps();
});
}

A user on our hypothetical site may be able to like numerous models, so is a good idea to put your polymorpic relationship methods in a trait to re-use them easily. It's also a great place to add your dynamic relationship:

namespace App\Models;
 
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphMany;
 
trait Likable
{
public function likes(): MorphMany
{
return $this->morphMany(Like::class, 'likable');
}
 
public function like(): BelongsTo
{
return $this->belongsTo(Like::class);
}
 
public function scopeWithLike(Builder $query, User $user): void
{
$query->addSelect(['like_id' => Like::select('id')
->whereColumn('likable_id', $this->getTable().'.id')
->where('likable_type', $this->getMorphClass())
->where('user_id', $user->id)
->take(1),
])->with('like');
}
}

In this example, we are allowing a user to like a comment. Don't forget to include the trait in the Comment model:

class Comment extends Model
{
use Likable;
 
// ...
}

Here is an example usage:

$comments = Comment::query()
->withCount('likes')
->when(auth()->check(), fn ($query) => $query->withLike(auth()->user()))
->latest()
->get();

Then in your view you would simply check if $comment->like is not null to see if the current user liked the comment. It would be null otherwise.

Thats it for now! Huge thanks to Jonathan Reinink for the great technique!

© 2024 Curran Jensen.

Twitter · GitHub

Proudly built with Laravel and hosted on Digital Ocean.

Syntax highlighting kindly provided by torchlight.dev