Installation
composer require turahe/laravel-likeable
Ensure your Laravel version is 11+ or 12+ and PHP is 8.3+.
Use the Trait
Apply the Likeable trait to your Eloquent model:
use Turahe\Likeable\Likeable;
class Post extends Model
{
use Likeable;
}
This auto-generates:
likes() relationship (many-to-many with users table).like(), unlike(), toggleLike() methods.isLikedBy() helper.First Use Case
// Toggle a user's like on a Post
$post = Post::find(1);
$post->toggleLike(auth()->id());
// Check if a user liked the Post
if ($post->isLikedBy(auth()->id())) {
echo "Liked!";
}
Migrations
Run the included migration (published via LikeableServiceProvider):
php artisan vendor:publish --provider="Turahe\Likeable\LikeableServiceProvider" --tag="migrations"
php artisan migrate
Basic Like Actions
// Like/unlike via user ID
$post->like($userId); // Add like
$post->unlike($userId); // Remove like
$post->toggleLike($userId); // Toggle state
// Like/unlike via authenticated user (if using Laravel auth)
$post->like(auth()->id());
Querying Liked Models
// Get all liked Posts for a user
$likedPosts = auth()->user()->likedPosts;
// Get Posts liked by a specific user
$user = User::find(1);
$user->likedPosts; // Collection of liked Posts
Counting Likes
$post->likesCount; // Returns integer count of likes
Customizing the Like Table
Override the default likeable table name in your model:
class Post extends Model
{
use Likeable;
protected $likeableTable = 'post_likes';
}
Customizing the Relationship
Extend the default likes() relationship:
public function likes()
{
return $this->belongsToMany(User::class, 'custom_like_table')
->withTimestamps()
->as('likes');
}
Adding Metadata to Likes
Extend the pivot table to store additional data (e.g., created_at):
// Migration for custom pivot table
Schema::create('post_likes', function (Blueprint $table) {
$table->id();
$table->foreignId('post_id')->constrained()->cascadeOnDelete();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->timestamps();
});
// Update the relationship in your model
public function likes()
{
return $this->belongsToMany(User::class, 'post_likes')
->withTimestamps();
}
Batch Liking/Unliking
// Like multiple posts at once
$postIds = [1, 2, 3];
$userId = auth()->id();
foreach ($postIds as $id) {
Post::find($id)->toggleLike($userId);
}
// Or use a query builder approach
Post::whereIn('id', $postIds)->each->toggleLike($userId);
Event Listeners Listen for like/unlike events (e.g., to trigger notifications):
// In EventServiceProvider
protected $listen = [
'Turahe\Likeable\Events\LikeAdded' => [
'App\Listeners\SendLikeNotification',
],
'Turahe\Likeable\Events\LikeRemoved' => [
'App\Listeners\SendUnlikeNotification',
],
];
API Responses Serialize like data in API responses:
return PostResource::make($post)->additional([
'is_liked' => $post->isLikedBy(auth()->id()),
'likes_count' => $post->likesCount,
]);
Migration Conflicts
likeable table, the package’s migration will fail. Solution: Publish the migration first (vendor:publish --tag="migrations") and let the package handle it.User ID Mismatch
user_id in the pivot table. If your users table uses a different column (e.g., uuid), override the relationship:
public function likes()
{
return $this->belongsToMany(User::class, 'likeable')
->using(Likeable::class)
->withPivot('user_uuid'); // Custom pivot column
}
Caching Issues
likesCount and isLikedBy() may return stale data if cached. Solution: Clear the cache after bulk operations:
Cache::forget("post:{$post->id}:likes_count");
Mass Assignment Risks
$guarded = ['user_id'] to your model or use $fillable.Soft Deletes
users table uses soft deletes, ensure the relationship respects it:
public function likes()
{
return $this
->belongsToMany(User::class, 'likeable')
->withTimestamps()
->withPivot('created_at')
->whereNull('users.deleted_at');
}
Check Pivot Table Verify the pivot table structure:
php artisan schema:dump
Or inspect directly:
\DB::select("PRAGMA table_info(likeable)");
Log Like Events Temporarily log like actions to debug:
event(new \Turahe\Likeable\Events\LikeAdded($this, $userId));
// Add to EventServiceProvider
Test Relationships Validate the relationship in Tinker:
php artisan tinker
>> $post = App\Models\Post::find(1);
>> $post->likes
Custom Like Logic
Override the toggleLike() method for custom behavior:
public function toggleLike($userId)
{
if ($this->isLikedBy($userId)) {
$this->unlike($userId);
event(new LikeRemoved($this, $userId));
} else {
$this->like($userId);
event(new LikeAdded($this, $userId));
}
// Custom logic (e.g., increment a counter)
}
Add Like Conditions Restrict likes to specific users (e.g., only admins):
public function like($userId)
{
$user = User::find($userId);
if (!$user->isAdmin()) {
throw new \Exception("Only admins can like this.");
}
$this->likes()->attach($userId);
}
Custom Pivot Data
Store additional data (e.g., like_reason):
$post->likes()->attach($userId, ['reason' => 'Great content!']);
Rate Limiting Prevent spam likes with middleware:
// app/Http/Middleware/PreventLikeSpam.php
public function handle($request, Closure $next)
{
$user = auth()->user();
$likes = $user->likes()->where('created_at', '>', now()->subMinutes(5))->count();
if ($likes > 10) {
abort(429, 'Too many likes!');
}
return $next($request);
}
Testing
Use the Likeable trait in tests:
public function test_like_toggle()
{
$post = new Post();
$post->toggleLike(1);
$this->assertTrue($post->isLikedBy(1));
$post->toggleLike(1);
$this->assertFalse($post->isLikedBy(1));
}
How can I help you explore Laravel packages today?