<?php
|
declare(strict_types=1);
|
|
namespace App\Lookup;
|
|
use Symfony\Contracts\HttpClient\Exception\ExceptionInterface;
|
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
|
/**
|
* Looks books up in the Polish National Library catalogue (https://data.bn.org.pl).
|
*/
|
class NationalLibraryLookup implements BookLookupInterface {
|
private const int LIMIT = 10;
|
|
public function __construct(private readonly HttpClientInterface $bnClient) {
|
}
|
|
public function findByStandardNumber(string $number): array {
|
return $this->fetch(['isbnIssn' => str_replace(['-', ' '], '', $number)]);
|
}
|
|
public function findByDescription(string $title, string $author, string $publisher): array {
|
return $this->fetch(array_filter(
|
['title' => $title, 'author' => $author, 'publisher' => $publisher],
|
static fn(string $value): bool => $value !== '',
|
));
|
}
|
|
/**
|
* @param array<string, string> $query
|
*
|
* @return list<BookCandidate>
|
*/
|
private function fetch(array $query): array {
|
try {
|
$response = $this->bnClient->request('GET', '/api/networks/bibs.json', [
|
'query' => $query + ['limit' => self::LIMIT, 'kind' => 'książka'],
|
]);
|
$records = $response->toArray()['bibs'] ?? [];
|
} catch (ExceptionInterface $exception) {
|
throw new LookupFailedException('The catalogue request failed.', previous: $exception);
|
}
|
|
return array_map(self::toCandidate(...), $records);
|
}
|
|
/**
|
* @param array<string, mixed> $record
|
*/
|
private static function toCandidate(array $record): BookCandidate {
|
$number = preg_replace('/[^\dX]/', '', strtoupper((string)($record['isbnIssn'] ?? ''))) ?: null;
|
$isIssn = $number !== null && \strlen($number) === 8;
|
$year = (string)($record['publicationYear'] ?? '');
|
|
return new BookCandidate(
|
title: trim((string)($record['title'] ?? ''), " \t\n\r/:;,."),
|
author: self::firstPerson((string)($record['author'] ?? '')),
|
language: self::blankToNull((string)($record['language'] ?? '')),
|
publisher: self::singlePublisher((string)($record['publisher'] ?? '')),
|
publicationLoc: self::blankToNull(trim(explode(':', (string)($record['placeOfPublication'] ?? ''))[0])),
|
publicationYear: preg_match('/\d{4}/', $year, $match) === 1 ? (int)$match[0] : null,
|
isbn: $isIssn ? null : $number,
|
issn: $isIssn ? $number : null,
|
);
|
}
|
|
/**
|
* "MacLean, Alistair (1922-1987) Ginalski, Robert ..." becomes "Alistair MacLean".
|
*/
|
private static function firstPerson(string $authors): ?string {
|
$first = preg_split('/\s*\(\d{4}/', $authors, 2)[0];
|
$parts = array_map(trim(...), explode(',', rtrim($first, " .\t\n\r")));
|
|
return self::blankToNull(\count($parts) === 2 ? $parts[1] . ' ' . $parts[0] : implode(', ', $parts));
|
}
|
|
private static function singlePublisher(string $publisher): ?string {
|
$publisher = trim($publisher, " \t\n\r\",;:.");
|
|
return self::blankToNull(preg_replace('/^(.+)\s+\1$/u', '$1', $publisher) ?? $publisher);
|
}
|
|
private static function blankToNull(string $value): ?string {
|
return trim($value) === '' ? null : trim($value);
|
}
|
}
|