2018-11-03 23:25:08 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace App\Services;
|
|
|
|
|
|
|
|
use App\Models\Playlist;
|
|
|
|
use App\Models\Rule;
|
|
|
|
use App\Models\Song;
|
|
|
|
use App\Models\User;
|
|
|
|
use App\Repositories\SongRepository;
|
|
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
|
|
use Illuminate\Database\Eloquent\Collection;
|
|
|
|
use RuntimeException;
|
|
|
|
|
|
|
|
class SmartPlaylistService
|
|
|
|
{
|
|
|
|
private const RULE_REQUIRES_USER_PREFIXES = ['interactions.'];
|
|
|
|
|
|
|
|
private $songRepository;
|
|
|
|
|
|
|
|
public function __construct(SongRepository $songRepository)
|
|
|
|
{
|
|
|
|
$this->songRepository = $songRepository;
|
|
|
|
}
|
|
|
|
|
2020-12-22 20:11:22 +00:00
|
|
|
/** @return Collection|array<Song> */
|
2018-11-03 23:25:08 +00:00
|
|
|
public function getSongs(Playlist $playlist): Collection
|
|
|
|
{
|
|
|
|
if (!$playlist->is_smart) {
|
2020-12-22 20:11:22 +00:00
|
|
|
throw new RuntimeException($playlist->name . ' is not a smart playlist.');
|
2018-11-03 23:25:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
$rules = $this->addRequiresUserRules($playlist->rules, $playlist->user);
|
|
|
|
|
2018-11-18 21:50:03 +00:00
|
|
|
return $this->buildQueryFromRules($rules)->get();
|
2018-11-03 23:25:08 +00:00
|
|
|
}
|
|
|
|
|
2018-11-18 21:50:03 +00:00
|
|
|
public function buildQueryFromRules(array $rules): Builder
|
2018-11-03 23:25:08 +00:00
|
|
|
{
|
2018-11-18 21:50:03 +00:00
|
|
|
$query = Song::query();
|
2018-11-03 23:25:08 +00:00
|
|
|
|
2018-11-18 21:50:03 +00:00
|
|
|
collect($rules)->each(static function (array $ruleGroup) use ($query): void {
|
|
|
|
$query->orWhere(static function (Builder $subQuery) use ($ruleGroup): void {
|
|
|
|
foreach ($ruleGroup['rules'] as $config) {
|
|
|
|
Rule::create($config)->build($subQuery);
|
|
|
|
}
|
|
|
|
});
|
2018-11-03 23:25:08 +00:00
|
|
|
});
|
2018-11-18 21:50:03 +00:00
|
|
|
|
|
|
|
return $query;
|
2018-11-03 23:25:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Some rules need to be driven by an additional "user" factor, for example play count, liked, or last played
|
|
|
|
* (basically everything related to interactions).
|
|
|
|
* For those, we create an additional "user_id" rule.
|
|
|
|
*
|
2020-12-22 20:11:22 +00:00
|
|
|
* @return array<mixed>
|
2018-11-03 23:25:08 +00:00
|
|
|
*/
|
2018-11-18 21:50:03 +00:00
|
|
|
public function addRequiresUserRules(array $rules, User $user): array
|
2018-11-03 23:25:08 +00:00
|
|
|
{
|
2018-11-18 21:50:03 +00:00
|
|
|
foreach ($rules as &$ruleGroup) {
|
|
|
|
$additionalRules = [];
|
2018-11-03 23:25:08 +00:00
|
|
|
|
2018-11-18 21:50:03 +00:00
|
|
|
foreach ($ruleGroup['rules'] as &$config) {
|
|
|
|
foreach (self::RULE_REQUIRES_USER_PREFIXES as $modelPrefix) {
|
|
|
|
if (starts_with($config['model'], $modelPrefix)) {
|
|
|
|
$additionalRules[] = $this->createRequireUserRule($user, $modelPrefix);
|
|
|
|
}
|
2018-11-03 23:25:08 +00:00
|
|
|
}
|
|
|
|
}
|
2018-11-18 21:50:03 +00:00
|
|
|
|
|
|
|
// make sure all those additional rules are unique.
|
|
|
|
$ruleGroup['rules'] = array_merge($ruleGroup['rules'], collect($additionalRules)->unique('model')->all());
|
2018-11-03 23:25:08 +00:00
|
|
|
}
|
|
|
|
|
2018-11-18 21:50:03 +00:00
|
|
|
return $rules;
|
2018-11-03 23:25:08 +00:00
|
|
|
}
|
|
|
|
|
2020-12-22 20:11:22 +00:00
|
|
|
/** @return array<mixed> */
|
2018-11-03 23:25:08 +00:00
|
|
|
private function createRequireUserRule(User $user, string $modelPrefix): array
|
|
|
|
{
|
|
|
|
return [
|
2020-12-22 20:11:22 +00:00
|
|
|
'model' => $modelPrefix . 'user_id',
|
2018-11-03 23:25:08 +00:00
|
|
|
'operator' => 'is',
|
|
|
|
'value' => [$user->id],
|
|
|
|
];
|
|
|
|
}
|
|
|
|
}
|