Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PSR-17 and PSR-18 compability #5

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
/.gitlab-ci.yml export-ignore
/.php-cs-fixer.dist.php export-ignore
/phpstan.neon export-ignore
/Tests/ export-ignore
/tests/ export-ignore
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# PHP client for Canto API

## Setup

```shell
composer require fairway/canto-saas-api guzzlehttp/guzzle
```

## Example usage

```php
Expand All @@ -21,3 +27,26 @@ $allFolders = $client->libraryTree()
->getTree(new GetTreeRequest())
->getResults();
```

### Use with any PSR-17 and PSR-18 compatible http-client and http-factory

If you don't want to use [guzzlehttp/guzzle](https://packagist.org/packages/guzzlehttp/guzzle) as your http-client, you can work with any [PSR-17](https://www.php-fig.org/psr/psr-17/) compatible http-factory and [PSR-18](https://www.php-fig.org/psr/psr-18/) compatible http-client.

Simply pass instances to the `ClientOptions` options, like that:

```php
$clientOptions = new ClientOptions([
'cantoName' => 'my-canto-name',
'cantoDomain' => 'canto.de',
'appId' => '123456789',
'appSecret' => 'my-app-secret',
'httpClient' => new Psr18CompatibleHttpClient(),
'httpRequestFactory' => new Psr17CompatibleHttpRequestFactory(),
]);

# instantiate and use the client as normal.
$client = new Client($clientOptions);

# if you want to create a request, you can do this from the client itself:
$client->createRequest($method, $url); # will return a \Psr\Http\Message\RequestInterface
```
11 changes: 8 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,16 @@
"require": {
"php": "^7.4 || ^8.0",
"ext-json": "*",
"guzzlehttp/guzzle": "^6.3 || ^7.3",
"psr/log": "^1.1 || ^3.0"
"psr/log": "^1.1 || ^3.0",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0"
},
"require-dev": {
"phpunit/phpunit": "^9.5",
"phpstan/phpstan": "^1.2",
"friendsofphp/php-cs-fixer": "^3.3",
"phpro/grumphp": "^1.5"
"phpro/grumphp": "^1.5",
"guzzlehttp/guzzle": "^6.3 || ^7.3"
},
"autoload": {
"psr-4": {
Expand All @@ -41,5 +43,8 @@
"psr-4": {
"Fairway\\CantoSaasApi\\Tests\\": "tests"
}
},
"suggest": {
"guzzlehttp/guzzle": "Recommended default implementation of the psr-18 http-client interface."
}
}
157 changes: 123 additions & 34 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,21 @@
use Fairway\CantoSaasApi\Endpoint\Authorization\OAuth2;
use Fairway\CantoSaasApi\Endpoint\LibraryTree;
use Fairway\CantoSaasApi\Endpoint\Upload;
use Fairway\CantoSaasApi\Exception\HttpClientException;
use Fairway\CantoSaasApi\Helper\MdcUrlHelper;
use Fairway\CantoSaasApi\Http\Authorization\OAuth2Request;
use Fairway\CantoSaasApi\Http\Authorization\OAuth2Response;
use GuzzleHttp\ClientInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriFactoryInterface;
use Psr\Http\Message\UriInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;

