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

Interactions Laravel Package

laravel-interaction/interactions

Laravel Interaction lets Eloquent models interact polymorphically—likes, favorites, votes, follows, bookmarks, subscriptions, and more. Attach reusable interaction types to any model, query who interacted and what they interacted with, and build social features with clean, extendable APIs.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish

composer require laravel-interaction/interactions
php artisan vendor:publish --provider="LaravelInteraction\Interaction\InteractionServiceProvider"
php artisan migrate

Publish both the migrations and the config file — the config lets you swap the interaction model and rename tables, and it is much harder to retrofit once rows exist.

Add the traits

use LaravelInteraction\Interaction\Concerns\CanInteract;      // the actor (usually User)
use LaravelInteraction\Interaction\Concerns\HasInteractions;  // the subject (Post, Video, Comment…)

class User extends Model { use CanInteract; }
class Post extends Model { use HasInteractions; }

Look here first, in this order

  1. config/interactions.php — table name, model binding, morph key type (morphs vs uuidMorphs).
  2. The published migration — check the unique index and the polymorphic columns; that index defines the package's behaviour more than any doc does.
  3. The trait files in src/Concerns/ — the public API is small and the method names are the fastest way to understand the mental model. Confirm exact method names against the README for your pinned version rather than relying on memory.

First use case

Record a single interaction from a user on a post, then read the state back on a page render:

$user->interact($post, 'like');        // actor → subject, typed
$user->hasInteracted($post, 'like');   // boolean, for "did I like this?" buttons
$post->interactors('like');            // who liked it
$post->interactions()->count();        // how many, all types

If that loop works end to end, everything else in the package is a variation on it.

Implementation Patterns

Treat type as a first-class enum. The package stores interactions in one table discriminated by a string type. Back it with a PHP enum and cast it, then wrap the trait methods in small domain methods so controllers never pass raw strings:

enum InteractionType: string {
    case Like = 'like';
    case Bookmark = 'bookmark';
    case View = 'view';
}

// In a service or the model itself
public function like(Post $post): void { $this->interact($post, InteractionType::Like->value); }

This is the single highest-leverage decision: it keeps where('type', ...) scattered everywhere from leaking into your codebase.

Enforce a morph map early. If you let FQCNs land in interactionable_type, renaming or moving a model silently orphans every row. Register Relation::enforceMorphMap([...]) in a service provider before the first interaction is written — migrating existing rows afterwards is a data backfill, not a config change.

Idempotency for toggles. Like buttons get double-clicked and requests get retried. The unique index will reject the duplicate, so either:

try {
    $user->interact($post, InteractionType::Like->value);
} catch (QueryException $e) {
    // already liked — safe to ignore
}

…or use firstOrCreate/insertOrIgnore semantics if you wrap the package rather than calling the trait directly. Never rely on a hasInteracted() check followed by a write; it's a race.

Batch the per-user state. Rendering a feed of 50 posts with an isLikedBy-style call per post is 50 queries. Either eager-load the relation constrained to the current user, or pre-resolve a set of interacted IDs in one query and pass it to your view/resource. This is the most common performance regression with this package.

Pair counts with withCount rather than calling ->count() on a relation inside a loop, and consider a cached counter column on the subject for hot listings — then invalidate on interact/uninteract via a model observer.

Shape API resources consistently. Expose is_liked, likes_count, can_like from one resource class so the frontend never guesses at the shape and you keep the N+1 fix in one place.

Test with factories and assertDatabaseHas. Assert against the interactions table, not just the boolean helper — the helper can be satisfied by a stale cache while the row is missing.

Gotchas and Tips

  • Version matrix moved in 3.5.0. This release adds Laravel 13, PHP 8.5, and PHPUnit 12 support and bumps the orchestra/testbench constraint. Nothing in your application code changes, but your CI matrix and any pinned testbench/phpunit dev dependencies must line up, or the suite will fail on constraint resolution rather than on real errors. If you're still on Laravel 10/11 the previous 3.x line is fine; nothing forces the upgrade.
  • Morph map drift is silent. Changing an alias after rows exist means existing interactions simply stop matching. Treat aliases as immutable once written.
  • Soft deletes create ghost interactions. Deleted users still count toward interactions()->count() unless you constrain the relation. Decide deliberately whether counts are "all time" or "live only", and put it in a scope so it's consistent.
  • Multiple laravel-interaction packages share a table by design. If you install like, bookmark, and view alongside this one, verify whether they share the same table with a type discriminator or create their own — and check the config for each. Getting this wrong produces duplicate migrations and confusing counts.
  • UUID/ULID subjects need config, not a code change. Set the morph key type in the config before migrating; switching after the fact means recreating the table.
  • Unique-violation exceptions are the expected path under concurrency, not a bug. Catch QueryException around the write; don't let a double-tap 500 the request.
  • Debugging "count is wrong": query the table directly before touching application code — SELECT type, count(*) FROM interactions GROUP BY type almost always reveals a type-string typo or a duplicated write.
  • Extension points: swap the interaction model via config and subclass it for custom casts or scopes; use a model observer on the interaction model for counter-cache updates and activity feeds rather than editing the trait. Avoid vendor/ edits — the traits are used directly into your models and trait collisions with your own interactions() method are the most common breakage.

Overall, 3.5.0 is a compatibility release: upgrade for the Laravel 13 / PHP 8.5 support if you're moving your stack forward, otherwise there is no functional reason to rush.

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.
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
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