mirror of
https://github.com/AnimeThemes/animethemes-server.git
synced 2026-07-11 01:24:46 +02:00
refactor: improve performance action DX (#1130)
This commit is contained in:
@@ -21,32 +21,20 @@ class ManageSongPerformances
|
||||
{
|
||||
protected Song $song;
|
||||
|
||||
/** @var Collection<int, array{alias: string|null, as: string|null}> */
|
||||
protected Collection $groups;
|
||||
|
||||
/** @var Collection<int, non-empty-array<string, mixed>> */
|
||||
protected Collection $performances;
|
||||
|
||||
/** @var Collection<int, array<string, mixed>> */
|
||||
protected Collection $memberships;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->groups = new Collection();
|
||||
$this->performances = new Collection();
|
||||
$this->memberships = new Collection();
|
||||
}
|
||||
|
||||
public function forSong(Song|int $song): static
|
||||
{
|
||||
public function __construct(
|
||||
Song|int $song,
|
||||
/** @var Collection<int, non-empty-array<string, mixed>> */
|
||||
protected Collection $performances = new Collection(),
|
||||
/** @var Collection<int, array<string, mixed>> */
|
||||
protected Collection $memberships = new Collection(),
|
||||
) {
|
||||
$this->song = $song instanceof Song ? $song : Song::query()->find($song);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addSingleArtist(int $artist, ?string $alias = null, ?string $as = null): static
|
||||
{
|
||||
$this->performances->push([
|
||||
Performance::ATTRIBUTE_SONG => $this->song->getKey(),
|
||||
Performance::ATTRIBUTE_ARTIST_TYPE => Relation::getMorphAlias(Artist::class),
|
||||
Performance::ATTRIBUTE_ARTIST_ID => $artist,
|
||||
Performance::ATTRIBUTE_ALIAS => filled($alias) ? trim($alias) : null,
|
||||
@@ -56,38 +44,29 @@ class ManageSongPerformances
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addGroupData(int $group, ?string $alias = null, ?string $as = null): static
|
||||
{
|
||||
$this->groups->put($group, [
|
||||
Performance::ATTRIBUTE_ALIAS => filled($alias) ? trim($alias) : null,
|
||||
Performance::ATTRIBUTE_AS => filled($as) ? trim($as) : null,
|
||||
]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addMembership(int $group, int $member, ?string $alias = null, ?string $as = null): static
|
||||
{
|
||||
$membership = Membership::query()->firstOrCreate([
|
||||
public function addMembership(
|
||||
int $group,
|
||||
int $member,
|
||||
?string $alias = null,
|
||||
?string $as = null,
|
||||
?string $groupAlias = null,
|
||||
?string $groupAs = null
|
||||
): static {
|
||||
$membership = Membership::query()->firstOrCreate($attributes = [
|
||||
Membership::ATTRIBUTE_ARTIST => $group,
|
||||
Membership::ATTRIBUTE_MEMBER => $member,
|
||||
Membership::ATTRIBUTE_ALIAS => filled($alias) ? trim($alias) : null,
|
||||
Membership::ATTRIBUTE_AS => filled($as) ? trim($as) : null,
|
||||
]);
|
||||
|
||||
$this->memberships->put(
|
||||
$membership->getKey(),
|
||||
Arr::only(
|
||||
$membership->attributesToArray(),
|
||||
[Membership::ATTRIBUTE_ARTIST, Membership::ATTRIBUTE_MEMBER, Membership::ATTRIBUTE_ALIAS, Membership::ATTRIBUTE_AS]
|
||||
)
|
||||
);
|
||||
$this->memberships->put($membership->getKey(), $attributes);
|
||||
|
||||
$this->performances->push([
|
||||
Performance::ATTRIBUTE_SONG => $this->song->getKey(),
|
||||
Performance::ATTRIBUTE_ARTIST_TYPE => $membership->getMorphClass(),
|
||||
Performance::ATTRIBUTE_ARTIST_ID => $membership->getKey(),
|
||||
Performance::ATTRIBUTE_ALIAS => Arr::get($this->groups->get($group), Performance::ATTRIBUTE_ALIAS),
|
||||
Performance::ATTRIBUTE_AS => Arr::get($this->groups->get($group), Performance::ATTRIBUTE_AS),
|
||||
Performance::ATTRIBUTE_ALIAS => filled($groupAlias) ? trim($groupAlias) : null,
|
||||
Performance::ATTRIBUTE_AS => filled($groupAs) ? trim($groupAs) : null,
|
||||
]);
|
||||
|
||||
return $this;
|
||||
@@ -98,59 +77,24 @@ class ManageSongPerformances
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
$this->performances = $this->performances->map(
|
||||
fn (array $performance): array => [
|
||||
...$performance,
|
||||
Performance::ATTRIBUTE_SONG => $this->song->getKey(),
|
||||
]
|
||||
$new = collect($this->performances)
|
||||
->keyBy(fn (array $p): string => $p[Performance::ATTRIBUTE_ARTIST_TYPE].':'.$p[Performance::ATTRIBUTE_ARTIST_ID]);
|
||||
|
||||
$existing = Performance::query()
|
||||
->whereBelongsTo($this->song)
|
||||
->get()
|
||||
->keyBy(fn (Performance $p): string => $p->artist_type.':'.$p->artist_id);
|
||||
|
||||
$models = $new->map(
|
||||
fn (array $performance) => Performance::query()->updateOrCreate(
|
||||
Arr::only($performance, [Performance::ATTRIBUTE_SONG, Performance::ATTRIBUTE_ARTIST_TYPE, Performance::ATTRIBUTE_ARTIST_ID]),
|
||||
Arr::only($performance, [Performance::ATTRIBUTE_ALIAS, Performance::ATTRIBUTE_AS])
|
||||
)
|
||||
);
|
||||
|
||||
// Multiple queries because upsert does not dispatch an event.
|
||||
foreach ($this->performances->all() as $performance) {
|
||||
Performance::query()->updateOrCreate(
|
||||
Arr::only($performance, [Performance::ATTRIBUTE_SONG, Performance::ATTRIBUTE_ARTIST_TYPE, Performance::ATTRIBUTE_ARTIST_ID]),
|
||||
[
|
||||
Performance::ATTRIBUTE_ALIAS => Arr::get($performance, Performance::ATTRIBUTE_ALIAS),
|
||||
Performance::ATTRIBUTE_AS => Arr::get($performance, Performance::ATTRIBUTE_AS),
|
||||
]
|
||||
);
|
||||
}
|
||||
$existing->diffKeys($new)->each->delete();
|
||||
|
||||
// Delete membership performances that are not in the new list.
|
||||
$membershipPerformances = Performance::query()
|
||||
->whereBelongsTo($this->song)
|
||||
->where(Performance::ATTRIBUTE_ARTIST_TYPE, Relation::getMorphAlias(Membership::class))
|
||||
->whereNotIn(
|
||||
Performance::ATTRIBUTE_ARTIST_ID,
|
||||
$this->performances->filter(fn (array $performance): bool => $performance[Performance::ATTRIBUTE_ARTIST_TYPE] === Relation::getMorphAlias(Membership::class))
|
||||
->map(fn (array $performanceMembership) => Arr::get($performanceMembership, Performance::ATTRIBUTE_ARTIST_ID))
|
||||
->all(),
|
||||
);
|
||||
|
||||
$membershipPerformances->each(fn (Performance $performance) => $performance->delete());
|
||||
|
||||
// Delete solo performances that are not in the new list.
|
||||
$soloPerformances = Performance::query()
|
||||
->whereBelongsTo($this->song)
|
||||
->where(Performance::ATTRIBUTE_ARTIST_TYPE, Relation::getMorphAlias(Artist::class))
|
||||
->whereNotIn(
|
||||
Performance::ATTRIBUTE_ARTIST_ID,
|
||||
$this->performances->filter(fn (array $performance): bool => $performance[Performance::ATTRIBUTE_ARTIST_TYPE] === Relation::getMorphAlias(Artist::class))
|
||||
->map(fn (array $solo) => Arr::get($solo, Performance::ATTRIBUTE_ARTIST_ID))
|
||||
->all(),
|
||||
);
|
||||
|
||||
$soloPerformances->each(fn (Performance $performance) => $performance->delete());
|
||||
|
||||
Performance::withoutEvents(function (): void {
|
||||
foreach ($this->performances->all() as $index => $performance) {
|
||||
Performance::query()
|
||||
->whereBelongsTo($this->song)
|
||||
->where(Performance::ATTRIBUTE_ARTIST_TYPE, Arr::get($performance, Performance::ATTRIBUTE_ARTIST_TYPE))
|
||||
->where(Performance::ATTRIBUTE_ARTIST_ID, Arr::get($performance, Performance::ATTRIBUTE_ARTIST_ID))
|
||||
->update([Performance::ATTRIBUTE_RELEVANCE => $index + 1]);
|
||||
}
|
||||
});
|
||||
Performance::setNewOrder($models->pluck(Performance::ATTRIBUTE_ID)->all());
|
||||
|
||||
// Update artist_member table to match memberships
|
||||
ArtistMember::query()->upsert(
|
||||
@@ -214,25 +158,16 @@ class ManageSongPerformances
|
||||
*/
|
||||
protected function syncSongArtist(): void
|
||||
{
|
||||
$songArtists = [];
|
||||
foreach ($this->performances->all() as $performance) {
|
||||
if ($performance[Performance::ATTRIBUTE_ARTIST_TYPE] === Relation::getMorphAlias(Membership::class)) {
|
||||
$groupOrArtist = Arr::get(
|
||||
$this->memberships->get(Arr::get($performance, Performance::ATTRIBUTE_ARTIST_ID)),
|
||||
Membership::ATTRIBUTE_ARTIST
|
||||
);
|
||||
} else {
|
||||
$groupOrArtist = Arr::get($performance, Performance::ATTRIBUTE_ARTIST_ID);
|
||||
}
|
||||
$songArtists = $this->performances->mapWithKeys(function (array $performance): array {
|
||||
$artistId = $performance[Performance::ATTRIBUTE_ARTIST_TYPE] === Relation::getMorphAlias(Membership::class)
|
||||
? Arr::get($this->memberships->get($performance[Performance::ATTRIBUTE_ARTIST_ID]), Membership::ATTRIBUTE_ARTIST)
|
||||
: $performance[Performance::ATTRIBUTE_ARTIST_ID];
|
||||
|
||||
$songArtists[$groupOrArtist] = [
|
||||
ArtistSong::ATTRIBUTE_ALIAS => Arr::get($performance, Performance::ATTRIBUTE_ALIAS),
|
||||
ArtistSong::ATTRIBUTE_AS => Arr::get($performance, Performance::ATTRIBUTE_AS),
|
||||
return [
|
||||
$artistId => Arr::only($performance, [Performance::ATTRIBUTE_ALIAS, Performance::ATTRIBUTE_AS]),
|
||||
];
|
||||
}
|
||||
|
||||
ArtistSong::withoutEvents(function () use ($songArtists): void {
|
||||
$this->song->artists()->sync($songArtists);
|
||||
});
|
||||
|
||||
ArtistSong::withoutEvents(fn () => $this->song->artists()->sync($songArtists));
|
||||
}
|
||||
}
|
||||
|
||||
+6
-9
@@ -58,7 +58,7 @@ class PerformanceSongRelationManager extends PerformanceRelationManager
|
||||
|
||||
$song->load(Song::RELATION_PERFORMANCE_ARTISTS);
|
||||
|
||||
$performances = $song->performances;
|
||||
$performances = $song->performances->sortBy(Performance::ATTRIBUTE_RELEVANCE);
|
||||
|
||||
$artists = [];
|
||||
$memberships = [];
|
||||
@@ -105,9 +105,7 @@ class PerformanceSongRelationManager extends PerformanceRelationManager
|
||||
return;
|
||||
}
|
||||
|
||||
$action = new ManageSongPerformances();
|
||||
|
||||
$action->forSong($song);
|
||||
$action = new ManageSongPerformances($song);
|
||||
|
||||
foreach ($data as $artist) {
|
||||
$groupOrArtist = intval(Arr::get($artist, Artist::ATTRIBUTE_ID));
|
||||
@@ -123,11 +121,8 @@ class PerformanceSongRelationManager extends PerformanceRelationManager
|
||||
|
||||
$group = $groupOrArtist;
|
||||
|
||||
$action->addGroupData(
|
||||
$group,
|
||||
Arr::get($artist, Performance::ATTRIBUTE_ALIAS),
|
||||
Arr::get($artist, Performance::ATTRIBUTE_AS),
|
||||
);
|
||||
$groupAlias = Arr::get($artist, Performance::ATTRIBUTE_ALIAS);
|
||||
$groupAs = Arr::get($artist, Performance::ATTRIBUTE_AS);
|
||||
|
||||
foreach (Arr::get($artist, PerformanceForm::REPEATER_MEMBERSHIPS) as $membership) {
|
||||
$action->addMembership(
|
||||
@@ -135,6 +130,8 @@ class PerformanceSongRelationManager extends PerformanceRelationManager
|
||||
intval(Arr::get($membership, Membership::ATTRIBUTE_MEMBER)),
|
||||
Arr::get($membership, Membership::ATTRIBUTE_ALIAS),
|
||||
Arr::get($membership, Membership::ATTRIBUTE_AS),
|
||||
$groupAlias,
|
||||
$groupAs
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,15 @@ use App\Models\BaseModel;
|
||||
use App\Models\Wiki\Artist;
|
||||
use App\Models\Wiki\Song;
|
||||
use Database\Factories\Wiki\Song\PerformanceFactory;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use OwenIt\Auditing\Auditable as HasAudits;
|
||||
use OwenIt\Auditing\Contracts\Auditable;
|
||||
use Spatie\EloquentSortable\Sortable;
|
||||
use Spatie\EloquentSortable\SortableTrait;
|
||||
|
||||
/**
|
||||
* @property int $performance_id
|
||||
@@ -35,11 +38,12 @@ use OwenIt\Auditing\Contracts\Auditable;
|
||||
*
|
||||
* @method static PerformanceFactory factory(...$parameters)
|
||||
*/
|
||||
class Performance extends BaseModel implements Auditable, SoftDeletable
|
||||
class Performance extends BaseModel implements Auditable, SoftDeletable, Sortable
|
||||
{
|
||||
use HasAudits;
|
||||
use HasFactory;
|
||||
use SoftDeletes;
|
||||
use SortableTrait;
|
||||
use Submitable;
|
||||
|
||||
final public const string TABLE = 'performances';
|
||||
@@ -102,6 +106,11 @@ class Performance extends BaseModel implements Auditable, SoftDeletable
|
||||
Performance::ATTRIBUTE_RELEVANCE,
|
||||
];
|
||||
|
||||
public $sortable = [
|
||||
'order_column_name' => Performance::ATTRIBUTE_RELEVANCE,
|
||||
'sort_when_creating' => false,
|
||||
];
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return strval($this->getKey());
|
||||
@@ -117,6 +126,11 @@ class Performance extends BaseModel implements Auditable, SoftDeletable
|
||||
return $this->artist_type === Relation::getMorphAlias(Membership::class);
|
||||
}
|
||||
|
||||
public function buildSortQuery(): Builder
|
||||
{
|
||||
return static::query()->whereBelongsTo($this->song);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BelongsTo<Song, $this>
|
||||
*/
|
||||
|
||||
@@ -72,7 +72,7 @@ class ThemeQuery extends ElasticQuery
|
||||
'bool' => [
|
||||
'boost' => 0.7,
|
||||
'should' => $this->createTextQuery('synonym_slug', $criteria->getTerm()),
|
||||
]
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('performances', function (Blueprint $table) {
|
||||
$table->index('song_id');
|
||||
$table->dropUnique('unique_performance');
|
||||
$table->unique(['song_id', 'artist_type', 'artist_id', 'deleted_at'], 'unique_performance');
|
||||
$table->dropIndex('performances_song_id_index');
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user