<?php
|
declare(strict_types=1);
|
|
namespace App\Service;
|
|
use App\Entity\Book;
|
use App\Entity\OustedBook;
|
use Doctrine\ORM\EntityManagerInterface;
|
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
|
/**
|
* Operations that change where a book is: lent out, back on the shelf, out of the collection.
|
*/
|
class Library {
|
public function __construct(
|
private readonly EntityManagerInterface $entityManager,
|
private readonly CoverStorage $covers,
|
private readonly LibraryThingCovers $libraryThing,
|
) {
|
}
|
|
/**
|
* Gives a book without a cover the one LibraryThing has for its ISBN, if any.
|
*
|
* @return bool whether a cover was added
|
*/
|
public function downloadCover(Book $book): bool {
|
$isbn = $book->getIsbn();
|
|
if ($book->getCover() !== null || $isbn === null || !$this->libraryThing->isEnabled()) {
|
return false;
|
}
|
|
$data = $this->libraryThing->download($isbn);
|
|
if ($data === null) {
|
return false;
|
}
|
|
try {
|
$book->setCover($this->covers->store($data));
|
} catch (CoverProcessingException) {
|
// LibraryThing may send a format that cannot be stored, such as GIF.
|
return false;
|
}
|
|
$this->entityManager->flush();
|
|
return true;
|
}
|
|
/**
|
* Stores a new cover and drops the previous one; null only removes it.
|
*
|
* @throws CoverProcessingException
|
*/
|
public function changeCover(Book $book, ?UploadedFile $file): void {
|
$previous = $book->getCover();
|
$book->setCover($file ? $this->covers->store($file->getContent()) : null);
|
$this->entityManager->flush();
|
|
if ($previous !== null) {
|
$this->covers->delete($previous);
|
}
|
}
|
|
public function lend(Book $book, string $who): void {
|
$book->lendTo($who, new \DateTimeImmutable());
|
$this->entityManager->flush();
|
}
|
|
public function giveBack(Book $book): void {
|
$book->giveBack(new \DateTimeImmutable());
|
$this->entityManager->flush();
|
}
|
|
/**
|
* Moves a book to the ousted collection with its labels and loan history.
|
*/
|
public function oust(Book $book, string $reason): OustedBook {
|
if ($book->isBorrowed()) {
|
throw new \LogicException(\sprintf('Book %d must be returned before it is ousted.', $book->getId()));
|
}
|
|
$ousted = OustedBook::fromBook($book, $reason);
|
foreach ($book->getBorrowHistory() as $entry) {
|
$ousted->addBorrowHistory($entry->getWho(), $entry->getAt(), $entry->getTill());
|
}
|
|
$this->entityManager->wrapInTransaction(function () use ($book, $ousted): void {
|
$this->entityManager->remove($book);
|
$this->entityManager->flush();
|
$this->entityManager->persist($ousted);
|
});
|
|
return $ousted;
|
}
|
|
/**
|
* Ousts all the given books, or none of them if one fails.
|
*
|
* @param iterable<Book> $books
|
* @return int how many books were ousted
|
*/
|
public function oustAll(iterable $books, string $reason): int {
|
return $this->entityManager->wrapInTransaction(function () use ($books, $reason): int {
|
$count = 0;
|
foreach ($books as $book) {
|
$this->oust($book, $reason);
|
$count++;
|
}
|
$this->entityManager->flush();
|
|
return $count;
|
});
|
}
|
|
public function restore(OustedBook $ousted): Book {
|
$book = Book::fromOusted($ousted);
|
foreach ($ousted->getBorrowHistory() as $entry) {
|
$book->addBorrowHistory($entry->getWho(), $entry->getAt(), $entry->getTill());
|
}
|
|
$this->entityManager->wrapInTransaction(function () use ($book, $ousted): void {
|
$this->entityManager->remove($ousted);
|
$this->entityManager->flush();
|
$this->entityManager->persist($book);
|
});
|
|
return $book;
|
}
|
}
|