A simple way of implementing user bookmarks in Laravel

by Jeffrey van Rossum

For a recent project, I needed a way for users to bookmark posts. Other then posts, I imagine the application to have other objects in the future too. So, with this in mind, I decided to build a solution which can handle other objects as well.

In this post I'd like to share what I did.

How it works

Ideally, I would like the API to work something like this:

<?php

$user->bookmark($post); // bookmark or unbookmark
$user->bookmarks(); // get all bookmarks
$user->bookmarksByType('App\Post'); // get bookmarks by model type

Creating the bookmarks table

First, we create a table called bookmarks. For this we run the following command in our terminal:

php artisan make:migration create_bookmarks_table --create=bookmarks

The table needs to contain the user_id, the model_type and the model_id. The migration file should look something like this:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateBookmarksTable extends Migration
{
    public function up()
    {
        Schema::create('bookmarks', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('model_type');
            $table->integer('model_id');
            $table->bigInteger('user_id');
        });
    }

    public function down()
    {
        Schema::dropIfExists('bookmarks');
    }
}

Once you have created the migration file, you can run:

php artisan migrate

Creating the Bookmark model

Next, we create the bookmark model. We do this by running the following command:

php artisan make:model Bookmark

Since we are not using Laravel's default timestamps table columns, we'll disable those in the model.

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Bookmark extends Model
{
    public $timestamps = false;
    public $guarded = [];
}

Expanding the User model

Now that the table and model are in place, we can extend the User model. First, we wan't to be able to get a user's bookmarks. For this, we will add a bookmarks function using hasMany.

public function bookmarks()
{
	return $this->hasMany('App\Bookmark');
}

Now this will return all the user's bookmarks, but it might be handy to have a function that returns the bookmarks of a specific model type. This can be done with the following code:

public function bookmarksByType($type)
{
	return $this->bookmarks()->where('bookmarks.model_type', $type);
}

To add bookmarks to the user, we will add the bookmark function. This function will accept the object that is being bookmarked as a parameter. The function will look like this:

public function bookmark($object)
{
	return $this->bookmarks()->create(['model_type' => get_class($object), 'model_id' => $object->id]);
}

However, what happens if the user has bookmarked the object already? Currently, it will just go ahead and add the bookmark which will result in duplicates. Instead, I'd like the bookmark function to work as a toggle. If the user already bookmarked the object, it will go ahead and unbookmark. This can be implemented like this:

public function bookmark($object)
{
	if($this->isBookmarked($object)) {
		return $this->bookmarks()->where([
			['bookmarks.model_type', get_class($object)],
			['bookmarks.model_id', $object->id]
		])->delete();
	}

	return $this->bookmarks()->create(['model_type' => get_class($object), 'model_id' => $object->id]);
}

As you can see, we are referencing a new function called isBookmarked. This function will return either true or false. This is the code for that function:

public function isBookmarked($object)
{
	return $this->bookmarks()->where([
		['bookmarks.model_type', get_class($object)],
		['bookmarks.model_id', $object->id]
	])->exists();
}

In closing

Now that we have the desired functions at our disposal, we can use those to implement the feature at the front-end of the application. That however, is not covered in this post.

Of course, this is just a simple implementation which could be further improved upon and extended. For example, we could have added foreign key constraints on the table. We could have made the combination of the user id, model type and model unique in the table.

This post was last modified 12 May 2020

Comments

Talk about this article on X.
Did you like this post?

If you sign up for my newsletter, I can keep you up to date on more posts like this when they are published.