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

Laravel Likeable Laravel Package

thezeroday/laravel-likeable

A Laravel package that adds likeable behavior to Eloquent models. Attach likes from users or any model, toggle and count likes, check whether a record is liked, and query liked items through simple traits and relationships, with migrations included.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install the package

    composer require thezeroday/laravel-likeable
    

    Pin the version explicitly ("thezeroday/laravel-likeable": "^x.y") — with no dependents and a single release (2024-03-13), you want your lockfile to be the source of truth.

  2. Publish and run migrations (most likeable packages ship a likes table with a polymorphic likeable relation):

    php artisan vendor:publish --provider="Thezeroday\Likeable\LikeableServiceProvider"
    php artisan migrate
    

    If vendor:publish lists nothing, check src/ first — the migration may be auto-loaded and the config file may not exist at all.

  3. Where to look first — in order:

    • src/Traits/ (or src/Concerns/): the trait you add to your models is the entire public API surface.
    • src/Models/Like.php: reveals the polymorphic columns (likeable_type, likeable_id, user_id) and any unique index.
    • config/ or the service provider's mergeConfigFrom: tells you whether the user model, table name, or morph alias is configurable.
  4. First use case — add the trait to any model that can receive likes:

    use Thezeroday\Likeable\Traits\Likeable;
    
    class Post extends Model
    {
        use Likeable;
    }
    

    Then from a controller:

    public function like(Post $post)
    {
        $post->like();          // or $post->like(auth()->user())
        return back();
    }
    

    You now have the three calls that cover 90% of real usage: like(), unlike() (or dislike()), and isLiked().

Implementation Patterns

Toggle endpoint (the pattern you'll actually ship). Rather than separate like/unlike routes, expose one idempotent toggle and let the client stay dumb:

public function toggle(Post $post)
{
    $post->toggleLike(); // or: $post->isLiked() ? $post->unlike() : $post->like();

    return response()->json([
        'liked' => $post->fresh()->isLiked(),
        'count' => $post->fresh()->likesCount(), // adjust to actual method name
    ]);
}

Return the count in the response so the front end doesn't need a second round trip.

Always authorize before liking. The package will happily attach a like for whoever you pass in, so the guard belongs in your controller or a policy:

Gate::authorize('like', $post); // e.g. block liking your own content

Never render a list without eager-loaded counts. A feed of 25 posts that calls a count accessor per row is 25 extra queries:

$posts = Post::withCount('likes')
    ->with(['likes' => fn ($q) => $q->where('user_id', auth()->id())])
    ->latest()
    ->paginate(25);

Then in Blade:

<span>{{ $post->likes_count }}</span>
<button class="{{ $post->likes->isNotEmpty() ? 'liked' : '' }}">♥</button>

"Most liked" ordering falls straight out of withCount:

Post::withCount('likes')->orderByDesc('likes_count')->take(10)->get();

Compose with a policy and a domain event. If the package fires an event on like (check src/Events/), listen to it for notifications or activity feeds instead of stuffing that logic into the controller. If it doesn't, wrap the call in your own service method — that's your extension point and keeps you insulated if the package is abandoned.

Blade component. Wrap the button + count in a single <x-like-button :model="$post" /> so every place in the app gets the same markup, the same route, and the same optimistic-JS hook.

API resources. Put liked and likes_count in the resource, not in the model's $appends — appending globally re-introduces the N+1 you just fixed.

Gotchas and Tips

  • Zero stars / one release is the headline risk. Treat this as vendored code you happen to install via Composer. Read src/Traits/ end to end (it's small), and be prepared to fork. Commit a note in your README naming the fork point.
  • Morph map aliases. If your app calls Relation::enforceMorphMap(), make sure the likeable models' aliases match what's already in the likes.likeable_type column. A mismatch silently returns zero likes rather than throwing.
  • Unique constraint. A well-built likeable table has unique(['likeable_type', 'likeable_id', 'user_id']). If it doesn't, you'll get duplicate likes under double-click or concurrent requests — add the index in your own migration. If it does, handle the resulting QueryException (or wrap like() in a firstOrCreate-style check) so a race returns 200 rather than 500.
  • Soft deletes on the liker. Deleting a user usually leaves orphan likes unless the migration cascades. Check the FK; add onDelete('cascade') in your own migration if needed.
  • isLiked() and query cost. These packages typically check the loaded relation if present, otherwise hit the DB. In a loop that means N queries — hence the eager-loading pattern above. Verify by logging queries in a test.
  • Counts drift. If you cache counts (Redis, cache()->remember), invalidate on the like event, not on the page render. Toggling twice quickly with a stale TTL is the classic bug report.
  • Testing. With RefreshDatabase, assert both the row and the returned state:
    $this->actingAs($user)->post("/posts/{$post->id}/like")->assertOk();
    $this->assertDatabaseHas('likes', [
        'likeable_id' => $post->id,
        'likeable_type' => $post->getMorphClass(),
        'user_id' => $user->id,
    ]);
    
    Use getMorphClass() rather than a hardcoded string — it survives a morph map change.
  • Unmaintained-package hygiene. Wrap the trait's public methods in one thin app-level trait (App\Concerns\HasLikes) so a future swap to a maintained package is a one-file change. Also add a smoke test that just calls like(), unlike(), and the count accessor — that's your early warning if a Laravel upgrade breaks the package.
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.
andydefer/laravel-notification
adachsoft/cli-process-tester
jhaskell13/laraflow
sebastienheyd/boilerplate-media-manager
ccast/tagixo-primix
pretendtrue/laravel-builder
sawmainek/laravel-scout-tntsearch-driver
tomshaw/laravel-calendar-table
dibakar/laravel-ownership
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo
stevenay/myanfont
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc