Extracting Relations to Traits in Laravel Models

Hey maybe it’s just me, but maybe you also noticed that once the project becomes big, models tend to grow exponentionally. While this is not bad by itself, but up to 80% of the code is Laravel relations which eat up all the screen space and you have to constantly scroll through them to get to the spot that you really need. Also, they don’t change much. Like if you have a store and user has many orders, there’s not likely that much will change down the road right?

So let’s take the usual blog model to illustrate what to be done.

So we have three good ol’ models: user, post and comment.

So here’s User.php:

class User extends Authenticatable
{  

/// I've removed $hidden, $fillable etc

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

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

Here’s Comment.php:

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

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

And here’s Post.php

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

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

Nothing wrong with that, but you already have to scroll and this code doesn’t even do anything but state the obvious, and most likely it will never change and you’ll have to just scroll through it until the project is finished.

What we can do is to create Relations folder and extract all this code there. So

Relations\BelongsToUser.php

trait BelongsToUser
{
    /**
     * @property $this \Illuminate\Database\Eloquent\Model
     */
    public function user()
    {
        return $this->belongsTo(User::class)->withDefault([new User()]);
    }
}

withDefault() is a convenitent helper to get rid of all those null pointers I’m sure you’ll getting (or again, it’s JUST me)

So there’s also Relations\HasManyPosts.php

trait HasManyPosts
{
    /**
     * @property $this \Illuminate\Database\Eloquent\Model
     */
    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}

So there’s also Relations\HasManyComments.php

trait HasManyComments
{
    /**
     * @property $this \Illuminate\Database\Eloquent\Model
     */
    public function comments()
    {
        return $this->hasMany(Comment::class);
    }
}

Finally Relations\BelongsToPost

trait BelongsToPost
{
    /**
     * @property $this \Illuminate\Database\Eloquent\Model
     */
    public function post()
    {
        $this->belongsTo(Post::class);
    }
}

So after cleaning up, our models look much cleaner:

User.php

class User extends Authenticatable
{
    use Notifiable;
    use HasManyComments, HasManyPosts;
}

Post.php

class Post extends Model
{
    use BelongsToUser, HasManyComments;
}

Comment.php

class Comment extends Model
{
    use BelongsToUser, BelongsToPost;
}

I guess one might say that we didn’t really shorten the code, we just shoved it all in a separate folder. But we actually did shorten it if relations repeat themselves (and let’s face it in a big project a lot of stuff belongs to a user). And also I would argue that Relations folder will be the one you will hardly ever look into.

But there’s a downside, of course. Say you’d want to make things a bit more interesting let’s make comments polymorphic so that not only posts can have them, but users. So you can leave your enlightened “u retarded lol” or “do u even code” comment not only to this post but to my person in general.

In that case comment()->user() will be not very intuitive and can cause errors, so one might argue it’d be better to call this relation ->author()

But to be honest you need to think does storing comments to users and posts in the same table even makes sense.

Extracting Relations to Traits in Laravel Models

Leave a Reply

Your email address will not be published. Required fields are marked *

Scroll to top