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 Like Laravel Package

zing/laravel-like

zing/laravel-like is a lightweight Laravel package for adding like and unlike behavior to Eloquent models. Use traits and polymorphic relationships to let users like any model, check liked status, and retrieve like counts through a simple, expressive API.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and publish its assets:

composer require laravel-interaction/interactions
php artisan vendor:publish --tag="interactions-config"
php artisan vendor:publish --tag="interactions-migrations"
php artisan migrate

Then wire the two traits into your models. The interactor (usually User) gets HasInteractions; anything that can be interacted with gets Interactionable:

use LaravelInteraction\Interaction\Concerns\HasInteractions;

class User extends Authenticatable
{
    use HasInteractions;
}
use LaravelInteraction\Interaction\Concerns\Interactionable;

class Post extends Model
{
    use Interactionable;
}

Where to look first: config/interactions.php is the source of truth — it defines the available interaction types (like, dislike, love, …), the models used, and the table names. Read it before writing any feature code, because every method name and validation rule flows from that array.

First use case — a like button:

// Controller
$user->toggleLike($post);

// Read side
$post->likesCount();
$post->isLikedBy($user);

If toggleLike() / likesCount() / isLikedBy() feel like magic, it's because the package generates them from the configured type names — like becomes like(), unlike(), toggleLike(), likes(), likesCount(), isLikedBy(). Add 'favorite' to the config and you get favorite(), favoritesCount(), isFavoritedBy(), etc., for free.

Implementation Patterns

Controller + form request for the toggle endpoint. Keep the interaction call thin and let authorization do the work:

public function toggle(Request $request, Post $post)
{
    $this->authorize('interact', $post);

    $result = $request->user()->toggleLike($post);

    return response()->json([
        'active' => $result,
        'count'  => $post->likesCount(),
    ]);
}

Returning the fresh count from the same request avoids a second round-trip and keeps the button state consistent.

Eager-load counts, never count per row. The single biggest day-to-day performance trap is calling likesCount() inside a Blade @foreach. Use withCount with the generated relations:

$posts = Post::query()
    ->withCount(['likes', 'favorites'])
    ->paginate();

Then $post->likes_count in the view (note the _count suffix, not the method).

Resolve "did this user interact?" once per collection. isLikedBy($user) is a query per model. For a feed of 50 posts, preload the viewer's interactions and check in memory:

$likedIds = $user->likes()
    ->where('interactionable_type', Post::class)
    ->pluck('interactionable_id')
    ->flip();

// in the loop
'is_liked' => isset($likedIds[$post->id]),

Custom interaction types as a first-class domain. Anything with a distinct semantic — bookmark, pin, react:sad — should be a config entry rather than a raw insert. Keep a PHP enum or constants class mirroring the config keys so controllers and Blade never hardcode strings:

enum InteractionType: string
{
    case Like = 'like';
    case Favorite = 'favorite';
}

Guard against typos by validating incoming type strings against array_keys(config('interactions.types')) when you expose a generic /interactions/{type} endpoint.

Compose with your own domain logic via events. Rather than editing vendor code, listen for the package's model events on Interaction (or observe the model resolved from config) to trigger notifications, activity log entries, or cache invalidation:

Interaction::created(function (Interaction $interaction) {
    Cache::forget("post:{$interaction->interactionable_id}:stats");
    // notify the post author, skip self-interactions
});

API resources. Shape the output in a PostResource so the client contract stays stable even if you swap the package out later:

'likes_count' => $this->whenCounted('likes'),
'is_liked'    => $this->when(isset($this->viewerLikedIds), fn () => ...),

Testing. Because state lives in a polymorphic pivot table, assert on the relationship rather than on the response body alone:

$user->like($post);

$this->assertTrue($post->isLikedBy($user));
$this->assertDatabaseHas('interactions', [
    'user_id' => $user->id,
    'interactionable_id' => $post->id,
    'interactionable_type' => $post->getMorphClass(),
    'type' => 'like',
]);

Gotchas and Tips

1.0.0 is the first stable tag — pin it. There's no prior release history to diff against, so treat the public API (trait method names, config keys, table schema) as newly frozen rather than battle-tested. Use "^1.0" and read the changelog before every minor bump; generated method names are derived from config strings, so a renamed type is effectively a breaking change for your call sites.

Config cache is a silent breaker. After adding a new interaction type, php artisan config:cache (or config:clear in dev) is mandatory — the dynamic methods are built from the config array, and a stale cache means the new methods simply don't exist. If a method "disappears," this is the first thing to check.

Publish order matters for migrations. Publish the migrations before running migrate, and if you already have an interactions table, check the published file doesn't collide. On existing projects, review the migration's index strategy — the polymorphic lookup needs a compound index on (interactionable_type, interactionable_id, type) and a unique constraint on (user_id, interactionable_type, interactionable_id, type) to prevent double-likes.

Toggles are not atomic. toggleLike() reads then writes. Two rapid clicks, or a double-submitted form, can produce duplicate rows unless the unique index exists — and if it does exist, the second write throws a QueryException instead of failing gracefully. Handle both: add the unique index and catch the integrity constraint violation in the controller, treating it as "already interacted." Disable the button client-side as well.

Guest viewers. isLikedBy(null) behavior depends on the guard setup — always guard with @auth or a null-check before calling it in Blade, otherwise you'll get a 500 on public pages.

Morph map consistency. If you use Relation::enforceMorphMap(), interactions written before you introduced the map store FQCNs and won't resolve. Either add the map before launch or write a data migration that rewrites interactionable_type values.

Cascade on delete. The package's migration may not declare onDelete('cascade') for the interacting user. Deleting a user can leave orphaned rows and, worse, null-out user_id if the column is nullable. Verify the FK definitions and add a cleanup job or observer for hard-deleted models.

Denormalized counts drift. If you add a likes_count column to posts for sorting, keep it in sync through the Interaction created/deleted events — not through the relation, which will still query. Reconcile periodically with a scheduled withCount refresh, because a missed event means permanently wrong sort order.

Extension points to prefer over forking: the config file (types, model bindings, table names), swapping the Interaction model for your own subclass (adds casts, scopes, activity-log hooks), and model events. Editing vendor/ will bite you on the next release given how new 1.0.0 is.

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.
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle