Laravel Eloquent Relationships make codes cleaner
2024-05-23 15:18 WIB - Hendrik Lie
Just now I dare myself to actually implement Laravel’s
eloquent relationships to this site. Previously I was too afraid,
thinking that it would be too complicated. I resorted to query builder instead.
It took me some efforts to join from post_types and
users tables to posts table, and I am amazed
at how I decided to take a more complicated route to achieve pretty much
the same results.
Yes, Eloquent relationships are more succinct when we have implemented it to our models. In the following sections I will try to demonstrate the difference between the two and how eloquent creates cleaner code.
Before Eloquent - Query Builder
What we did to fetch a post was originally like the following:
use App\Models\Post;
// Fetch published posts of a certain type,
// the amount of which is limited
$type = 'news';
$limit = 15;
Post::select('posts.id',
'posts.created_at as created',
'post_types.code as type',
'post_types.name as type_name',
'posts.slug',
'posts.title',
'users.name as author',
'posts.body')
->where('is_published',1)
->where('post_types.code',$type)
->orderBy('created','desc')
->join('users',
'users.id',
'=','posts.users_id')
->join('post_types',
'post_types.id',
'=','posts.post_types_id')
->limit($limit)
->get();
// Fetch a particular post by selecting its
// ID
$id='put-post-id-here';
Post::select('posts.id',
'posts.created_at as created',
'post_types.code as type',
'posts.slug',
'posts.title',
'users.name as author',
'users.id as author_id',
'posts.body',
'posts.is_published')
->where('posts.id',$id)
->join('users',
'users.id',
'=','posts.users_id')
->join('post_types',
'post_types.id',
'=','posts.post_types_id')
->firstOrFail();The joins are necessary for me to be able to access values of its post types and author information.
Sets up Eloquent Relationships
Now let us implement Eloquent, by first adding the required functions to our models. First we want to access posts from our User model.
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class User extends Authenticatable
{
/**
* Get the posts for this user
*/
public function posts(): HasMany
{
return $this->hasMany(
Post::class,
'users_id');
}
}Now we also want to do the same from our PostType model.
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Model;
class PostType extends Model
{
/**
* Get the posts for this PostType
*/
public function posts(): HasMany
{
return $this->hasMany(
Post::class,
'post_types_id');
}
}Note that we use the hasMany methods to define that each
users and post types can have many posts. We also supplied our foreign
keys users_id and post_types_id because we
used nonstandard column names in our table migrations. Eloquent would
normally use snake case of our model name and append it with
_id. So for example, if in our posts table,
our foreign key for post type is post_type_id, we can just
return $this->hasMany(Post::class); and the method would
just work.
Now we need to define the inverse of hasMany in our
Post model. To define the inverse of hasMany
we can use belongsTo method:
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
/**
* Get the owner of a post
*/
public function user(): BelongsTo
{
return $this->belongsTo(
User::class,
'users_id');
}
/**
* Get the type of a post
*/
public function type(): BelongsTo
{
return $this->belongsTo(
PostType::class,
'post_types_id');
}
}Accessing related models with Eloquent
Now we can access our post’s user and type effortlessly:
use App\Models\Post;
// Get the model of the post's owner
Post::first()->user;
//Get the model of our post type
Post::first()->typeThen if we want to say, get the name of the post’s author, or if we want to get the code of our post type, we can do:
use App\Models\Post;
// Get the name of the post's author
Post::first()->user->name;
// Get the code of the post's type
Post::first()->type->code;Or even accessing posts created by a particular user:
Or get posts of a certain post type:
We can even use query builder alongside with Eloquent. The following achieves similar results as our earlier two query builder examples.
use App\Models\PostType;
use App\Models\Post;
// Fetch published posts of a certain type,
// the amount of which is limited
$type = 'news';
$limit = 15;
PostType::where('code',$type)
->first()
->posts()
->where('is_published',true)
->orderBy('created_at','desc')
->limit($limit)
->get();
// Fetch a particular post by selecting its
// ID
$id='put-post-id-here';
Post::where('id',$id)
->firstOrFail();The primary difference is that now we don’t have to perform joins. The difference would be on how we access the attributes.
When using query builder, we aliased certain columns for easy
accessing. For example users.name is aliased to
author when users table is joined to the
posts table in our previous query builder example.
Therefore assume that we store a particular Post model
after the join operation into a variable $post, we can
access the author name attribute by $post->author.
If the same post model is implementing eloquent relationships like we
described above, there is no need to alias users.id to
author, all we have to do is to get the user of a post, and
access the name attribute:
$post->user->name. Therefore we also made
modifications to our controller methods to reflect the changes. We are
also adjusting our blade templates, as some still directly access models
for displaying contents.
Conclusion
The change is done with minimal differences from the perspective of casual website viewers. However behind the scene the code is being tidied out. Our code can be cleaner as we no longer have to manually join database tables.
In the future we wish to implement user roles using eloquent relationship, and therefore allowing us to conveniently differentiate user’s permissions based on their role that is accessible directly from their user model.