feat: replace some attributes with casts

This commit is contained in:
Phan An 2024-06-04 13:10:44 +02:00
parent 90a47d59b5
commit 4a10aa9915
8 changed files with 92 additions and 43 deletions

View file

@ -0,0 +1,28 @@
<?php
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
class SongLyricsCast implements CastsAttributes
{
/** @param string|null $value */
public function get(Model $model, string $key, mixed $value, array $attributes): string
{
if (!$value) {
return '';
}
// Since we're displaying the lyrics using <pre>, replace breaks with newlines and strip all tags.
$value = strip_tags(preg_replace('#<br\s*/?>#i', PHP_EOL, $value));
// also remove the timestamps that often come with LRC files
return preg_replace('/\[\d{2}:\d{2}.\d{2}]\s*/m', '', $value);
}
public function set(Model $model, string $key, mixed $value, array $attributes): mixed
{
return $value;
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace App\Casts;
use App\Enums\SongStorageType;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
class SongStorageCast implements CastsAttributes
{
/** @param string|null $value */
public function get(Model $model, string $key, mixed $value, array $attributes): SongStorageType
{
return SongStorageType::tryFrom($value) ?? SongStorageType::LOCAL;
}
/** @param SongStorageType|string|null $value */
public function set(Model $model, string $key, mixed $value, array $attributes): string
{
$type = $value instanceof SongStorageType ? $value : SongStorageType::tryFrom($value);
return $type->value;
}
}

View file

@ -0,0 +1,28 @@
<?php
namespace App\Casts;
use App\Models\Song;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
class SongTitleCast implements CastsAttributes
{
/**
* @param Song $model
* @param string|null $value
*/
public function get(Model $model, string $key, mixed $value, array $attributes): string
{
// If the title is empty, we "guess" the title by extracting the filename from the song's path.
return $value ?: pathinfo($model->path, PATHINFO_FILENAME);
}
/**
* @param string $value
*/
public function set(Model $model, string $key, mixed $value, array $attributes): string
{
return html_entity_decode($value);
}
}

View file

@ -193,12 +193,12 @@ class Playlist extends Model
protected function isCollaborative(): Attribute
{
return Attribute::get(fn (): bool => !$this->is_smart &&
LicenseFacade::isPlus()
return Attribute::get(fn (): bool => !$this->is_smart
&& LicenseFacade::isPlus()
&& $this->collaborators->isNotEmpty());
}
/** @return array<mixed> */
/** @inheritdoc */
public function toSearchableArray(): array
{
return [

View file

@ -4,6 +4,9 @@ namespace App\Models;
use App\Builders\SongBuilder;
use App\Casts\Podcast\EpisodeMetadataCast;
use App\Casts\SongLyricsCast;
use App\Casts\SongStorageCast;
use App\Casts\SongTitleCast;
use App\Enums\PlayableType;
use App\Enums\SongStorageType;
use App\Models\Concerns\SupportsDeleteWhereValueNotIn;
@ -79,11 +82,14 @@ class Song extends Model
protected $hidden = ['updated_at', 'path', 'mtime'];
protected $casts = [
'title' => SongTitleCast::class,
'lyrics' => SongLyricsCast::class,
'length' => 'float',
'mtime' => 'int',
'track' => 'int',
'disc' => 'int',
'is_public' => 'bool',
'storage' => SongStorageCast::class,
'episode_metadata' => EpisodeMetadataCast::class,
];
@ -148,14 +154,6 @@ class Song extends Model
return Attribute::get(fn () => $this->podcast_id ? PlayableType::PODCAST_EPISODE : PlayableType::SONG);
}
protected function title(): Attribute
{
return new Attribute(
get: fn (?string $value) => $value ?: pathinfo($this->path, PATHINFO_FILENAME),
set: static fn (string $value) => html_entity_decode($value)
);
}
public function accessibleBy(User $user): bool
{
if ($this->isEpisode()) {
@ -170,31 +168,6 @@ class Song extends Model
return $this->owner_id === $user->id;
}
protected function lyrics(): Attribute
{
$normalizer = static function (?string $value): string {
// Since we're displaying the lyrics using <pre>, replace breaks with newlines and strip all tags.
$value = strip_tags(preg_replace('#<br\s*/?>#i', PHP_EOL, $value));
// also remove the timestamps that often come with LRC files
return preg_replace('/\[\d{2}:\d{2}.\d{2}]\s*/m', '', $value);
};
return new Attribute(get: $normalizer, set: $normalizer);
}
protected function storage(): Attribute
{
return new Attribute(
get: static fn (?string $raw) => SongStorageType::tryFrom($raw) ?? SongStorageType::LOCAL,
set: static function (SongStorageType|string|null $type) {
$type = $type instanceof SongStorageType ? $type : SongStorageType::tryFrom($type);
return $type->value;
}
);
}
protected function storageMetadata(): Attribute
{
return new Attribute(

View file

@ -40,7 +40,7 @@ use Laravel\Sanctum\PersonalAccessToken;
* @property ?string $sso_provider
* @property ?string $sso_id
* @property bool $is_sso
* @property Collection<array-key, Podcast> $podcast
* @property Collection<array-key, Podcast> $podcasts
*/
class User extends Authenticatable
{

View file

@ -9,11 +9,6 @@ use Illuminate\Support\Collection;
/** @extends Repository<Podcast> */
class PodcastRepository extends Repository
{
public function __construct()
{
parent::__construct(Podcast::class);
}
public function findOneByUrl(string $url): ?Podcast
{
return $this->findOneBy(['url' => $url]);

View file

@ -7,11 +7,12 @@ use Tests\TestCase;
class SongTest extends TestCase
{
public function testLyricsDoNotContainTimestamps(): void
public function testRetrievedLyricsDoNotContainTimestamps(): void
{
/** @var Song $song */
$song = Song::factory()->create(['lyrics' => "[00:00.00]Line 1\n[00:01.00]Line 2\n[00:02.00]Line 3"]);
self::assertSame("Line 1\nLine 2\nLine 3", $song->lyrics);
self::assertSame("[00:00.00]Line 1\n[00:01.00]Line 2\n[00:02.00]Line 3", $song->getAttributes()['lyrics']);
}
}