Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
50.00% covered (danger)
50.00%
2 / 4
CRAP
83.87% covered (warning)
83.87%
26 / 31
SampleDataSource
0.00% covered (danger)
0.00%
0 / 1
50.00% covered (danger)
50.00%
2 / 4
10.42
83.87% covered (warning)
83.87%
26 / 31
 __construct
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
13 / 13
 getQuotes
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 transformResponseInQuotesCollection
0.00% covered (danger)
0.00%
0 / 1
3.21
71.43% covered (warning)
71.43%
5 / 7
 fetchQuotesFromSource
0.00% covered (danger)
0.00%
0 / 1
3.24
70.00% covered (warning)
70.00%
7 / 10
<?php
declare(strict_types=1);
namespace App\Quote\DataSource;
use App\Quote\Collection\QuoteCollection;
use App\Quote\Collection\QuoteCollectionInterface;
use App\Quote\DataSource\Exception\DataSourceException;
use App\Quote\DataSource\Exception\DataSourceIsNotReadableException;
use Psr\Log\LoggerInterface;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\HttpClient\Exception\ClientException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;
use Webmozart\Assert\Assert;
final class SampleDataSource implements DataSourceInterface
{
    private const KEY = 'SampleDataSource';
    private HttpClientInterface $httpClient;
    private QuoteCollectionInterface $quotes;
    private AdapterInterface $cache;
    private LoggerInterface $logger;
    private string $url;
    public function __construct(
        HttpClientInterface $httpClient,
        AdapterInterface $cache,
        LoggerInterface $logger,
        string $dataSourceUrl
    ) {
        $this->httpClient = $httpClient;
        $this->cache = $cache;
        $this->url = $dataSourceUrl;
        $this->logger = $logger;
        $cachedItem = $this->cache->getItem(self::KEY);
        if ($cachedItem->get() instanceof QuoteCollectionInterface) {
            $this->quotes = $cachedItem->get();
            $this->logger->info('Data fetched from cache', ['DataSource' => self::KEY]);
        }
        if (false === $cachedItem->isHit()) {
            $this->fetchQuotesFromSource();
            $cachedItem->set($this->quotes);
            $this->cache->save($cachedItem);
        }
    }
    public function getQuotes(): QuoteCollectionInterface
    {
        return $this->quotes;
    }
    private function transformResponseInQuotesCollection(ResponseInterface $rawDataResponse): array
    {
        if ($rawDataResponse->getStatusCode() !== Response::HTTP_OK) {
            return [];
        }
        $data = json_decode($rawDataResponse->getContent(), true, 512, JSON_THROW_ON_ERROR);
        if (empty($data)) {
            return [];
        }
        Assert::keyExists($data, 'quotes');
        return $data['quotes'];
    }
    private function fetchQuotesFromSource(): void
    {
        try {
            $rawDataResponse = $this->httpClient->request(Request::METHOD_GET, $this->url);
        } catch (TransportExceptionInterface | ClientException $e) {
            throw new DataSourceException(
                sprintf('Could not fetch quotes from %s data source', __CLASS__)
            );
        }
        if ($rawDataResponse->getStatusCode() === Response::HTTP_NOT_FOUND) {
            throw new DataSourceIsNotReadableException(
                sprintf('The url %s provided is not readable or reachable', $this->url)
            );
        }
        $this->logger->info('Raw data fetched from text collection quotes api', ['DataSource' => self::KEY]);
        $this->quotes = QuoteCollection::fromArray($this->transformResponseInQuotesCollection($rawDataResponse));
    }
}