wtf
All checks were successful
CI - Build Tonehaus Docker image / tonehaus-ci-build (push) Successful in 2m0s
All checks were successful
CI - Build Tonehaus Docker image / tonehaus-ci-build (push) Successful in 2m0s
This commit is contained in:
@@ -2,8 +2,11 @@
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\AlbumRepository;
|
||||
use App\Entity\AlbumTrack;
|
||||
use App\Entity\User;
|
||||
use App\Repository\AlbumRepository;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
@@ -15,6 +18,10 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
#[ORM\HasLifecycleCallbacks]
|
||||
class Album
|
||||
{
|
||||
#[ORM\OneToMany(mappedBy: 'album', targetEntity: AlbumTrack::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
|
||||
#[ORM\OrderBy(['discNumber' => 'ASC', 'trackNumber' => 'ASC'])]
|
||||
private Collection $tracks;
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
@@ -68,6 +75,11 @@ class Album
|
||||
#[ORM\Column(type: 'datetime_immutable')]
|
||||
private ?\DateTimeImmutable $updatedAt = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->tracks = new ArrayCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes timestamps right before first persistence.
|
||||
*/
|
||||
@@ -324,6 +336,29 @@ class Album
|
||||
'source' => $this->source,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, AlbumTrack>
|
||||
*/
|
||||
public function getTracks(): Collection
|
||||
{
|
||||
return $this->tracks;
|
||||
}
|
||||
|
||||
public function addTrack(AlbumTrack $track): void
|
||||
{
|
||||
if (!$this->tracks->contains($track)) {
|
||||
$this->tracks->add($track);
|
||||
$track->setAlbum($this);
|
||||
}
|
||||
}
|
||||
|
||||
public function removeTrack(AlbumTrack $track): void
|
||||
{
|
||||
if ($this->tracks->removeElement($track) && $track->getAlbum() === $this) {
|
||||
$track->setAlbum(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
140
src/Entity/AlbumTrack.php
Normal file
140
src/Entity/AlbumTrack.php
Normal file
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\AlbumTrackRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* AlbumTrack persists individual tracks fetched from Spotify.
|
||||
*/
|
||||
#[ORM\Entity(repositoryClass: AlbumTrackRepository::class)]
|
||||
#[ORM\Table(name: 'album_tracks')]
|
||||
#[ORM\UniqueConstraint(name: 'uniq_album_disc_track', columns: ['album_id', 'disc_number', 'track_number'])]
|
||||
class AlbumTrack
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: Album::class, inversedBy: 'tracks')]
|
||||
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
|
||||
private ?Album $album = null;
|
||||
|
||||
#[ORM\Column(type: 'string', length: 64, nullable: true)]
|
||||
private ?string $spotifyTrackId = null;
|
||||
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private int $discNumber = 1;
|
||||
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private int $trackNumber = 1;
|
||||
|
||||
#[ORM\Column(type: 'string', length: 512)]
|
||||
private string $name = '';
|
||||
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private int $durationMs = 0;
|
||||
|
||||
#[ORM\Column(type: 'string', length: 1024, nullable: true)]
|
||||
private ?string $previewUrl = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getAlbum(): ?Album
|
||||
{
|
||||
return $this->album;
|
||||
}
|
||||
|
||||
public function setAlbum(?Album $album): void
|
||||
{
|
||||
$this->album = $album;
|
||||
}
|
||||
|
||||
public function getSpotifyTrackId(): ?string
|
||||
{
|
||||
return $this->spotifyTrackId;
|
||||
}
|
||||
|
||||
public function setSpotifyTrackId(?string $spotifyTrackId): void
|
||||
{
|
||||
$this->spotifyTrackId = $spotifyTrackId;
|
||||
}
|
||||
|
||||
public function getDiscNumber(): int
|
||||
{
|
||||
return $this->discNumber;
|
||||
}
|
||||
|
||||
public function setDiscNumber(int $discNumber): void
|
||||
{
|
||||
$this->discNumber = max(1, $discNumber);
|
||||
}
|
||||
|
||||
public function getTrackNumber(): int
|
||||
{
|
||||
return $this->trackNumber;
|
||||
}
|
||||
|
||||
public function setTrackNumber(int $trackNumber): void
|
||||
{
|
||||
$this->trackNumber = max(1, $trackNumber);
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setName(string $name): void
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public function getDurationMs(): int
|
||||
{
|
||||
return $this->durationMs;
|
||||
}
|
||||
|
||||
public function setDurationMs(int $durationMs): void
|
||||
{
|
||||
$this->durationMs = max(0, $durationMs);
|
||||
}
|
||||
|
||||
public function getPreviewUrl(): ?string
|
||||
{
|
||||
return $this->previewUrl;
|
||||
}
|
||||
|
||||
public function setPreviewUrl(?string $previewUrl): void
|
||||
{
|
||||
$this->previewUrl = $previewUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the track for template rendering.
|
||||
*
|
||||
* @return array{disc:int,track:int,name:string,duration_label:string,duration_seconds:int,preview_url:?string}
|
||||
*/
|
||||
public function toTemplateArray(): array
|
||||
{
|
||||
$seconds = (int) floor($this->durationMs / 1000);
|
||||
$minutes = intdiv($seconds, 60);
|
||||
$remainingSeconds = $seconds % 60;
|
||||
|
||||
return [
|
||||
'disc' => $this->discNumber,
|
||||
'track' => $this->trackNumber,
|
||||
'name' => $this->name,
|
||||
'duration_label' => sprintf('%d:%02d', $minutes, $remainingSeconds),
|
||||
'duration_seconds' => $seconds,
|
||||
'preview_url' => $this->previewUrl,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user