<?php
namespace App\Services;
use App\Events\MultipleSongsLiked;
use App\Events\MultipleSongsUnliked;
use App\Events\SongFavoriteToggled;
use App\Models\Contracts\Favoriteable;
use App\Models\Favorite;
use App\Models\Song;
use App\Models\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use InvalidArgumentException;
class FavoriteService
{
public function toggleFavorite(Favoriteable|Model $favoriteable, User $user): ?Favorite
{
$favorite = $favoriteable->favorites()->where('user_id', $user->id)->delete() === 0
? $favoriteable->favorites()->create(['user_id' => $user->id])
: null;
if ($favoriteable instanceof Song) {
event(new SongFavoriteToggled($favoriteable, (bool) $favorite, $user));
}
return $favorite;
}
/**
* Batch favorite multiple entities.
*
* @param Collection<int, Model> $entities
*/
public function batchFavorite(Collection $entities, User $user): void
{
$favorites = [];
foreach ($entities as $entity) {
if (!$entity instanceof Favoriteable) {
throw new InvalidArgumentException('Entity must implement Favoriteable interface.');
}
$favorites[] = [
'user_id' => $user->id,
'favoriteable_type' => $entity->getMorphClass(),
'favoriteable_id' => $entity->getKey(),
'created_at' => now(),
];
}
Favorite::query()->upsert($favorites, ['favoriteable_type', 'favoriteable_id', 'user_id']);
$songs = $entities->filter(static fn (Model $entity) => $entity instanceof Song);
if ($songs->isNotEmpty()) {
event(new MultipleSongsLiked($songs, $user));
}
}
/**
* Batch undo favorite for multiple entities.
*
* @param Collection<int, Model&Favoriteable> $entities
*/
public function batchUndoFavorite(Collection $entities, User $user): void
{
$grouped = $entities->groupBy(static fn (Model $entity) => $entity->getMorphClass());
DB::transaction(static function () use ($grouped, $user): void {
foreach ($grouped as $type => $items) {
Favorite::query()
->whereBelongsTo($user)
->where('favoriteable_type', $type)
->whereIn('favoriteable_id', $items->pluck('id'))
->delete();
}
});
$songs = $entities->filter(static fn (Model $entity) => $entity instanceof Song);
if ($songs->isNotEmpty()) {
event(new MultipleSongsUnliked($songs, $user));
}
}
}