refactor: improve performance action DX (#1130)

This commit is contained in:
Kyrch
2026-03-16 21:26:27 -03:00
committed by GitHub
parent 5839dfc4c2
commit a230b803b7
5 changed files with 89 additions and 120 deletions
@@ -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));
}
}
@@ -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
);
}
}
+15 -1
View File
@@ -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');
});
}
};