toneflix/social-interactions
toneflix/social-interactions is a Laravel package that adds likes, comments, follows, bookmarks, and other social interactions to Eloquent models using reusable traits, ready-made migrations, and simple APIs for quick integration into engagement features.
This package adds social interaction primitives — likes/reactions, follows, bookmarks, comments — to your Eloquent models through reusable traits, so you don't hand-roll polymorphic pivot tables for every social feature. The 2.0 release is mostly a re-vendor: the install name and namespace both changed, and the floor is now Laravel 12/13 + PHP 8.3.
Install (fresh projects):
composer require toneflix/social-interactions:^2.0
php artisan vendor:publish --tag="social-interactions-config"
php artisan vendor:publish --tag="social-interactions-migrations"
php artisan migrate
If you're upgrading from 1.x, the release notes give you the exact path:
composer remove toneflix-code/social-interactions
composer require toneflix/social-interactions:^2.0
Then find/replace ToneflixCode\SocialInteractions → Toneflix\SocialInteractions in your use statements and in any published config file (the config file is not auto-rewritten — it contains hard-coded FQCNs).
Where to look first: the package README's trait list and the published migration. The migration tells you the table names, columns, and the polymorphic *_type/*_id shape — that's the source of truth for how you'll query it. Then read the trait source to confirm method names before wiring them into controllers.
First use case: add the actor trait to User and the target trait to the model you want to be likeable (e.g. Post), then:
$post->like(); // current authenticated user likes the post
$post->likes()->count();
$user->follow($author);
$user->isFollowing($author);
Confirm the exact method signatures in the trait source before assuming — the README is the primary reference and this release updated it for the new namespace.
Trait-on-both-sides is the core pattern. Social interactions are bidirectional: the actor (User) needs like(), following(), bookmarks(); the target (Post, Comment, Video) needs likes(), isLikedBy(), likers(). Put the actor trait on User only, and the target trait on each interactable model — not the reverse.
Attach interactions to any model via polymorphism. Because interactions are morph-mapped, you can make a new model interactable without a migration by adding the target trait. That's the main extension point: Post, Comment, Article, Video all share the same interaction tables.
Controller/API shape. The idiomatic flow is a thin controller that resolves the authenticated user and delegates:
public function store(Post $post)
{
$post->like(); // or ->like($request->user())
return response()->noContent();
}
public function destroy(Post $post)
{
$post->unlike();
return response()->noContent();
}
Toggles (toggleLike() if present) are tempting but make the endpoint non-idempotent — prefer explicit like/unlike for REST, keep toggle for UI buttons that already know current state.
Querying counts efficiently. Interaction counts are aggregate queries over a polymorphic table, so avoid $post->likes()->count() inside a loop. Use withCount() against the relationship or a loadCount() on the collection. For "did this user interact", prefer whereHas with the user constraint over loading all likers and filtering in PHP.
Follow graphs are self-referential. Follows are User → User, so the actor and target traits both land on the same model. Watch for recursive eager loads (with('followers.followers')) — these explode fast; only load what the view renders.
Testing. The dev stack moved to Pest 4 + Testbench 10.11/11.1, so if you're writing package-level or Pest feature tests against the traits, mirror the package's own matrix: test against both Laravel 12 and 13 if you support both. Use RefreshDatabase and a factory for the interactable model; assert on the relationship ($post->likes()->count()) rather than internal table rows so you're not coupled to the schema.
The 2.0 rename is a hard break, not a soft alias. There is no class_alias shim — the old ToneflixCode\SocialInteractions namespace is gone. Anything that didn't get updated (queued jobs, event listeners, Blade components referencing FQCNs, config arrays) will throw "class not found" at runtime, often lazily. Grep the whole codebase, not just app/:
grep -rn "ToneflixCode\\\\SocialInteractions" --exclude-dir=vendor .
Published config is the sneaky one. Republishing config overwrites your customizations. Diff the new stub against your published file before overwriting, and remember to hand-edit the namespace in it since the release notes call this out explicitly.
PHP/Laravel floor moved. Requiring ^8.3|^8.4 and Illuminate ^12|^13 means 2.0 won't install on Laravel 11 or PHP 8.2. If you're still on 11, stay on 1.1.1 until you upgrade the app — don't try to force the constraint.
Morph map discipline. If you use Relation::enforceMorphMap(), any new interactable model must be registered or the polymorphic writes will store FQCNs inconsistently with existing rows. Add the mapping before the first interaction is recorded for that model type.
Unique constraints and duplicate reactions. The migration should have a unique index on the interaction tuple (actor, target, type). If your app allows rapid double-taps, catch the QueryException on duplicate insert or wrap the call in an idempotent check — don't assume the package deduplicates for you.
Counts and soft deletes. If your interactable models use SoftDeletes, interaction rows survive the parent deletion. Decide whether orphaned interactions matter for your counts and clean up via a scheduled command or a deleting observer hook.
Extension points to prefer: (1) adding the target trait to new models — cheapest, no migration; (2) overriding the published config for table names; (3) binding a custom interaction model only if you need extra columns, which means extending the package model and updating the config's model reference. Avoid editing vendor files — the rename means you'll re-pull cleanly and lose those edits.
Debugging "why no likes": check (a) the authenticated guard the package resolves by default vs. the one your API uses, (b) morph map registration, (c) that the target model actually has the trait — a missing trait silently returns an empty relationship rather than erroring.
How can I help you explore Laravel packages today?