DEV Community

Cover image for How to implement a simple like system with Laravel

How to implement a simple like system with Laravel

Benjamin Delespierre on June 29, 2020

This tutorial has been updated for Laravel 8 (I also fixed a few issues). A working implementation of this tutorial is also available on GitHub, e...
Collapse
 
theovier profile image
Theo Harkenbusch

Hey, thanks for the tutorial! However, I think there is a slight mistake in the Likes.php class.

It should be
return $likeable->likes()->whereHas('user', function(Builder $query) {
return $query->whereId($this->id);
})->exists();

rather than
return $likeable->likes()->whereHas('user', function($query) {
return $query->whereId($this->id)->exists();
});

in Laravel 7.

Collapse
 
mateator profile image
mateator • Edited

Ty man, i have been stuck for weeks because of that, it wasnt working until you wrote to took out the ->exists();

same thing to delete, the result is:
$likeable->likes()->whereHas('user', function(Builder $query) {
$query->whereId($this->id);
})->delete();

Also, if someone implements this, dont forget to add this in Likes.php
use Illuminate\Database\Eloquent\Builder;

Collapse
 
bdelespierre profile image
Benjamin Delespierre

Yeah I removed the Builder typehint, it wasn't very helpful anyway.

Collapse
 
stleroux profile image
stleroux • Edited

For some reason I get an error message when I try to post my own comment

Collapse
 
bdelespierre profile image
Benjamin Delespierre

My mistake. I'll change it, thanks for pointing it out

Collapse
 
devkitshow profile image
devkitshow • Edited

Hello! There's also a problem in LikesRequest

'id' => [
                "required",
                function ($attribute, $value, $fail) {
                    $class = $this->getClass($value);

                    if (! $class::where('id', $value)->exists()) {
                        $fail($value." is not present in database");
                    }
                },
            ],
Enter fullscreen mode Exit fullscreen mode

this would give you an error due to getClass($value) is trying to get class of numeric id.
So it should probably be like

$class = $this->getClass($this->input('likeable'));
Enter fullscreen mode Exit fullscreen mode
Collapse
 
bdelespierre profile image
Benjamin Delespierre

Thanks for pointing this out. I'm fixing the article as we speak to take your feedback into account. Stay tuned!

Collapse
 
bdelespierre profile image
Benjamin Delespierre

Should be fixed now. What do you think?

Collapse
 
deyvidbh profile image
Deyvidbh

Thank you very much for the tutorial! I have some doubts regarding how to implement this on my blade, I am a beginner. could you give me a tip? what do I put on my like button?

Collapse
 
bdelespierre profile image
Benjamin Delespierre • Edited

assuming you've done everything in the tutorial right you should have a route "/like"

You can check it with php artisan route:list --name like

The button should look like this:

@if(!auth()->user()->hasLiked($post))
    <form action="/like" method="post">
        @csrf
        <input type="hidden" name="likeable" value="{{ get_class($post) }}">
        <input type="hidden" name="id" value="{{ $post->id }}">
        <button type="submit" class="btn btn-primary">
            Like
        </button>
    </form>
@else
    <button class="btn btn-secondary" disabled>
        {{ $post->likes()->count() }} likes
    </button>
@endif
Enter fullscreen mode Exit fullscreen mode

I believe you can figure out how to make the unlike button by yourself with the resources provided in the tutorial ;-)

Collapse
 
md3108 profile image
MD3108

Could u show how unlike works because I'm just managing to like but not dislike... #alsoBeginner

Thread Thread
 
md3108 profile image
MD3108

