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

Idempotent no-ops — liking what’s already liked, unliking what isn’t — dispatch no events. On a real change the package fires exactly one event:

use RoundlyConsulting\Likes\Events\Liked;

class NotifyAuthor
{
    public function handle(Liked $event): void
    {
        // $event->actor, $event->likeable, $event->type, $event->like
    }
}
EventWhenProperties
LikedA like is created or restoredactor, likeable, type, like
UnlikedA like is removedactor, likeable, type, like
ReactionChangedA reaction is switched via react()actor, likeable, from, to, like
  • A switch via react() fires only ReactionChanged — never Liked or Unliked. When react() collapses several reactions into one, it also fires Unliked for each extra reaction it removes.
  • Concurrent requests can’t double-fire: only the request that actually changed a row dispatches its event.
  • Bulk likeMany() / unlikeMany() fire one event per item that actually changed.

Registering listeners

use Illuminate\Support\Facades\Event;
use RoundlyConsulting\Likes\Events\Liked;
use RoundlyConsulting\Likes\Events\ReactionChanged;

// e.g. in AppServiceProvider::boot()
Event::listen(Liked::class, NotifyAuthor::class);

Event::listen(function (ReactionChanged $event): void {
    // $event->from, $event->to — a clean transition, never an unlike + like
});

The events use SerializesModels, so queued listeners work as usual.

Optional: a persisted counter column

The package never adds columns to your own tables. For O(1) counts use withLikesCount() / loadCount('likes'), or maintain your own counter column with a small listener — for example a likes_count column on the likeable:

use RoundlyConsulting\Likes\Events\Liked;
use RoundlyConsulting\Likes\Events\Unliked;

class SyncLikesCounter
{
    public function increment(Liked $event): void
    {
        $event->likeable->increment('likes_count');
    }

    public function decrement(Unliked $event): void
    {
        $event->likeable->decrement('likes_count');
    }
}
Event::listen(Liked::class, [SyncLikesCounter::class, 'increment']);
Event::listen(Unliked::class, [SyncLikesCounter::class, 'decrement']);

Register it yourself — the package does not auto-register it. A reaction switch fires neither Liked nor Unliked, and a react() that collapses several reactions fires Unliked for each one it removes, so the counter stays correct.

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.