Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Likeable Laravel Package

modularavel/likeable

modularavel/likeable is a Laravel package that adds like functionality to Eloquent models. Let users like posts, comments, and other resources through a reusable trait, with relationship helpers and like counts for quick integration.

View on GitHub
Deep Wiki
Context7

Getting Started

v1.0.0 is the first stable tag for modularavel/likeable, so this assessment establishes the baseline. The 1.0.0 version number is the main signal here: the maintainers are declaring the public API (trait methods, relation names, config keys) stable enough to depend on with a normal ^1.0 constraint. The changelog is empty beyond the tag, which is typical for an initial release — there is no upgrade path to worry about, but also no migration guide to lean on, so read the README and the trait source before wiring it into an existing model.

Minimal setup:

composer require modularavel/likeable
php artisan vendor:publish --provider="Modularavel\Likeable\LikeableServiceProvider"
php artisan migrate

Then apply the trait to the model that receives likes (and, if the package follows the usual two-trait split, the liker trait to your User):

use Modularavel\Likeable\Traits\Likeable;

class Post extends Model
{
    use Likeable;
}

First use case — the one to validate before building anything else:

$post->like();                    // as the authenticated user
$post->unlike();
$post->toggleLike();             // for a single "heart" button
$post->isLikedBy(auth()->user());

$post->likes()->count();
$post->likes;                    // collection of like records

Look first at: the published migration (confirm the morph columns and indexes actually match your naming), the config/likeable.php file (user model resolution and table names are the two things that break first), and the trait itself to see which methods are public API versus internal helpers.

Implementation Patterns

Trait placement is asymmetric — get it right. The "likeable" trait goes on content models (Post, Comment, Video), while the "liker" trait belongs on User. Mixing these up produces confusing failures where auth()->user()->likes() returns nothing or throws a relation-not-found error. If your app has more than one actor type (users and teams, say), confirm the package resolves the actor via the auth guard or accepts an explicit model argument; v1.0.0's contract is worth pinning down in a test.

Prefer toggleLike() for UI, explicit like()/unlike() for APIs. A toggle endpoint is idempotent-hostile — a double-click or a retried request flips the state back. For a JSON API, make the client send intent (POST /likes, DELETE /likes) and map those to the explicit methods.

Eager-load counts everywhere. The single biggest performance trap is rendering a feed and calling $post->likes()->count() in the loop. Use withCount('likes') on the index query and read $post->likes_count. If you need "did this user like it" across a feed, that is a second query unless the package ships a helper — usually worth writing a small whereLikedBy($user) scope or a single whereIn lookup keyed by likeable ID rather than N isLikedBy() calls.

Wrap the write and the read in a transaction when you fan out. A realistic like endpoint does: insert the like, increment a cached counter, and dispatch a notification event. Do the first two in a transaction and dispatch after commit (DB::afterCommit or ShouldDispatchAfterCommit) so a rollback doesn't leave a phantom notification.

Expose it through a resource, not the raw relation. In an ApiResource, return is_liked and likes_count rather than embedding the full likes collection — the collection leaks user IDs and balloons payload size on popular posts.

Blade usage: a small anonymous component (<x-like-button :model="$post" />) that renders the count plus a state-aware form keeps the toggle logic out of views and gives you one place to add optimistic UI or a Livewire binding later.

Integration tip: if you already have a Reaction or Favorite table, don't run both systems. Pick one and migrate; two parallel polymorphic tables doing the same job will diverge within a month and the reporting queries become unanswerable.

Gotchas and Tips

  • Morph map drift is the classic failure. If you later call Relation::enforceMorphMap(), existing rows written with fully-qualified class names stop resolving and likes silently vanish from relations. Decide on a morph map before production data exists, or write a one-time backfill.
  • Duplicates and race conditions. A unique index on (user_id, likeable_type, likeable_id) is the only reliable guard against double-likes from concurrent requests. Verify the published migration has it; if not, add it in your own follow-up migration. Handle the resulting QueryException (or use firstOrCreate/insertOrIgnore) rather than letting a 500 surface to the user.
  • Indexes beyond the unique key. Feed-style reads (where likeable_type = ? and likeable_id in (...)) and "who liked this" reads want different index orders. Check EXPLAIN on your real query before the table gets large.
  • Counting strategy matters more than the package. withCount is fine to tens of thousands of likes; beyond that, a denormalized likes_count column updated in the same transaction (with a periodic reconciliation job) is the safer bet. Watch for the count drifting after hard deletes — polymorphic relations do not cascade.
  • Soft-deleted users. If User uses SoftDeletes, likes from deleted users still count and can break isLikedBy() assumptions. Decide explicitly whether to filter, and if the package doesn't, add a scope.
  • Config quirks. The user model and table names in config/likeable.php are usually read at boot; changing them means re-publishing or clearing config cache (php artisan config:clear) — a stale cached config is a frequent "why is it querying the wrong table" answer in local dev.
  • Testing: RefreshDatabase plus actingAs($user) covers most of it. Add one test asserting that liking twice does not create two rows, and one asserting the toggle returns to the original state — those two catch the majority of real-world regressions.
  • Debugging: when likes "disappear," check the morph type string on the row first, then the config, then the trait's relation definition — in that order. The bug is almost never in the toggle logic.
  • Extension points are thin in a 1.0.0. If you need custom like types (reactions, up/down votes), either model that as separate content types or wrap the package behind your own service class now. Wrapping early costs an hour and makes a future swap or fork cheap.
  • Pin sensibly. ^1.0 is reasonable for a 1.0.0, but with no changelog history yet, review the diff before taking 1.1.0 until the project demonstrates a track record of documented breaking changes.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource