-
-
Notifications
You must be signed in to change notification settings - Fork 201
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: introduce BookRepository as abstraction of OpenLibrary (#471)
- Loading branch information
1 parent
7e4f8f5
commit 2040ecc
Showing
22 changed files
with
332 additions
and
147 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App\BookRepository; | ||
|
||
use App\Entity\Book; | ||
|
||
interface BookRepositoryInterface | ||
{ | ||
public function find(string $url): ?Book; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App\BookRepository; | ||
|
||
use App\Entity\Book; | ||
use Symfony\Component\DependencyInjection\Attribute\AsAlias; | ||
use Symfony\Component\DependencyInjection\Attribute\AutowireIterator; | ||
|
||
#[AsAlias] | ||
final readonly class ChainBookRepository implements BookRepositoryInterface | ||
{ | ||
/** @param iterable<RestrictedBookRepositoryInterface> $repositories */ | ||
public function __construct( | ||
#[AutowireIterator(tag: RestrictedBookRepositoryInterface::TAG)] | ||
private iterable $repositories, | ||
) { | ||
} | ||
|
||
public function find(string $url): ?Book | ||
{ | ||
foreach ($this->repositories as $repository) { | ||
if ($repository->supports($url)) { | ||
return $repository->find($url); | ||
} | ||
} | ||
|
||
return null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App\BookRepository; | ||
|
||
use App\Entity\Book; | ||
use Symfony\Component\Serializer\Encoder\DecoderInterface; | ||
use Symfony\Contracts\HttpClient\HttpClientInterface; | ||
|
||
final readonly class GutendexBookRepository implements RestrictedBookRepositoryInterface | ||
{ | ||
public function __construct( | ||
private HttpClientInterface $gutendexClient, | ||
private DecoderInterface $decoder, | ||
) { | ||
} | ||
|
||
public function supports(string $url): bool | ||
{ | ||
return str_starts_with($url, 'https://gutendex.com'); | ||
} | ||
|
||
public function find(string $url): ?Book | ||
{ | ||
$options = ['headers' => ['Accept' => 'application/json']]; | ||
$response = $this->gutendexClient->request('GET', $url, $options); | ||
if (200 !== $response->getStatusCode()) { | ||
return null; | ||
} | ||
|
||
$book = new Book(); | ||
|
||
$data = $this->decoder->decode($response->getContent(), 'json'); | ||
$book->title = $data['title']; | ||
$book->author = $data['authors'][0]['name'] ?? null; | ||
|
||
return $book; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App\BookRepository; | ||
|
||
use App\Entity\Book; | ||
use Symfony\Component\Serializer\Encoder\DecoderInterface; | ||
use Symfony\Contracts\HttpClient\HttpClientInterface; | ||
|
||
final readonly class OpenLibraryBookRepository implements RestrictedBookRepositoryInterface | ||
{ | ||
public function __construct( | ||
private HttpClientInterface $openLibraryClient, | ||
private DecoderInterface $decoder, | ||
) { | ||
} | ||
|
||
public function supports(string $url): bool | ||
{ | ||
return str_starts_with($url, 'https://openlibrary.org'); | ||
} | ||
|
||
public function find(string $url): ?Book | ||
{ | ||
$options = ['headers' => ['Accept' => 'application/json']]; | ||
$response = $this->openLibraryClient->request('GET', $url, $options); | ||
if (200 !== $response->getStatusCode()) { | ||
return null; | ||
} | ||
|
||
$book = new Book(); | ||
|
||
$data = $this->decoder->decode($response->getContent(), 'json'); | ||
$book->title = $data['title']; | ||
|
||
$book->author = null; | ||
if (isset($data['authors'][0]['key'])) { | ||
$author = $this->openLibraryClient->request('GET', $data['authors'][0]['key'] . '.json', $options); | ||
if (isset($author['name'])) { | ||
$book->author = $author['name']; | ||
} | ||
} | ||
|
||
return $book; | ||
} | ||
} |
15 changes: 15 additions & 0 deletions
15
api/src/BookRepository/RestrictedBookRepositoryInterface.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App\BookRepository; | ||
|
||
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag; | ||
|
||
#[AutoconfigureTag(name: RestrictedBookRepositoryInterface::TAG)] | ||
interface RestrictedBookRepositoryInterface extends BookRepositoryInterface | ||
{ | ||
public const TAG = 'book.repository'; | ||
|
||
public function supports(string $url): bool; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App\Validator; | ||
|
||
use Symfony\Component\Validator\Constraint; | ||
|
||
#[\Attribute(\Attribute::TARGET_PROPERTY)] | ||
final class BookUrl extends Constraint | ||
{ | ||
public string $message = 'This book URL is not valid.'; | ||
|
||
public function __construct(?array $options = null, ?string $message = null, ?array $groups = null, mixed $payload = null) | ||
{ | ||
parent::__construct($options ?? [], $groups, $payload); | ||
|
||
$this->message = $message ?? $this->message; | ||
} | ||
|
||
public function getTargets(): string | ||
{ | ||
return self::PROPERTY_CONSTRAINT; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App\Validator; | ||
|
||
use App\BookRepository\BookRepositoryInterface; | ||
use Symfony\Component\Validator\Constraint; | ||
use Symfony\Component\Validator\ConstraintValidator; | ||
use Symfony\Component\Validator\Exception\UnexpectedTypeException; | ||
|
||
final class BookUrlValidator extends ConstraintValidator | ||
{ | ||
public function __construct( | ||
private readonly BookRepositoryInterface $bookRepository, | ||
) { | ||
} | ||
|
||
/** | ||
* @param string|null $value | ||
*/ | ||
public function validate($value, Constraint $constraint): void | ||
{ | ||
if (!$constraint instanceof BookUrl) { | ||
throw new UnexpectedTypeException($constraint, BookUrl::class); | ||
} | ||
|
||
if (null === $value || '' === $value) { | ||
return; | ||
} | ||
|
||
if (!$this->bookRepository->find($value)) { | ||
$this->context->buildViolation($constraint->message)->addViolation(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.