cybercog/laravel-love
Add reactions, likes, votes, and other “feelings” to any Eloquent model with Laravel Love. Flexible, enterprise-ready system inspired by GitHub/Facebook/Slack reactions. Includes migrations and APIs to make models reactable in minutes.
Installation:
composer require cybercog/laravel-love
php artisan migrate
Make a model reactable (e.g., Post):
php artisan love:setup-reactable Post
This generates a migration adding love_reactant_id to your model.
Make users reactable (e.g., User):
php artisan love:setup-reacterable User
This generates a migration adding love_reacter_id to the love_reactions table.
Use in code:
// User reacts to a Post
$user->reactTo($post, 'like');
// Check if user reacted
$user->hasReactedTo($post, 'like');
// Get reaction count
$post->reactionCount('like');
// In a controller or service
$post = Post::find(1);
$user = auth()->user();
// Add a reaction
$user->reactTo($post, 'like');
// Check reaction status
if ($user->hasReactedTo($post, 'like')) {
// User has liked
}
// Get total likes
$likeCount = $post->reactionCount('like');
Post, Comment):
use Cog\Laravel\Love\Traits\Reactable;
class Post extends Model
{
use Reactable;
}
User, Guest):
use Cog\Laravel\Love\Traits\Reacter;
class User extends Authenticatable
{
use Reacter;
}
Define custom reaction types in config/love.php:
'reaction_types' => [
'like' => [
'name' => 'Like',
'icon' => '❤️',
'weight' => 1,
],
'dislike' => [
'name' => 'Dislike',
'icon' => '👎',
'weight' => -1,
],
'laugh' => [
'name' => 'Laugh',
'icon' => '😂',
'weight' => 0.5,
],
],
// React with a custom rate (e.g., for sentiment analysis)
$user->reactTo($post, 'like', 0.8); // 80% "like"
// Check reaction rate
$rate = $user->getReactionRate($post, 'like');
// Get posts reacted by user
$posts = Post::whereReactedToBy($user)->get();
// Get posts with reactions between dates
$posts = Post::whereReactedToBetween(
now()->subDays(7),
now()
)->get();
// Get reactions for a post
$reactions = $post->reactions()->get();
// Get total reactions for a post
$total = $post->reactionTotal();
// Get weighted sum (e.g., for sentiment)
$weightedSum = $post->reactionWeightedSum();
// Get reaction counts by type
$counts = $post->reactionCounts();
// Recount all aggregates (e.g., after data import)
php artisan love:recount
// Recount for a specific model
php artisan love:recount --model=Post
// Example: Add reaction via API
Route::post('/posts/{post}/react', function (Post $post, Request $request) {
$user = auth()->user();
$type = $request->input('type');
$rate = $request->input('rate', null);
$user->reactTo($post, $type, $rate);
return response()->json(['success' => true]);
});
// Example: Toggle reaction
async function toggleReaction(postId, type) {
const response = await fetch(`/posts/${postId}/react`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
},
body: JSON.stringify({ type }),
});
const data = await response.json();
if (data.success) {
updateReactionUI(postId, type);
}
}
// Extend Reacter trait for custom logic
class User extends Authenticatable
{
use Reacter;
public function reactTo($reactant, $type, $rate = null)
{
// Custom logic before reacting
if ($this->isBanned()) {
throw new \Exception("Banned users cannot react.");
}
return parent::reactTo($reactant, $type, $rate);
}
}
// Listen for reaction events
ReactionCreated::listen(function ($reaction) {
// Send notification
Notification::send($reaction->reactant, new ReactionNotification($reaction));
});
Foreign Key Constraints:
love_reactions, ensure foreign keys are handled:
DB::statement('SET FOREIGN_KEY_CHECKS=0;');
// Delete records
DB::statement('SET FOREIGN_KEY_CHECKS=1;');
php artisan love:recount after bulk deletions to update aggregates.Rate Validation:
RATE_MIN (default: -10) and RATE_MAX (default: 10) will throw RateOutOfRange or RateInvalid.config/love.php:
'rate' => [
'min' => -5,
'max' => 5,
],
Queue Jobs:
$this->app->bind(\Cog\Laravel\Love\Reactant\Jobs\IncrementReactionAggregatesJob::class, function ($app) {
return new class extends \Cog\Laravel\Love\Reactant\Jobs\IncrementReactionAggregatesJob {
public function handle() {
parent::handle();
}
};
});
Model Caching:
$post->fresh(); // Refresh model from DB
Laravel Version Mismatches:
composer.json matches the package’s requirements. For example:
"require": {
"laravel/framework": "^10.0",
"cybercog/laravel-love": "^10.0"
}
Reaction Not Saving:
love_reacter_id column exists in the love_reactions table.Reacter trait is used on the user model.Aggregate Counters Stale:
php artisan love:recount or manually trigger the job:
\Cog\Laravel\Love\Reactant\Jobs\RebuildReactionAggregatesJob::dispatch();
Query Performance:
whereReactedToBy and whereReactedToBetween scopes sparingly on large datasets. Add indexes:
Schema::table('love_reactions', function (Blueprint $table) {
$table->index(['love_reactant_id', 'love_reacter_id']);
$table->index(['love_reacter_id', 'created_at']);
});
Custom Reaction Types Not Working:
config/love.php under reaction_types.Custom Reaction Types Dynamically:
// Add reaction types at runtime
$reactionTypes = config('love.reaction_types');
$reactionTypes['custom'] = [
'name' => 'Custom',
'icon' => '🎨',
'weight' => 0,
];
config(['love.reaction_types' => $reactionTypes]);
Bulk Reaction Assignment:
// Assign reactions to multiple users at once
$users = User::all();
$post = Post::find(1);
foreach ($users
How can I help you explore Laravel packages today?