class Client
class Client implements RequestFactoryInterface, UriFactoryInterface, StreamFactoryInterface
{
protected const API_VERSION = 'v1';
protected const API_ROUTE = 'https://%s.%s/api';
Expand All @@ -34,47 +41,41 @@ class Client

protected ClientInterface $httpClient;

protected RequestFactoryInterface $requestFactory;

protected UriFactoryInterface $uriFactory;

protected StreamFactoryInterface $streamFactory;

protected ?string $accessToken = null;

public function __construct(ClientOptions $options)
{
$this->options = $options;
$this->httpClient = $this->options->getHttpClient() ?? $this->buildHttpClient();
$this->logger = $this->options->getLogger() ?? new NullLogger();
}

public function getOptions(): ClientOptions
{
return $this->options;
}

public function getLogger(): LoggerInterface
{
return $this->logger;
$this->requestFactory = $this->options->getHttpRequestFactory() ?? $this->buildRequestFactory();
$this->uriFactory = $this->options->getUriFactory() ?? $this->buildUriFactory();
$this->streamFactory = $this->options->getStreamFactory() ?? $this->buildStreamFactory();
}

public function getHttpClient(): ClientInterface
{
return $this->httpClient;
}

public function getAccessToken(): ?string
{
return $this->accessToken;
}

public function setAccessToken(string $accessToken): void
public function getLogger(): LoggerInterface
{
if ($accessToken !== '') {
$this->accessToken = $accessToken;
}
return $this->logger;
}

/**
* @throws Endpoint\Authorization\AuthorizationFailedException|Endpoint\Authorization\NotAuthorizedException
*/
public function authorizeWithClientCredentials(string $userId = '', string $scope = OAuth2Request::SCOPE_ADMIN): OAuth2Response
{
public function authorizeWithClientCredentials(
string $userId = '',
string $scope = OAuth2Request::SCOPE_ADMIN
): OAuth2Response {
$request = new OAuth2Request();
$request->setAppId($this->options->getAppId())
->setAppSecret($this->options->getAppSecret())
Expand All @@ -91,6 +92,28 @@ public function authorizeWithClientCredentials(string $userId = '', string $scop
return $response;
}

public function getAccessToken(): ?string
{
return $this->accessToken;
}

public function setAccessToken(string $accessToken): void
{
if ($accessToken !== '') {
$this->accessToken = $accessToken;
}
}

/**
* @param string $method
* @param UriInterface|string $uri
* @return RequestInterface
*/
public function createRequest(string $method, $uri): RequestInterface
{
return $this->requestFactory->createRequest($method, $uri);
}

public function asset(): Asset
{
return new Asset($this);
Expand All @@ -114,41 +137,107 @@ public function mdc(): MdcUrlHelper
public function getApiUrl(string $path = null): string
{
$url = sprintf(
self::API_ROUTE,
static::API_ROUTE,
$this->getOptions()->getCantoName(),
$this->getOptions()->getCantoDomain(),
);
return sprintf(
'%s/%s/%s',
$url,
self::API_VERSION,
static::API_VERSION,
$path ?? ''
);
}

public function getOptions(): ClientOptions
{
return $this->options;
}

public function getMdcUrl(string $assetId = '', string $scheme = 'image'): string
{
$path = '';
if ($assetId) {
$path = sprintf('%s_%s/', $scheme, $assetId);
}
return sprintf(
self::MDC_ROUTE,
static::MDC_ROUTE,
$this->options->getMdcDomainName(),
$this->options->getMdcAwsAccountId(),
$path,
);
}

public function createUri(string $uri = ''): UriInterface
{
return $this->uriFactory->createUri($uri);
}

public function createStream(string $content = ''): StreamInterface
{
return $this->streamFactory->createStream($content);
}

public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface
{
return $this->streamFactory->createStreamFromFile($filename, $mode);
}

public function createStreamFromResource($resource): StreamInterface
{
return $this->streamFactory->createStreamFromResource($resource);
}

protected function buildHttpClient(): ClientInterface
{
return new \GuzzleHttp\Client([
'allow_redirects' => true,
'connect_timeout' => (int)$this->options->getHttpClientOptions()['timeout'],
'debug' => (bool)$this->options->getHttpClientOptions()['debug'],
'headers' => [
'userAgent' => $this->options->getHttpClientOptions()['userAgent'],
],
]);
if (class_exists('\GuzzleHttp\Client')) {
return new \GuzzleHttp\Client([
'allow_redirects' => true,
'connect_timeout' => (int)$this->options->getHttpClientOptions()['timeout'],
'debug' => (bool)$this->options->getHttpClientOptions()['debug'],
'headers' => [
'userAgent' => $this->options->getHttpClientOptions()['userAgent'],
],
]);
}

throw HttpClientException::noDefaultHttpClient();
}

protected function buildRequestFactory(): RequestFactoryInterface
{
if (class_exists('\GuzzleHttp\Psr7\Request')) {
return new class () implements RequestFactoryInterface {
public function createRequest(string $method, $uri): RequestInterface
{
return new \GuzzleHttp\Psr7\Request($method, $uri);
}
};
}

throw HttpClientException::noDefaultHttpRequestFactory();
}

protected function buildUriFactory(): UriFactoryInterface
{
if (class_exists('\GuzzleHttp\Psr7\Uri')) {
return new class () implements UriFactoryInterface {
public function createUri(string $uri = ''): UriInterface
{
return new \GuzzleHttp\Psr7\Uri($uri);
}
};
}

throw HttpClientException::noDefaultUriFactory();
}

private function buildStreamFactory(): StreamFactoryInterface
{
if (class_exists('\GuzzleHttp\Psr7\HttpFactory')) {
return new \GuzzleHttp\Psr7\HttpFactory();
}

throw HttpClientException::noDefaultStreamFactory();
}
}
32 changes: 31 additions & 1 deletion src/ClientOptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@

namespace Fairway\CantoSaasApi;

use GuzzleHttp\ClientInterface;
use InvalidArgumentException;
use JetBrains\PhpStorm\ArrayShape;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\UriFactoryInterface;
use Psr\Log\LoggerInterface;

/**
Expand All @@ -36,6 +39,9 @@ class ClientOptions
'timeout' => 'int',
'userAgent' => 'string',
],
'httpRequestFactory' => RequestFactoryInterface::class,
'uriFactory' => UriFactoryInterface::class,
'streamFactory' => StreamFactoryInterface::class,
'logger' => LoggerInterface::class
];

Expand All @@ -59,6 +65,12 @@ class ClientOptions

private array $httpClientOptions;

private ?RequestFactoryInterface $httpRequestFactory;

private ?UriFactoryInterface $uriFactory;

private ?StreamFactoryInterface $streamFactory;

private ?LoggerInterface $logger;

/**
Expand All @@ -83,6 +95,9 @@ public function __construct(array $options = [])
],
$options['httpClientOptions'] ?? []
);
$this->httpRequestFactory = $options['httpRequestFactory'] ?? null;
$this->uriFactory = $options['uriFactory'] ?? null;
$this->streamFactory = $options['streamFactory'] ?? null;
$this->logger = $options['logger'] ?? null;
$this->mdcAwsAccountId = $options['mdcAwsAccountId'] ?? '';
$this->mdcDomainName = $options['mdcDomainName'] ?? '';
Expand Down Expand Up @@ -124,6 +139,21 @@ public function getHttpClientOptions(): array
return $this->httpClientOptions;
}

public function getHttpRequestFactory(): ?RequestFactoryInterface
{
return $this->httpRequestFactory;
}

public function getUriFactory(): ?UriFactoryInterface
{
return $this->uriFactory;
}

public function getStreamFactory(): ?StreamFactoryInterface
{
return $this->streamFactory;
}

public function getMdcDomainName(): string
{
return $this->mdcDomainName;
Expand Down
Loading