From 0d80ea02fc2868ee1aca560725538cbe2af44636 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Wed, 14 Aug 2024 17:01:11 -0700 Subject: [PATCH 01/11] Concept for providing set of PSR data --- src/Client.php | 1 + src/Transports.php | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 src/Transports.php diff --git a/src/Client.php b/src/Client.php index 6664889..8653188 100644 --- a/src/Client.php +++ b/src/Client.php @@ -48,6 +48,7 @@ class Client public function __construct( #[SensitiveParameter] ?string $secretKey = null, private string $apiHost = self::DEFAULT_API_HOST, + private ?Transports $transports = null, ) { // Auto-detect if not provided if ($secretKey === null) { diff --git a/src/Transports.php b/src/Transports.php new file mode 100644 index 0000000..9285543 --- /dev/null +++ b/src/Transports.php @@ -0,0 +1,18 @@ + Date: Wed, 14 Aug 2024 17:14:09 -0700 Subject: [PATCH 02/11] tighten concept a bit --- src/Client.php | 9 ++++- src/Transport/Curl.php | 57 ++++++++++++++++++++++++++++ src/Transport/Psr.php | 44 +++++++++++++++++++++ src/Transport/TransportInterface.php | 9 +++++ src/Transports.php | 18 --------- 5 files changed, 118 insertions(+), 19 deletions(-) create mode 100644 src/Transport/Curl.php create mode 100644 src/Transport/Psr.php create mode 100644 src/Transport/TransportInterface.php delete mode 100644 src/Transports.php diff --git a/src/Client.php b/src/Client.php index 8653188..0459daf 100644 --- a/src/Client.php +++ b/src/Client.php @@ -45,10 +45,12 @@ class Client private string $secretKey; + private Transport\TransportInterface $transport; + public function __construct( #[SensitiveParameter] ?string $secretKey = null, private string $apiHost = self::DEFAULT_API_HOST, - private ?Transports $transports = null, + ?Transport\TransportInterface $transport = null, ) { // Auto-detect if not provided if ($secretKey === null) { @@ -69,6 +71,10 @@ public function __construct( } $this->secretKey = $secretKey; + if ($transport === null) { + $transport = new Transport\Curl(); + } + $this->transport = $transport; } public function verifyAuthToken(string $authToken): AuthResponse @@ -108,6 +114,7 @@ public function attachRegistration(string $regToken, array $user): Credential */ public function makeApiCall(string $route, array $params): array { + // this->transport->makeApiCall($route, $params) // TODO: PSR-xx $json = json_encode($params, JSON_THROW_ON_ERROR); $ch = curl_init(); diff --git a/src/Transport/Curl.php b/src/Transport/Curl.php new file mode 100644 index 0000000..a595d7b --- /dev/null +++ b/src/Transport/Curl.php @@ -0,0 +1,57 @@ + sprintf('%s%s', $this->apiHost, $route), + CURLOPT_POST => 1, + CURLOPT_POSTFIELDS => $json, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => [ + 'Authorization: Basic ' . base64_encode(':' . $this->secretKey), + 'Accept: application/json', + 'Content-Type: application/json', + 'Content-Length: ' . strlen($json), + sprintf( + 'User-Agent: php-sdk/%s curl/%s php/%s', + InstalledVersions::getVersion('snapauth/sdk'), + curl_version()['version'] ?? 'unknown', + PHP_VERSION, + ), + sprintf('X-SDK: php/%s', InstalledVersions::getVersion('snapauth/sdk')), + ], + ]); + + try { + $response = curl_exec($ch); + $errno = curl_errno($ch); + $code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE); + + if ($response === false || $errno !== CURLE_OK) { + $this->error(); + } + + if ($code >= 300) { + $this->error(); + } + // Handle non-200s, non-JSON (severe upstream error) + assert(is_string($response)); + $decoded = json_decode($response, true, flags: JSON_THROW_ON_ERROR); + assert(is_array($decoded)); + return $decoded['result']; + } catch (JsonException) { + $this->error(); + } finally { + curl_close($ch); + } + } +} diff --git a/src/Transport/Psr.php b/src/Transport/Psr.php new file mode 100644 index 0000000..430217e --- /dev/null +++ b/src/Transport/Psr.php @@ -0,0 +1,44 @@ +streamFactory->createStream($json); + + $request = $this->requestFactory + ->createRequest(method: 'POST', uri: $uri) + ->withHeader('Authoriation', 'Basic blahblah') + ->withHeader('Accept', 'application/json') + ->withHeader('Content-type', 'application/json') + ->withHeader('Content-length', strlen($json)) + ->withHeader('User-agent', 'blahblah psr') + ->withHeader('X-SDK', 'php/%s'); + + // try/catch + $response = $this->client->sendRequest($request); + + $code = $response->getStatusCode(); + if ($code >= 300) { + // error + } + + $responseJson = (string) $response->getBody(); + // decode, index + } +} diff --git a/src/Transport/TransportInterface.php b/src/Transport/TransportInterface.php new file mode 100644 index 0000000..b763f94 --- /dev/null +++ b/src/Transport/TransportInterface.php @@ -0,0 +1,9 @@ + Date: Wed, 14 Aug 2024 17:15:28 -0700 Subject: [PATCH 03/11] Add the HTTP tools in dev --- composer.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/composer.json b/composer.json index aa74856..e509611 100644 --- a/composer.json +++ b/composer.json @@ -35,6 +35,8 @@ "phpstan/phpstan-phpunit": "^1.0", "phpstan/phpstan-strict-rules": "^1.0", "phpunit/phpunit": "^10.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.1", "squizlabs/php_codesniffer": "^3.5" }, "conflict": { From da796f1ae4e8205273a4ba457d844fd4f7236cb6 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Wed, 14 Aug 2024 17:20:43 -0700 Subject: [PATCH 04/11] Build out the concept a bit more --- src/Client.php | 4 ++-- src/Transport/Curl.php | 2 +- src/Transport/Psr.php | 2 +- src/Transport/TransportInterface.php | 9 +++++++++ 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Client.php b/src/Client.php index 0459daf..6938b7d 100644 --- a/src/Client.php +++ b/src/Client.php @@ -114,8 +114,7 @@ public function attachRegistration(string $regToken, array $user): Credential */ public function makeApiCall(string $route, array $params): array { - // this->transport->makeApiCall($route, $params) - // TODO: PSR-xx + // $result = $this->transport->makeApiCall($route, $params); $json = json_encode($params, JSON_THROW_ON_ERROR); $ch = curl_init(); curl_setopt_array($ch, [ @@ -176,6 +175,7 @@ public function __debugInfo(): array return [ 'apiHost' => $this->apiHost, 'secretKey' => substr($this->secretKey, 0, 9) . '***' . substr($this->secretKey, -2), + 'transport' => get_class($this->transport), ]; } } diff --git a/src/Transport/Curl.php b/src/Transport/Curl.php index a595d7b..cabc6da 100644 --- a/src/Transport/Curl.php +++ b/src/Transport/Curl.php @@ -4,7 +4,7 @@ namespace SnapAuth\Transport; -class Curl implements TransportInterface +final class Curl implements TransportInterface { public function makeApiCall(string $route, array $params): array { diff --git a/src/Transport/Psr.php b/src/Transport/Psr.php index 430217e..b8b0ec7 100644 --- a/src/Transport/Psr.php +++ b/src/Transport/Psr.php @@ -7,7 +7,7 @@ use Psr\Http\Client\ClientInterface; use Psr\Http\Message\{RequestFactoryInterface, StreamFactoryInterface}; -class Psr implements TransportInterface +final class Psr implements TransportInterface { public function __construct( public readonly ClientInterface $client, diff --git a/src/Transport/TransportInterface.php b/src/Transport/TransportInterface.php index b763f94..9bd954d 100644 --- a/src/Transport/TransportInterface.php +++ b/src/Transport/TransportInterface.php @@ -6,4 +6,13 @@ interface TransportInterface { + /** + * @internal This method is made public for cases where APIs do not have + * native SDK support, but is NOT considered part of the public, stable + * API and is not subject to SemVer. + * + * @param mixed[] $params + * @return mixed[] + */ + public function makeApiCall(string $route, array $params): array; } From c5c8c8665db4d6ca9d6429c7b9af64ab2912bee9 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Wed, 14 Aug 2024 17:28:26 -0700 Subject: [PATCH 05/11] Build out more --- src/Client.php | 47 +++------------------------- src/Transport/Curl.php | 9 +++--- src/Transport/Psr.php | 12 ++++--- src/Transport/Response.php | 17 ++++++++++ src/Transport/TransportInterface.php | 3 +- 5 files changed, 33 insertions(+), 55 deletions(-) create mode 100644 src/Transport/Response.php diff --git a/src/Client.php b/src/Client.php index 6938b7d..f80eb78 100644 --- a/src/Client.php +++ b/src/Client.php @@ -114,51 +114,12 @@ public function attachRegistration(string $regToken, array $user): Credential */ public function makeApiCall(string $route, array $params): array { - // $result = $this->transport->makeApiCall($route, $params); - $json = json_encode($params, JSON_THROW_ON_ERROR); - $ch = curl_init(); - curl_setopt_array($ch, [ - CURLOPT_URL => sprintf('%s%s', $this->apiHost, $route), - CURLOPT_POST => 1, - CURLOPT_POSTFIELDS => $json, - CURLOPT_RETURNTRANSFER => true, - CURLOPT_HTTPHEADER => [ - 'Authorization: Basic ' . base64_encode(':' . $this->secretKey), - 'Accept: application/json', - 'Content-Type: application/json', - 'Content-Length: ' . strlen($json), - sprintf( - 'User-Agent: php-sdk/%s curl/%s php/%s', - InstalledVersions::getVersion('snapauth/sdk'), - curl_version()['version'] ?? 'unknown', - PHP_VERSION, - ), - sprintf('X-SDK: php/%s', InstalledVersions::getVersion('snapauth/sdk')), - ], - ]); - - try { - $response = curl_exec($ch); - $errno = curl_errno($ch); - $code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE); - - if ($response === false || $errno !== CURLE_OK) { - $this->error(); - } - - if ($code >= 300) { - $this->error(); - } - // Handle non-200s, non-JSON (severe upstream error) - assert(is_string($response)); - $decoded = json_decode($response, true, flags: JSON_THROW_ON_ERROR); - assert(is_array($decoded)); - return $decoded['result']; - } catch (JsonException) { + $result = $this->transport->makeApiCall($route, $params); + if ($result->code >= 300) { $this->error(); - } finally { - curl_close($ch); } + $decoded = $result->decoded; + return $decoded['result']; } /** diff --git a/src/Transport/Curl.php b/src/Transport/Curl.php index cabc6da..f6c6d57 100644 --- a/src/Transport/Curl.php +++ b/src/Transport/Curl.php @@ -4,9 +4,11 @@ namespace SnapAuth\Transport; +use JsonException; + final class Curl implements TransportInterface { - public function makeApiCall(string $route, array $params): array + public function makeApiCall(string $route, array $params): Response { // TODO: PSR-xx $json = json_encode($params, JSON_THROW_ON_ERROR); @@ -40,14 +42,11 @@ public function makeApiCall(string $route, array $params): array $this->error(); } - if ($code >= 300) { - $this->error(); - } // Handle non-200s, non-JSON (severe upstream error) assert(is_string($response)); $decoded = json_decode($response, true, flags: JSON_THROW_ON_ERROR); assert(is_array($decoded)); - return $decoded['result']; + return new Response(code: $code, decoded: $decoded); } catch (JsonException) { $this->error(); } finally { diff --git a/src/Transport/Psr.php b/src/Transport/Psr.php index b8b0ec7..d81cc04 100644 --- a/src/Transport/Psr.php +++ b/src/Transport/Psr.php @@ -16,7 +16,7 @@ public function __construct( ) { } - public function makeApiCall(string $route, array $params): array + public function makeApiCall(string $route, array $params): Response { $json = json_encode($params, JSON_THROW_ON_ERROR); $stream = $this->streamFactory->createStream($json); @@ -34,11 +34,13 @@ public function makeApiCall(string $route, array $params): array $response = $this->client->sendRequest($request); $code = $response->getStatusCode(); - if ($code >= 300) { - // error - } $responseJson = (string) $response->getBody(); - // decode, index + if (!json_validate($responseJson)) { + // ?? + } + $decoded = json_decode($responseJson, true, flags: JSON_THROW_ON_ERROR); + assert(is_array($decoded)); + return new Response($code, $decoded); } } diff --git a/src/Transport/Response.php b/src/Transport/Response.php new file mode 100644 index 0000000..93e689e --- /dev/null +++ b/src/Transport/Response.php @@ -0,0 +1,17 @@ + Date: Wed, 14 Aug 2024 17:32:27 -0700 Subject: [PATCH 06/11] More concise format --- src/Client.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Client.php b/src/Client.php index f80eb78..6a8ad1c 100644 --- a/src/Client.php +++ b/src/Client.php @@ -71,10 +71,7 @@ public function __construct( } $this->secretKey = $secretKey; - if ($transport === null) { - $transport = new Transport\Curl(); - } - $this->transport = $transport; + $this->transport = $transport ?? new Transport\Curl(); } public function verifyAuthToken(string $authToken): AuthResponse From 73221df6f275b236ba744b305d71d02346f4e438 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Wed, 14 Aug 2024 17:33:42 -0700 Subject: [PATCH 07/11] move over some imports --- src/Client.php | 17 ----------------- src/Transport/Curl.php | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Client.php b/src/Client.php index 6a8ad1c..d03971d 100644 --- a/src/Client.php +++ b/src/Client.php @@ -5,17 +5,9 @@ namespace SnapAuth; use Composer\InstalledVersions; -use JsonException; use SensitiveParameter; use function assert; -use function curl_close; -use function curl_errno; -use function curl_exec; -use function curl_getinfo; -use function curl_init; -use function curl_setopt_array; -use function curl_version; use function is_array; use function is_string; use function json_decode; @@ -23,15 +15,6 @@ use function sprintf; use function strlen; -use const CURLE_OK; -use const CURLINFO_RESPONSE_CODE; -use const CURLOPT_HTTPHEADER; -use const CURLOPT_POST; -use const CURLOPT_POSTFIELDS; -use const CURLOPT_RETURNTRANSFER; -use const CURLOPT_URL; -use const JSON_THROW_ON_ERROR; - /** * SDK Prototype. This makes no attempt to short-circuit the network for * internal use, forcing a completely dogfooded experience. diff --git a/src/Transport/Curl.php b/src/Transport/Curl.php index f6c6d57..791a05f 100644 --- a/src/Transport/Curl.php +++ b/src/Transport/Curl.php @@ -6,6 +6,23 @@ use JsonException; +use function curl_close; +use function curl_errno; +use function curl_exec; +use function curl_getinfo; +use function curl_init; +use function curl_setopt_array; +use function curl_version; + +use const CURLE_OK; +use const CURLINFO_RESPONSE_CODE; +use const CURLOPT_HTTPHEADER; +use const CURLOPT_POST; +use const CURLOPT_POSTFIELDS; +use const CURLOPT_RETURNTRANSFER; +use const CURLOPT_URL; +use const JSON_THROW_ON_ERROR; + final class Curl implements TransportInterface { public function makeApiCall(string $route, array $params): Response From 12b721495222b88ddcd7ad0747b5978c8c7c8810 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Wed, 14 Aug 2024 17:34:51 -0700 Subject: [PATCH 08/11] Comment and tidy up --- src/Transport/Response.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Transport/Response.php b/src/Transport/Response.php index 93e689e..a07d2fe 100644 --- a/src/Transport/Response.php +++ b/src/Transport/Response.php @@ -4,10 +4,14 @@ namespace SnapAuth\Transport; +/** + * This is a vastly simplified version of PSR-7 ResponseInterface. It's used to + * avoid forcing an external dependency on clients. + */ final class Response { /** - * @param mixed[] $decodedResponse + * @param mixed[] $decoded */ public function __construct( public readonly int $code, From 765208c4b62bbc2496ce5198b145b85dac48c654 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Wed, 14 Aug 2024 17:39:00 -0700 Subject: [PATCH 09/11] Solidify the url handling a bit --- src/Client.php | 3 ++- src/Transport/Curl.php | 4 ++-- src/Transport/Psr.php | 6 +++--- src/Transport/TransportInterface.php | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Client.php b/src/Client.php index d03971d..eb7c9d7 100644 --- a/src/Client.php +++ b/src/Client.php @@ -94,7 +94,8 @@ public function attachRegistration(string $regToken, array $user): Credential */ public function makeApiCall(string $route, array $params): array { - $result = $this->transport->makeApiCall($route, $params); + $url = sprintf('%s%s', $this->apiHost, $route); + $result = $this->transport->makeApiCall(url: $url, params: $params); if ($result->code >= 300) { $this->error(); } diff --git a/src/Transport/Curl.php b/src/Transport/Curl.php index 791a05f..b30f634 100644 --- a/src/Transport/Curl.php +++ b/src/Transport/Curl.php @@ -25,13 +25,13 @@ final class Curl implements TransportInterface { - public function makeApiCall(string $route, array $params): Response + public function makeApiCall(string $url, array $params): Response { // TODO: PSR-xx $json = json_encode($params, JSON_THROW_ON_ERROR); $ch = curl_init(); curl_setopt_array($ch, [ - CURLOPT_URL => sprintf('%s%s', $this->apiHost, $route), + CURLOPT_URL => $url, CURLOPT_POST => 1, CURLOPT_POSTFIELDS => $json, CURLOPT_RETURNTRANSFER => true, diff --git a/src/Transport/Psr.php b/src/Transport/Psr.php index d81cc04..f4dc566 100644 --- a/src/Transport/Psr.php +++ b/src/Transport/Psr.php @@ -16,17 +16,17 @@ public function __construct( ) { } - public function makeApiCall(string $route, array $params): Response + public function makeApiCall(string $url, array $params): Response { $json = json_encode($params, JSON_THROW_ON_ERROR); $stream = $this->streamFactory->createStream($json); $request = $this->requestFactory - ->createRequest(method: 'POST', uri: $uri) + ->createRequest(method: 'POST', uri: $url) ->withHeader('Authoriation', 'Basic blahblah') ->withHeader('Accept', 'application/json') ->withHeader('Content-type', 'application/json') - ->withHeader('Content-length', strlen($json)) + ->withHeader('Content-length', (string) strlen($json)) ->withHeader('User-agent', 'blahblah psr') ->withHeader('X-SDK', 'php/%s'); diff --git a/src/Transport/TransportInterface.php b/src/Transport/TransportInterface.php index 55c9c56..0eb9707 100644 --- a/src/Transport/TransportInterface.php +++ b/src/Transport/TransportInterface.php @@ -13,5 +13,5 @@ interface TransportInterface * * @param mixed[] $params */ - public function makeApiCall(string $route, array $params): Response; + public function makeApiCall(string $url, array $params): Response; } From 08762031dff7d6d11f082a4305b8f84d6285f292 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Wed, 14 Aug 2024 17:39:42 -0700 Subject: [PATCH 10/11] Task notes --- src/Transport/Psr.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Transport/Psr.php b/src/Transport/Psr.php index f4dc566..b2cee06 100644 --- a/src/Transport/Psr.php +++ b/src/Transport/Psr.php @@ -23,12 +23,12 @@ public function makeApiCall(string $url, array $params): Response $request = $this->requestFactory ->createRequest(method: 'POST', uri: $url) - ->withHeader('Authoriation', 'Basic blahblah') + ->withHeader('Authoriation', 'Basic blahblah') // FIXME ->withHeader('Accept', 'application/json') ->withHeader('Content-type', 'application/json') ->withHeader('Content-length', (string) strlen($json)) - ->withHeader('User-agent', 'blahblah psr') - ->withHeader('X-SDK', 'php/%s'); + ->withHeader('User-agent', 'blahblah psr') // FIXME + ->withHeader('X-SDK', 'php/%s'); // FIXME // try/catch $response = $this->client->sendRequest($request); From 269b282a0dcc9630e784b7223b908eb3cf7ead87 Mon Sep 17 00:00:00 2001 From: Eric Stern Date: Wed, 14 Aug 2024 17:41:50 -0700 Subject: [PATCH 11/11] Fix import --- src/Client.php | 1 - src/Transport/Curl.php | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Client.php b/src/Client.php index eb7c9d7..5a19123 100644 --- a/src/Client.php +++ b/src/Client.php @@ -4,7 +4,6 @@ namespace SnapAuth; -use Composer\InstalledVersions; use SensitiveParameter; use function assert; diff --git a/src/Transport/Curl.php b/src/Transport/Curl.php index b30f634..4248875 100644 --- a/src/Transport/Curl.php +++ b/src/Transport/Curl.php @@ -4,6 +4,7 @@ namespace SnapAuth\Transport; +use Composer\InstalledVersions; use JsonException; use function curl_close;