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 givenMethods
| Method | Returns | Description |
|---|---|---|
actor(Model $actor) | PendingLike | Start a chain for a specific actor. |
as(string $type) | PendingLike | Start a chain for a specific reaction type. |
like(Model $likeable) | bool | Ensure liked; returns true. |
unlike(Model $likeable) | bool | Ensure not liked; returns false. |
toggle(Model $likeable) | bool | Toggle; returns the new state. |
react(Model $likeable) | bool | Set the actor’s single reaction, switching in place. |
has(Model $likeable) | bool | Whether the actor has the reaction (default type unless as() is used). |
likeMany(iterable $likeables) | void | Bulk like. |
unlikeMany(iterable $likeables) | void | Bulk unlike. |
for(Model $likeable) | LikeableLikes | Read handle for one likeable — count(), summary(), likedBy(). |
fake() | LikesFake | Swap 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 reactionResolving 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 neededShow 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 cryptoBy 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.