Weave Code
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 Love Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require cybercog/laravel-love
    php artisan migrate
    

    Run migrations to create love_reactions, love_reaction_totals, and love_reactant_reaction_totals tables.

  2. Enable Reactions on a Model: Use the make:reactable Artisan command:

    php artisan make:reactable Post
    

    This generates a migration for the love_reactant_id column and adds the Reactable trait to your model.

  3. Define Reaction Types: In your Post model, define allowed reactions (e.g., like, dislike):

    use Cog\Laravel\Love\Traits\Reactable;
    
    class Post extends Model
    {
        use Reactable;
    
        protected $reactable = [
            'like', 'dislike', 'love', 'haha'
        ];
    }
    
  4. First Reaction: In a controller or service, react to a post:

    $post = Post::find(1);
    $user = auth()->user();
    $user->reactTo($post, 'like');
    

Key First Use Case

Display Reaction Counts:

$post = Post::find(1);
echo $post->reactionsCount; // Total reactions
echo $post->reactionsTotal('like'); // Count for 'like'

Implementation Patterns

Core Workflows

1. Reacting to Models

  • Basic Reaction:
    $user->reactTo($post, 'like');
    
  • Weighted Reactions (e.g., rating systems):
    $user->reactTo($post, 'rate', 4.5); // Rate between 1-5
    
  • Check Existing Reactions:
    if ($user->hasReactedTo($post, 'like')) {
        $user->removeReaction($post, 'like');
    }
    

2. Querying Reactable Models

  • Scope Reactions:
    $likedPosts = Post::whereReactedTo('like')->get();
    $postsReactedByUser = Post::whereReactedToBy(auth()->user())->get();
    
  • Filter by Reaction Range (for weighted reactions):
    $highRatedPosts = Post::whereReactedToBetween('rate', 4, 5)->get();
    

3. Aggregates and Caching

  • Recount Reactions (e.g., after bulk updates):
    php artisan love:recount --queue-connection=redis
    
  • Access Aggregates:
    $post->reactionsTotal('like'); // Cached count
    $post->reactionsAverage('rate'); // Average rating
    

4. Custom Reaction Types

  • Extend the ReactionType class or use the reactable array to define custom types:
    protected $reactable = [
        'upvote' => ['icon' => '↑', 'weight' => 1],
        'downvote' => ['icon' => '↓', 'weight' => -1],
    ];
    

5. API Integration

  • React via API:
    Route::post('/posts/{post}/react', function (Post $post) {
        request()->user()->reactTo($post, request('type'), request('rate'));
        return response()->json(['success' => true]);
    });
    
  • Return Reaction Data:
    return response()->json([
        'reactions' => $post->reactions,
        'counts' => $post->reactionsTotals,
    ]);
    

Integration Tips

1. Database Optimization

  • Use indexes on love_reactions table for reactant_id, reacter_id, and type.
  • Configure queue connections for love:recount to avoid blocking requests:
    'queue-connection' => env('QUEUE_CONNECTION', 'database'),
    

2. Frontend Integration

  • React Icons:
    @foreach($post->reactable as $type)
        <button onclick="react('{{ $type }}')">
            {{ $post->reactionsTotal($type) }} {{ $type }}
        </button>
    @endforeach
    
  • JavaScript:
    function react(type) {
        fetch(`/posts/${postId}/react`, {
            method: 'POST',
            body: JSON.stringify({ type }),
            headers: { 'Content-Type': 'application/json' }
        });
    }
    

3. Testing

  • Unit Tests:
    public function test_user_can_react_to_post()
    {
        $user = User::factory()->create();
        $post = Post::factory()->create();
    
        $user->reactTo($post, 'like');
        $this->assertDatabaseHas('love_reactions', [
            'reactant_id' => $post->id,
            'reacter_id' => $user->id,
            'type' => 'like',
        ]);
    }
    
  • Feature Tests:
    public function test_reaction_counts_are_cached()
    {
        $post = Post::factory()->create();
        $user = User::factory()->create();
    
        $user->reactTo($post, 'like');
        $this->assertEquals(1, $post->fresh()->reactionsTotal('like'));
    }
    

4. Advanced: Custom Aggregates

  • Extend the ReactionAggregate class to add custom metrics (e.g., "net sentiment"):
    use Cog\Laravel\Love\ReactionAggregate;
    
    class SentimentAggregate extends ReactionAggregate
    {
        public function calculate()
        {
            $positive = $this->reactions->where('type', 'like')->count();
            $negative = $this->reactions->where('type', 'dislike')->count();
            return $positive - $negative;
        }
    }
    
  • Register in config/love.php:
    'aggregates' => [
        'sentiment' => \App\Aggregates\SentimentAggregate::class,
    ],
    

Gotchas and Tips

Pitfalls

  1. Foreign Key Constraints:

    • If you manually delete records from love_reactions, ensure foreign keys are handled:
      DB::statement('SET FOREIGN_KEY_CHECKS=0;');
      // Delete records
      DB::statement('SET FOREIGN_KEY_CHECKS=1;');
      
    • Fix: Use removeReaction() instead of raw deletions.
  2. Rate Validation:

    • Weighted reactions (e.g., ratings) must adhere to RATE_MIN and RATE_MAX (default: 1 and 5).
    • Error Handling:
      try {
          $user->reactTo($post, 'rate', 6); // Throws RateOutOfRange
      } catch (\Cog\Contracts\Love\Reaction\Exceptions\RateOutOfRange $e) {
          return response()->json(['error' => 'Rate must be between 1 and 5'], 400);
      }
      
  3. Queue Delays:

    • Aggregates (reactionsTotal, reactionsAverage) are updated asynchronously. For real-time needs:
      • Use sync queue connection during development:
        'queue-connection' => 'sync',
        
      • Or manually trigger recounts:
        php artisan love:recount
        
  4. Model Caching:

    • Eloquent models cache reactionsTotal and reactionsCount. To refresh:
      $post->refreshReactionsTotals();
      
    • Tip: Use fresh() to bypass cache:
      $post->fresh()->reactionsTotal('like');
      
  5. Migration Conflicts:

    • If you rename the love_reactant_id column, drop the foreign key first:
      Schema::table('love_reactions', function (Blueprint $table) {
          $table->dropForeign(['reactant_id']);
      });
      

Debugging Tips

  1. Log Reactions:

    • Enable query logging in config/database.php:
      'log_queries' => true,
      
    • Check Laravel logs for SQL queries during reactTo or removeReaction.
  2. Queue Monitoring:

    • Monitor love_reaction_aggregates_job and love_rebuild_aggregates_job in Laravel Horizon or queue workers:
      php artisan queue:work --queue=reactant-aggregates
      
  3. Common Issues:

    • "Reaction not found": Ensure reacter_id and reactant_id are
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.
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle
atriumphp/atrium
sandermuller/package-boost-laravel
sandermuller/boost-skills
redaxo/core
yusufgenc/filament-api-forge
l3aro/rating-star-for-filament
leek/filament-subtenant-scope
anil/file-picker
broqit/fields-ai