<?php
|
declare(strict_types=1);
|
|
namespace App\Entity;
|
|
use App\Enum\LabelType;
|
use App\Validator\AvailableBookId;
|
use Doctrine\Common\Collections\Collection;
|
use Doctrine\DBAL\Types\Types;
|
use Doctrine\ORM\Mapping as ORM;
|
use Symfony\Component\Validator\Constraints as Assert;
|
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
|
/**
|
* Bibliographic data shared by books on the shelves and books taken out of the collection.
|
*
|
* The ID is assigned by hand: it is the code on the sticker put on the physical book.
|
*/
|
#[ORM\MappedSuperclass]
|
abstract class AbstractBook {
|
#[ORM\Id]
|
#[ORM\Column]
|
#[Assert\NotNull]
|
#[Assert\PositiveOrZero]
|
#[AvailableBookId(groups: ['creation'])]
|
protected ?int $id = null;
|
|
#[ORM\Column(length: 255)]
|
#[Assert\NotBlank]
|
#[Assert\Length(max: 255)]
|
protected ?string $title = null;
|
|
#[ORM\Column(length: 255)]
|
#[Assert\NotBlank]
|
#[Assert\Length(max: 255)]
|
protected ?string $author = null;
|
|
#[ORM\Column(length: 255, nullable: true)]
|
#[Assert\Length(max: 255)]
|
protected ?string $language = null;
|
|
#[ORM\Column(length: 255, nullable: true)]
|
#[Assert\Length(max: 255)]
|
protected ?string $edition = null;
|
|
#[ORM\Column(length: 255, nullable: true)]
|
#[Assert\Length(max: 255)]
|
protected ?string $publisher = null;
|
|
#[ORM\Column(length: 255, nullable: true)]
|
#[Assert\Length(max: 255)]
|
protected ?string $publicationLoc = null;
|
|
#[ORM\Column(type: Types::INTEGER, nullable: true)]
|
#[Assert\Range(min: 1000, max: 2200)]
|
protected ?int $publicationYear = null;
|
|
#[ORM\Column(length: 255, nullable: true)]
|
#[Assert\Length(max: 255)]
|
protected ?string $placement = null;
|
|
#[ORM\Column(length: 64, nullable: true)]
|
protected ?string $cover = null;
|
|
/**
|
* When the book was last found on its shelf during an inventory.
|
*/
|
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)]
|
protected ?\DateTimeImmutable $lastSeen = null;
|
|
public function getId(): ?int {
|
return $this->id;
|
}
|
|
/**
|
* File name of the original cover, see CoverStorage.
|
*/
|
public function getCover(): ?string {
|
return $this->cover;
|
}
|
|
public function setCover(?string $cover): static {
|
$this->cover = $cover;
|
|
return $this;
|
}
|
|
public function setId(?int $id): static {
|
$this->id = $id;
|
|
return $this;
|
}
|
|
public function getTitle(): ?string {
|
return $this->title;
|
}
|
|
public function setTitle(?string $title): static {
|
$this->title = $title;
|
|
return $this;
|
}
|
|
public function getAuthor(): ?string {
|
return $this->author;
|
}
|
|
public function setAuthor(?string $author): static {
|
$this->author = $author;
|
|
return $this;
|
}
|
|
public function getLanguage(): ?string {
|
return $this->language;
|
}
|
|
public function setLanguage(?string $language): static {
|
$this->language = $language;
|
|
return $this;
|
}
|
|
public function getEdition(): ?string {
|
return $this->edition;
|
}
|
|
public function setEdition(?string $edition): static {
|
$this->edition = $edition;
|
|
return $this;
|
}
|
|
public function getPublisher(): ?string {
|
return $this->publisher;
|
}
|
|
public function setPublisher(?string $publisher): static {
|
$this->publisher = $publisher;
|
|
return $this;
|
}
|
|
public function getPublicationLoc(): ?string {
|
return $this->publicationLoc;
|
}
|
|
public function setPublicationLoc(?string $publicationLoc): static {
|
$this->publicationLoc = $publicationLoc;
|
|
return $this;
|
}
|
|
public function getPublicationYear(): ?int {
|
return $this->publicationYear;
|
}
|
|
public function setPublicationYear(?int $publicationYear): static {
|
$this->publicationYear = $publicationYear;
|
|
return $this;
|
}
|
|
#[Assert\Callback]
|
public function validateStandardNumbers(ExecutionContextInterface $context): void {
|
foreach ([LabelType::ISBN, LabelType::ISSN] as $type) {
|
$value = $this->firstLabel($type)?->getLabel();
|
|
if ($value !== null && !$type->isValid($value)) {
|
$context->buildViolation('label_invalid_number')
|
->setTranslationDomain('messages')
|
->atPath($type->value)
|
->addViolation();
|
}
|
}
|
}
|
|
public function getIsbn(): ?string {
|
return $this->firstLabel(LabelType::ISBN)?->getLabel();
|
}
|
|
public function setIsbn(?string $isbn): static {
|
$this->replaceFirstLabel(LabelType::ISBN, $isbn);
|
|
return $this;
|
}
|
|
public function getIssn(): ?string {
|
return $this->firstLabel(LabelType::ISSN)?->getLabel();
|
}
|
|
public function setIssn(?string $issn): static {
|
$this->replaceFirstLabel(LabelType::ISSN, $issn);
|
|
return $this;
|
}
|
|
/**
|
* @return Collection<int, covariant AbstractLabel>
|
*/
|
abstract public function getLabels(): Collection;
|
|
abstract protected function createLabel(string $label, LabelType $type): AbstractLabel;
|
|
/**
|
* @return list<AbstractLabel>
|
*/
|
public function getTextLabels(): array {
|
return array_values($this->getLabels()->filter(
|
static fn(AbstractLabel $label): bool => $label->getType() === LabelType::TEXT,
|
)->toArray());
|
}
|
|
public function addLabel(string $text, LabelType $type = LabelType::TEXT): void {
|
$text = $type->normalize($text);
|
|
foreach ($this->getLabels() as $label) {
|
if ($label->getType() === $type && $label->getLabel() === $text) {
|
return;
|
}
|
}
|
|
$this->getLabels()->add($this->createLabel($text, $type));
|
}
|
|
public function removeLabel(AbstractLabel $label): void {
|
$this->getLabels()->removeElement($label);
|
}
|
|
public function getPlacement(): ?string {
|
return $this->placement;
|
}
|
|
public function setPlacement(?string $placement): static {
|
$this->placement = $placement;
|
|
return $this;
|
}
|
|
public function getLastSeen(): ?\DateTimeImmutable {
|
return $this->lastSeen;
|
}
|
|
public function setLastSeen(?\DateTimeImmutable $lastSeen): static {
|
$this->lastSeen = $lastSeen;
|
|
return $this;
|
}
|
|
/**
|
* Records that the book was found at $placement during an inventory.
|
*/
|
public function seenAt(string $placement, \DateTimeImmutable $at): void {
|
$this->placement = $placement;
|
$this->lastSeen = $at;
|
}
|
|
/**
|
* Copies the bibliographic data and labels, keeping the ID; used when a book moves between
|
* collections.
|
*/
|
protected function copyDataFrom(self $source): void {
|
$this->id = $source->id;
|
$this->title = $source->title;
|
$this->author = $source->author;
|
$this->language = $source->language;
|
$this->edition = $source->edition;
|
$this->publisher = $source->publisher;
|
$this->publicationLoc = $source->publicationLoc;
|
$this->publicationYear = $source->publicationYear;
|
$this->placement = $source->placement;
|
$this->cover = $source->cover;
|
$this->lastSeen = $source->lastSeen;
|
|
foreach ($source->getLabels() as $label) {
|
$this->addLabel($label->getLabel(), $label->getType());
|
}
|
}
|
|
private function firstLabel(LabelType $type): ?AbstractLabel {
|
foreach ($this->getLabels() as $label) {
|
if ($label->getType() === $type) {
|
return $label;
|
}
|
}
|
|
return null;
|
}
|
|
private function replaceFirstLabel(LabelType $type, ?string $value): void {
|
$value = $type->normalize((string)$value);
|
$current = $this->firstLabel($type);
|
|
if ($current?->getLabel() === $value) {
|
return;
|
}
|
|
if ($current !== null) {
|
$this->removeLabel($current);
|
}
|
|
if ($value !== '') {
|
$this->addLabel($value, $type);
|
}
|
}
|
}
|