Liking, unliking & toggling
All three verbs are idempotent and return a boolean meaning “is liked now” — like() returns true, unlike() returns false and toggle() returns the resulting state. The facade acts as the signed-in user unless you name an actor:
use RoundlyConsulting\Likes\Facades\Likes;
Likes::like($post); // true — ensure liked (no-op if already liked)
Likes::unlike($post); // false — ensure not liked (no-op if not liked)
Likes::toggle($post); // true on like, false on unlike
Likes::actor($user)->like($post); // as any actor instead of the signed-in userThe trait shorthand
GivesLikes offers the same verbs on the actor — $user->like($post) is Likes::actor($user)->like($post):
$post = Post::find(1);
$user = auth()->user();
$user->like($post); // true — ensure liked (no-op if already liked)
$user->unlike($post); // false — ensure not liked (no-op if not liked)
$user->toggleLike($post); // true on like, false on unlikeRe-liking restores a previously removed like instead of creating a duplicate row, so each actor keeps a single record per likeable and reaction type. A unique index on the table enforces it, so this also holds under concurrency: a double-click, a second tab or a retried request can never create a second row, and only the request that actually changed something fires Liked / Unliked. Idempotent no-ops write nothing and dispatch no events, so a double-tapped button can’t inflate counts or notifications.
Reaction types
Pick a reaction type with as() — or pass it as the second argument of a trait verb. Without one, default_reaction is used (see Typed reactions):
Likes::as('love')->like($post);
Likes::actor($user)->as('love')->toggle($post);
$user->like($post, 'love'); // the trait takes the type as a second argument
$user->unlike($post, 'love');In a controller
Because toggle() returns the new state, a heart button needs one call:
use Illuminate\Http\Request;
use RoundlyConsulting\Likes\Facades\Likes;
final class TogglePostLike
{
public function __invoke(Request $request, Post $post): array
{
$liked = Likes::actor($request->user())->toggle($post);
return ['liked' => $liked, 'count' => Likes::for($post)->count()];
}
}Bulk operations
Like or unlike many models at once — handy for imports and “favourite all”:
Likes::likeMany([$post, $comment]); // as the resolved actor
Likes::actor($team)->as('love')->unlikeMany([$post]);
$user->likeMany([$post, $comment, $photo]); // the trait shorthand
$user->unlikeMany([$post, $comment]);
$user->likeMany(Post::whereKey($ids)->get(), 'love'); // any iterable of models, optional typeBulk calls return nothing. They run the same idempotent like or unlike per item, so already-liked items are skipped and each changed item fires its own events.
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 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.