I get this error when I try my dislikeSQLSTATE[22007]: Invalid datetime format: 1292 Truncated incorrect INTEGER value: 'like' (SQL: delete fromnoteswhereid= like

Thread Thread
 
bdelespierre profile image
Benjamin Delespierre

Sure, I think I'll update the article to add the views, stay tuned!

Thread Thread
 
bdelespierre profile image
Benjamin Delespierre

Aaaaand done. check it out!

Thread Thread
 
md3108 profile image
MD3108

ohh nice, couldn't have hoped for better. I'll leave a comment on how it went once I get to it again!

Thread Thread
 
bdelespierre profile image
Benjamin Delespierre

most welcome my friend. You can also check this out, it's a working implementation of the like system github.com/bdelespierre/laravel-li...

Thread Thread
 
md3108 profile image
MD3108

I managed to get as far as last time same error presists...
SQLSTATE[22007]: Invalid datetime format: 1292 Truncated incorrect INTEGER value: 'like' (SQL: delete fromnoteswhereid= like)`

however I think in 'app/Models/Concerns/Likes.php' the 'use App\Like' should be 'use App\Models\Like'. For the rest I got a flaw less exp. just don't know how to solve that issue there.

Collapse
 
stleroux profile image
stleroux

Hi,
Love the code. Straight forward and works like a charm.
Is there a way to eager load the likes data on index pages?
I tried adding "likes" to the with query but it doesn't seem to reduce the amount of queries being performed.

Thanks

Collapse
 
bdelespierre profile image
Benjamin Delespierre • Edited

I have just tried

$posts = Post::latest()->with('likes')->take(5)->get();
Enter fullscreen mode Exit fullscreen mode

Using the demo project and Laravel Debug bar.

From 8 queries (with the N+1) problem you mention, I now have only 4 👍

See before and after

Hint: be sure to place the ->with('...') part BEFORE ->get() or ->all().

Collapse
 
stleroux profile image
stleroux

Actually, I was using the code in the index page and doing a @can ('like') and @can ('unlike') which was querying the DB twice per record. I changed the @can ('unlike') for @else and now the duplicate queries are gone.
Thanks

Thread Thread
 
bdelespierre profile image
Benjamin Delespierre • Edited

Yes, that's because the gates in AuthServiceProvider are using User::hasLike :

    public function hasLiked(Likeable $likeable): bool
    {
        if (! $likeable->exists) {
            return false;
        }

        return $likeable->likes()
            ->whereHas('user', fn($q) =>  $q->whereId($this->id))
            ->exists();
    }
Enter fullscreen mode Exit fullscreen mode

You may change it to use a cache, like this (actual working solution) :

    public function hasLiked(Likeable $likeable): bool
    {
        if (! $likeable->exists) {
            return false;
        }

        return $this->likes->contains(
            fn($like) => $like->likeable_type == get_class($likeable) && $like->likeable_id == $likeable->id
        );
    }
Enter fullscreen mode Exit fullscreen mode

It should minimize the number of queries. It's ugly but it works!

Result before with 15 queries (one per @can('like')) and after only 6!

Collapse
 
bdelespierre profile image
Benjamin Delespierre

Can you show us what you tried?

Collapse
 
stleroux profile image
stleroux

Hello,
I have run into a weird issue.
Wen I run the code on the localhost where I do my development, everything works fine.
This is a Windows 10 machine running PHP 7.4.15

However, when I transfer my files to my Ubuntu 18.04 server, I get the following error message on all pages :
->> syntax error, unexpected '=>' (T_DOUBLE_ARROW), expecting '{' <<-
And it is pointing to


      $likeable->likes()
        ->whereHas('user', function($q) => $q->whereId($this->id))
        ->delete();

Enter fullscreen mode Exit fullscreen mode

in my User model.
I am running PHP 7.4.21 on the server.
If I comment out the lines, the pages load properly.
Hopefully, this is enough info to TS, if not, please let me know what else is needed.
Thanks

Collapse
 
bdelespierre profile image
Benjamin Delespierre

In the code snippet you provided, there's a typo on the arrow function, it should be:

      $likeable->likes()
        ->whereHas('user', fn($q) => $q->whereId($this->id))
        ->delete();
Enter fullscreen mode Exit fullscreen mode
Collapse
 
webfuelcode profile image
webfuelcode • Edited

I followed the steps, running the localhost says "trait app\concerns\likeable" not found... Tried a lot to figure out the reason but failed. It may be a typo or the new version is the reason. Can you tell me...

Collapse
 
bdelespierre profile image
Benjamin Delespierre

It's very unlikely a version issue (if you're running PHP7.2+ of course).

If you see the 'not found' error it means a namespace import is missing.

Collapse
 
squarou profile image
Benjamin

Hello @bdelespierre

That's really brillant, thanks a lot for sharing!

I noticed an error in app/Http/Controllers/LikeController.php

Change
public function unlike(UnlikeRequest $like)
with
public function unlike(UnlikeRequest $request) ;-)

Thank you again,

Collapse
 
fatahidzhar profile image
FatahIdzhar • Edited

i get proble on
Undefined property: stdClass::$likes

flareapp.io/share/q5YLj4j7#F62

Collapse
 
bdelespierre profile image
Benjamin Delespierre

Your $model variable shouldn't be an stdClass instance. It should be a Post instance. (or anything with the Likes trait).

I suggest you dump this variable with dd($model) in the controller before returning the view and try to understand what's going on.

Good luck!

Collapse
 
fatahidzhar profile image
FatahIdzhar • Edited

I've tried it, it's still an error in the
Undefined property: stdClass::$likes

and already using dd()

Collapse
 
bytescoded profile image
Winner Onaolapo

Thanks for this tutorial sir. Just curious is there a way to do this without having the user to sign in before liking a post? Thank you, sir

Collapse
 
bdelespierre profile image
Benjamin Delespierre

Well, you could tweak the design a bit by making the user_id nullable in order to create userless likes.

Collapse
 
atr0x23 profile image
atr0x23

Hi, I am new to Laravel. I followed your orders here but when i am trying to run and see a view (blade) with the posts from db I am getting this error:

Trait 'App\Concerns\Likes' not found

What am I missing?
Thanks in advance.

Collapse
 
fatahidzhar profile image
FatahIdzhar

i get proble on stdClass::$Likes

More details are here :
flareapp.io/share/q5YLj4j7#F62

Collapse
 
zhandos998 profile image
zhandos998

using Post::all();
no DB::table('post')->get();

Collapse
 
snalpas profile image
Silvere

Nice work that will serve as a reference for the design of features on Laravel 8
Thank you 👍

Collapse
 
imozaiingame profile image
Ozai.hft • Edited

How the user look what was his liked ? @bdelespierre

Collapse
 
lucasmartins-me profile image
Lucas Martins

$something->likes

Collapse
 
jyrjsinhsolanki profile image
Jayrajsinh Solanki

Thanks for the tutorial...