feat(service): add api service

This commit is contained in:
2024-07-29 18:50:58 +02:00
parent e929853bb2
commit e1375f1bf5
18 changed files with 639 additions and 5 deletions

View File

@@ -7,8 +7,8 @@ namespace App\Entity\Remote\Brilo\Users;
readonly class AddressGeo
{
public function __construct(
public float $lat,
public float $lng,
public string $lat,
public string $lng,
) {
}
}

View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace App\Service\Remote;
use App\Entity\Remote\Brilo\Comments\Comment;
use App\Entity\Remote\Brilo\Posts\Post;
use Psr\Log\LoggerInterface;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
class BriloApiComments
{
use BriloApiFetchTrait;
public function __construct(
protected readonly HttpClientInterface $httpClient,
protected readonly LoggerInterface $logger,
protected readonly SerializerInterface $serializer,
protected readonly int $retryCount = 3,
protected readonly int $sleepTimeS = 1,
) {
}
/**
* Fetch all comments from the Brilo API and return them as an array of Comment entities.
* @return Comment[]
* @throws \Exception
*/
public function getComments(): array
{
return $this->fetchApiResponse('https://jsonplaceholder.typicode.com/comments', Comment::class);
}
}

View File

@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace App\Service\Remote;
use App\Entity\Remote\Brilo\Users\User;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpClient\Exception\TransportException;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
trait BriloApiFetchTrait
{
use RetryingClientTrait;
protected readonly int $retryCount;
protected readonly int $sleepTimeS;
protected readonly LoggerInterface $logger;
protected readonly HttpClientInterface $httpClient;
protected readonly SerializerInterface $serializer;
/**
* Implementation of fetch logic. Handles retrying and logging of failed requests.
* Returns deserialized data.
* @template T
*
* @param string $uri Uri of the API endpoint.
* @param class-string<T> $class Class name of the entity to deserialize data into.
*
* @return T[]
* @throws \Exception
*/
protected function fetchApiResponse(string $uri, string $class): array
{
return $this->retryingFailRequest($this->retryCount, $this->sleepTimeS, [TransportException::class, BriloRemoteApiException::class], $this->logger, function () use ($uri, $class) {
$this->logger->debug('Trying to download brilo users');
$response = $this->httpClient->request('GET', $uri);
if ($response->getStatusCode() != 200) {
$this->logger->error("Can't download brilo users. HTTP status code: " . $response->getStatusCode());
throw new BriloRemoteApiException("Can't download brilo users. HTTP status code: " . $response->getStatusCode());
}
$this->logger->debug('Brilo users downloaded');
$this->logger->debug('Deserialize brilo users');
// extra attributes muzeme safe ignorovat
return $this->serializer->deserialize($response->getContent(), $class . '[]', 'json');
});
}
}

View File

@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace App\Service\Remote;
class BriloApiPosts
{
}

View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace App\Service\Remote;
use App\Entity\Remote\Brilo\Users\User;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpClient\Exception\TransportException;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
final class BriloApiUsers
{
use BriloApiFetchTrait;
public function __construct(
protected readonly HttpClientInterface $httpClient,
protected readonly LoggerInterface $logger,
protected readonly SerializerInterface $serializer,
protected readonly int $retryCount = 3,
protected readonly int $sleepTimeS = 1,
) {
}
/**
* @return User[]
* @throws \Exception
*/
public function getUsers(): array
{
return $this->fetchApiResponse('https://jsonplaceholder.typicode.com/users', User::class);
}
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace App\Service\Remote;
/**
* Remote API exception (in case return code from API endpoint is not 200 - OK)
*/
class BriloRemoteApiException extends \RuntimeException
{
}

View File

@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace App\Service\Remote;
use Exception;
use Psr\Log\LoggerInterface;
trait RetryingClientTrait
{
/**
* Better to retry failed requests (in case of non-destructive operations) than throwing immediately.
*
* @param int $count
* @param float $sleep
* @param array<string> $catchableExceptions
* @param LoggerInterface $logger
* @param callable $callback
* @return mixed
* @throws Exception
*/
protected function retryingFailRequest(
int $count,
float $sleep,
array $catchableExceptions,
LoggerInterface $logger,
callable $callback
): mixed {
for ($i = 0;; $i++) {
try {
return $callback();
} catch (Exception $e) {
foreach ($catchableExceptions as $exceptionClass) {
if ($e instanceof $exceptionClass) {
$logger->error("transport: fail request retrying... got catchable exception", [
'exception' => $e,
'try' => $i
]);
usleep((int) ($sleep * 1_000_000));
if ($i == $count) {
throw $e;
}
continue 2;
}
}
throw $e;
}
}
// phpstan fail
return null;
}
}