A simple way of implementing user bookmarks in Laravel
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.