seatplus/esi-client

View on GitHub
src/EsiClient.php

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
<?php

namespace Seatplus\EsiClient;

use GuzzleHttp\Psr7\Uri;
use Seatplus\EsiClient\DataTransferObjects\EsiAuthentication;
use Seatplus\EsiClient\DataTransferObjects\EsiResponse;
use Seatplus\EsiClient\Exceptions\EsiScopeAccessDeniedException;
use Seatplus\EsiClient\Exceptions\InvalidAuthenticationException;
use Seatplus\EsiClient\Exceptions\RequestFailedException;
use Seatplus\EsiClient\Exceptions\UriDataMissingException;
use Seatplus\EsiClient\Fetcher\GuzzleFetcher;
use Seatplus\EsiClient\Log\LogInterface;
use Seatplus\EsiClient\Services\CheckAccess;

class EsiClient
{
    protected array $query_parameters = [];

    protected array $request_body = [];

    private readonly LogInterface $logger;

    public function __construct(
        private readonly ?EsiAuthentication $authentication = null,
        private ?GuzzleFetcher $fetcher = null,
        private ?CheckAccess $checkAccess = null
    ) {
        $this->fetcher ??= $this->createFetcher();
        $this->logger = $this->createLogger();
        $this->checkAccess ??= new CheckAccess($this->authentication);
    }

    private function createFetcher(): GuzzleFetcher
    {
        /** @var string $fetcher_class */
        $fetcher_class = $this->getConfiguration('fetcher');

        return new $fetcher_class($this->authentication);
    }

    /**
     * @throws RequestFailedException
     * @throws \Throwable
     * @throws UriDataMissingException
     * @throws InvalidAuthenticationException
     * @throws EsiScopeAccessDeniedException
     */
    public function invoke(
        string $method,
        string $uri_original,
        array $uri_data = [],
        string $version = 'latest',
        array $query_parameters = [],
        array $request_body = []
    ): EsiResponse {
        // Enrich the uri
        $uri = $this->buildDataUri($uri_original, $uri_data, $version, $query_parameters);

        // First check if access requirements are met
        if (! $this->hasAccess($method, $uri_original)) {
            // Log the deny.
            $this->logger->warning("Access denied to {$uri} due to missing scopes.");
            throw new EsiScopeAccessDeniedException("Access denied to {$uri}");
        }

        // Fetcher will take care of caching
        return $this->fetcher->call($method, $uri, $request_body);
    }

    private function createLogger(): LogInterface
    {
        return $this->getConfiguration()->getLogger();
    }

    private function getConfiguration(?string $property = null): EsiConfiguration|string
    {
        return $property ? EsiConfiguration::getInstance()->$property : EsiConfiguration::getInstance();
    }

    /**
     * @throws UriDataMissingException
     */
    private function buildDataUri(string $uri, array $data, string $version, array $query_parameters): \Psr\Http\Message\UriInterface
    {
        // Create a query string for the URI. We automatically
        // include the datasource value from the configuration.
        $query_params = array_merge(['datasource' => $this->getConfiguration('datasource')], $query_parameters);

        $path = sprintf(
            '/%s/%s/',
            rtrim($version, '/'), // remove a potential tailing slash,
            trim($this->mapDataToUri($uri, $data), '/')
        );

        return Uri::fromParts([
            'scheme' => $this->getConfiguration('esi_scheme'),
            'host' => $this->getConfiguration('esi_host'),
            'port' => $this->getConfiguration('esi_port'),
            'path' => $path,
            'query' => http_build_query($query_params),
        ]);
    }

    /**
     * @throws UriDataMissingException
     */
    private function mapDataToUri(string $uri, array $data): string
    {
        // Extract fields in curly braces. If there are fields,
        // replace the data with those in the URI
        if (preg_match_all('/{+(.*?)}/', $uri, $matches)) {
            if (empty($data)) {
                throw new UriDataMissingException("The data array for the uri {$uri} is empty. Please provide data to use.");
            }

            foreach ($matches[1] as $match) {
                if (! array_key_exists($match, $data)) {
                    throw new UriDataMissingException("Data for {$match} is missing. Please provide this by setting a value for {$match}.");
                }
                $uri = str_replace("{{$match}}", $data[$match], $uri);
            }
        }

        return $uri;
    }

    private function hasAccess(string $method, string $uri_original): bool
    {
        return $this->checkAccess->can($method, $uri_original);
    }
}