NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Likes for Laravel

The Likes facade

RoundlyConsulting\Likes\Facades\Likes resolves the actor from the authenticated user by default, so the common path is one line. Override the actor or reaction type with the fluent builder, and read one likeable with Likes::for():

use RoundlyConsulting\Likes\Facades\Likes;

Likes::like($post);                     // as the authenticated user
Likes::unlike($post);
Likes::toggle($post);
Likes::react($post);                    // one active reaction per actor + likeable
Likes::has($post);                      // bool
Likes::likeMany([$post, $comment]);
Likes::unlikeMany([$post, $comment]);

Likes::actor($user)->like($comment);    // as any actor
Likes::actor($team)->toggle($post);
Likes::as('love')->like($post);         // typed reaction
Likes::actor($user)->as('wow')->toggle($post);
Likes::actor($user)->as('love')->react($post);  // switch reaction in place
Likes::actor($user)->has($post);
Likes::actor($user)->likeMany([$post, $comment]);

Likes::for($post)->count();             // int — all reactions (uses an eager-loaded likes_count)
Likes::for($post)->count('love');       // int — one reaction type
Likes::for($post)->summary($viewer);    // ReactionSummary (viewer defaults to the resolved actor)
Likes::for($post)->likedBy($user);      // bool — default reaction type unless given

Methods

MethodReturnsDescription
actor(Model $actor)PendingLikeStart a chain for a specific actor.
as(string $type)PendingLikeStart a chain for a specific reaction type.
like(Model $likeable)boolEnsure liked; returns true.
unlike(Model $likeable)boolEnsure not liked; returns false.
toggle(Model $likeable)boolToggle; returns the new state.
react(Model $likeable)boolSet the actor’s single reaction, switching in place.
has(Model $likeable)boolWhether the actor has the reaction (default type unless as() is used).
likeMany(iterable $likeables)voidBulk like.
unlikeMany(iterable $likeables)voidBulk unlike.
for(Model $likeable)LikeableLikesRead handle for one likeable — count(), summary(), likedBy().
fake()LikesFakeSwap in the recording test fake.

The for() read handle

Likes::for($likeable) returns a LikeableLikes handle: count(?type) uses an eager-loaded likes_count when no type is given, summary(?viewer) returns a ReactionSummary for the viewer (the resolved actor by default), and likedBy($actor, ?type) checks the default reaction type unless you pass one.

The model traits are sugar

HasLikes and GivesLikes call the same manager, so everything here — including Likes::fake() — applies to them:

$user->like($post);          // = Likes::actor($user)->like($post)
$post->likesCount();         // = Likes::for($post)->count()
$post->reactionSummary();    // = Likes::for($post)->summary()

An immutable builder

actor() and as() return a new PendingLike each time, so a partially built chain is safe to keep and reuse:

$asLove = Likes::as('love');            // an immutable PendingLike — safe to keep

$asLove->like($post);
$asLove->actor($user)->toggle($photo);  // a new builder; $asLove is unchanged
Likes::actor($user)->has($post);        // checks the default reaction

Resolving the actor

With no explicit actor, the facade asks actor_resolver: null uses auth()->user(); a callable, or an invokable class-string resolved from the container, must return a model or null; a value that doesn’t resolve to a callable throws InvalidConfigurationException. Configure it for non-web guards or non-user actors:

// config/likes.php
'actor_resolver' => fn () => auth('api')->user(),
// or an invokable class-string:
'actor_resolver' => \App\Likes\CurrentActorResolver::class,
namespace App\Likes;

use Illuminate\Database\Eloquent\Model;

final class CurrentActorResolver
{
    public function __invoke(): ?Model
    {
        return auth('api')->user();
    }
}

Laravel can’t cache config that contains closures, so prefer the invokable class when you run php artisan config:cache.

When no actor resolves

Likes::like($post);   // a guest request with no resolvable actor

// RoundlyConsulting\Likes\Exceptions\NoAuthenticatedActorException:
// No actor could be resolved. Authenticate a user or pass an actor
// explicitly via Likes::actor($model).

Reads that must render for guests — withLikedState(), reactionSummary(), toLikeArray() and LikeResource — resolve the actor the same way but fall back to an empty viewer state instead of throwing.

Global alias

facade_alias registers a short global alias — Likes by default — so the facade works without an import. Set it to null or false (LIKES_FACADE_ALIAS=false, also 0/off/no) to skip the alias; a blank value is not set, so Likes is still registered:

\Likes::toggle($post);   // the global alias from facade_alias — no import needed

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.