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

feat: adding storage client #191

Merged
merged 16 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@
"Leaderboard\\": "types/Leaderboard/",
"Permission_messages\\": "types/Permission_messages/",
"Token\\": "types/Token/",
"Webhook\\": "types/Webhook/"
"Webhook\\": "types/Webhook/",
"Store\\": "types/Store/"
},
"files": [
"src/Utilities/_DataValidation.php"
],
"classmap": [
"src/Cache/CacheOperationTypes/CacheOperationTypes.php",
"src/Storage/StorageOperationTypes/StorageOperationTypes.php",
"src/Cache/Errors/Errors.php"
]
},
Expand Down
2 changes: 1 addition & 1 deletion examples/composer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"require": {
"momentohq/client-sdk-php": "1.4.0",
"momentohq/client-sdk-php": "1.9.1",
"monolog/monolog": "^2.5"
}
}
125 changes: 125 additions & 0 deletions examples/storage-example.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<?php
declare(strict_types=1);
require "vendor/autoload.php";

use Momento\Auth\CredentialProvider;
use Momento\Config\Configurations\StorageLaptop;
use Momento\Logging\StderrLoggerFactory;
use Psr\Log\LoggerInterface;
use Momento\Storage\PreviewStorageClient;
use Momento\Storage\StorageOperationTypes\StorageValueType;

$STORE_NAME = uniqid("php-storage-example-");
$VALUES = [
"str"=> "StringValue",
"int" => 123,
"double" => 123.456,
"fakeout" => "123"
];

// Setup
$authProvider = CredentialProvider::fromEnvironmentVariable("MOMENTO_AUTH_TOKEN");
$configuration = StorageLaptop::latest(new StderrLoggerFactory());
$client = new PreviewStorageClient($configuration, $authProvider);
$logger = $configuration->getLoggerFactory()->getLogger("ex:");

function printBanner(string $message, LoggerInterface $logger): void
{
$line = "**************************************************************************";
$logger->info($line);
$logger->info($message);
$logger->info($line);
}

// Used to tear down temporary store after failure or completion of script
function deleteStore(string $storeName, LoggerInterface $logger, PreviewStorageClient $client): void
{
$logger->info("Deleting store $storeName\n");
$response = $client->deleteStore($storeName);
if ($response->asError()) {
$logger->info("Error deleting store: " . $response->asError()->message() . "\n");
exit(1);
}
}

printBanner("* Momento Storage Example Start *", $logger);

// Ensure test store exists
$response = $client->createStore($STORE_NAME);
if ($response->asSuccess()) {
$logger->info("Created store " . $STORE_NAME . "\n");
} elseif ($response->asError()) {
$logger->info("Error creating store: " . $response->asError()->message() . "\n");
exit(1);
} elseif ($response->asAlreadyExists()) {
$logger->info("Store " . $STORE_NAME . " already exists.\n");
}

// List stores
$response = $client->listStores();
if ($response->asSuccess()) {
$logger->info("SUCCESS: List stores: \n");
foreach ($response->asSuccess()->stores() as $store) {
$storeName = $store->name();
$logger->info("$storeName\n");
}
$logger->info("\n");
} elseif ($response->asError()) {
$logger->info("Error listing store: " . $response->asError()->message() . "\n");
deleteStore($STORE_NAME, $logger, $client);
exit(1);
}

// Set
foreach ($VALUES as $key => $value) {
$logger->info("Setting key: '$key' to value: '$value', type = " . get_class($value) . "\n");
$response = $client->set($STORE_NAME, $key, $value);
if ($response->asSuccess()) {
$logger->info("SUCCESS\n");
} elseif ($response->asError()) {
$logger->info("Error setting key: " . $response->asError()->message() . "\n");
deleteStore($STORE_NAME, $logger, $client);
exit(1);
}
}

// Get
foreach ($VALUES as $key => $value) {
$logger->info("Getting value for key: '$key'\n");
$response = $client->get($STORE_NAME, $key);
if ($response->asSuccess()) {
$logger->info("SUCCESS\n");
$valueType = $response->asSuccess()->type();
if ($valueType == StorageValueType::STRING) {
print("Got string value: " . $response->asSuccess()->tryGetString() . "\n");
} elseif ($valueType == StorageValueType::INTEGER) {
print("Got integer value: " . $response->asSuccess()->tryGetInteger() . "\n");
} elseif ($valueType == StorageValueType::DOUBLE) {
print("Got double value: " . $response->asSuccess()->tryGetDouble() . "\n");
} elseif ($valueType == StorageValueType::BYTES) {
// This case is not expected in this example as PHP doesn't have a native byte type
print("Got bytes value: " . $response->asSuccess()->tryGetBytes() . "\n");
}
} elseif ($response->asError()) {
$logger->info("Error getting key: " . $response->asError()->message() . "\n");
deleteStore($STORE_NAME, $logger, $client);
exit(1);
}
}

// Delete
foreach (array_keys($VALUES) as $key) {
$logger->info("Deleting key: '$key'\n");
$response = $client->delete($STORE_NAME, $key);
if ($response->asSuccess()) {
$logger->info("SUCCESS\n");
} elseif ($response->asError()) {
$logger->info("Error deleting key: " . $response->asError()->message() . "\n");
exit(1);
}
}

// Delete store
deleteStore($STORE_NAME, $logger, $client);

printBanner("* Momento Storage Example End *", $logger);
2 changes: 2 additions & 0 deletions src/Auth/AuthUtils.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public static function parseV1Token(string $authToken): object {
$payload = new \stdClass();
$payload->c = "cache.{$tokenData->endpoint}";
$payload->cp = "control.{$tokenData->endpoint}";
$payload->storage = "storage.{$tokenData->endpoint}";
$payload->authToken = $tokenData->api_key;
return $payload;
}
Expand All @@ -47,6 +48,7 @@ public static function parseJwtToken(string $authToken): object
try {
$payload = $exploded[1];
$payload = JWT::jsonDecode(JWT::urlsafeB64Decode($payload));
$payload->storage = null;
pgautier404 marked this conversation as resolved.
Show resolved Hide resolved
$payload->authToken = $authToken;
} catch (\Exception $e) {
self::throwBadAuthToken();
Expand Down
3 changes: 2 additions & 1 deletion src/Auth/EnvMomentoTokenProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public function __construct(
string $envVariableName,
?string $controlEndpoint = null,
?string $cacheEndpoint = null,
?string $storageEndpoint = null,
?string $trustedControlEndpointCertificateName = null,
?string $trustedCacheEndpointCertificateName = null
)
Expand All @@ -23,6 +24,6 @@ public function __construct(
throw new InvalidArgumentError("Environment variable $envVariableName is empty or null.");
}
$authToken = $_SERVER[$envVariableName];
parent::__construct($authToken, $controlEndpoint, $cacheEndpoint, $trustedControlEndpointCertificateName, $trustedCacheEndpointCertificateName);
parent::__construct($authToken, $controlEndpoint, $cacheEndpoint, $storageEndpoint, $trustedControlEndpointCertificateName, $trustedCacheEndpointCertificateName);
}
}
14 changes: 14 additions & 0 deletions src/Auth/StringMomentoTokenProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,18 @@ class StringMomentoTokenProvider extends CredentialProvider
protected string $authToken;
protected ?string $controlEndpoint = null;
protected ?string $cacheEndpoint = null;
protected ?string $storageEndpoint = null;
protected ?string $trustedControlEndpointCertificateName = null;
protected ?string $trustedCacheEndpointCertificateName = null;

public function __construct(
string $authToken,
?string $controlEndpoint = null,
?string $cacheEndpoint = null,
// TODO: adding this arg would be a breaking change for anyone currently passing in
// endpointCertificateName arguments. I am pretty sure that is 0 people, but wanted
// to call it out.
?string $storageEndpoint = null,
malandis marked this conversation as resolved.
Show resolved Hide resolved
?string $trustedControlEndpointCertificateName = null,
?string $trustedCacheEndpointCertificateName = null
)
Expand All @@ -38,6 +43,7 @@ public function __construct(
$this->authToken = $payload->authToken;
$this->controlEndpoint = $controlEndpoint ?? $payload->cp;
$this->cacheEndpoint = $cacheEndpoint ?? $payload->c;
$this->storageEndpoint = $storageEndpoint ?? $payload->storage;
$this->trustedControlEndpointCertificateName = $trustedControlEndpointCertificateName;
$this->trustedCacheEndpointCertificateName = $trustedCacheEndpointCertificateName;
}
Expand Down Expand Up @@ -66,6 +72,14 @@ public function getControlEndpoint(): string
return $this->controlEndpoint;
}

/**
* @return string|null The host which the Momento client will connect to for Momento storage operations.
*/
public function getStorageEndpoint(): ?string
{
return $this->storageEndpoint;
}

/**
* @return string|null Used for routing gRPC calls through a proxy server
*/
Expand Down
41 changes: 41 additions & 0 deletions src/Cache/Errors/Errors.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,21 @@ abstract class MomentoErrorCode
public const ALREADY_EXISTS_ERROR = "ALREADY_EXISTS_ERROR";
/**
* Cache with specified name doesn't exist
* @deprecated Use CacheNotFoundError instead
*/
public const NOT_FOUND_ERROR = "NOT_FOUND_ERROR";
/**
* Cache with specified name doesn't exist
*/
public const CACHE_NOT_FOUND_ERROR = "NOT_FOUND_ERROR";
/**
* Store with specified name doesn't exist
*/
public const STORE_NOT_FOUND_ERROR = "STORE_NOT_FOUND_ERROR";
/**
* Item with specified name doesn't exist
*/
public const ITEM_NOT_FOUND_ERROR = "ITEM_NOT_FOUND_ERROR";
Comment on lines +33 to +36
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should call this STORE_ITEM... to disambiguate vs cache items.

Copy link
Collaborator Author

@pgautier404 pgautier404 Jun 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no not found error for cache items-- those are represented as misses. This is also how this is defined in the JS SDK. I'm not necessarily opposed to changing this error code to be specific to storage for future-proofing, but we should be consistent across SDKs. I'm going to leave this for now to maintain consistency with other SDKs but am happy to discuss further.

/**
* An unexpected error occurred while trying to fulfill the request
*/
Expand Down Expand Up @@ -216,13 +229,41 @@ class LimitExceededError extends SdkError

/**
* Cache with specified name doesn't exist
* @deprecated Use CacheNotFoundError instead
*/
class NotFoundError extends SdkError
{
public string $errorCode = MomentoErrorCode::NOT_FOUND_ERROR;
public string $messageWrapper = 'A cache with the specified name does not exist. To resolve this error, make sure you have created the cache before attempting to use it';
}

/**
* Cache with specified name doesn't exist
*/
class CacheNotFoundError extends SdkError
{
public string $errorCode = MomentoErrorCode::CACHE_NOT_FOUND_ERROR;
public string $messageWrapper = 'A cache with the specified name does not exist. To resolve this error, make sure you have created the cache before attempting to use it';
}

/**
* Store with specified name doesn't exist
*/
class StoreNotFoundError extends SdkError
{
public string $errorCode = MomentoErrorCode::STORE_NOT_FOUND_ERROR;
public string $messageWrapper = 'A store with the specified name does not exist. To resolve this error, make sure you have created the store before attempting to use it';
}

/**
* Item with specified name doesn't exist
*/
class ItemNotFoundError extends SdkError
{
public string $errorCode = MomentoErrorCode::ITEM_NOT_FOUND_ERROR;
public string $messageWrapper = 'An item with the specified name does not exist. To resolve this error, make sure you have created the item before attempting to use it';
}

Comment on lines +258 to +266
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename should extend here too.

/**
* Insufficient permissions to perform operation
*/
Expand Down
54 changes: 54 additions & 0 deletions src/Cache/Internal/IdleStorageDataClientWrapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);

namespace Momento\Cache\Internal;

use Momento\Config\IConfiguration;
use Momento\Config\IStorageConfiguration;
use Momento\Storage\Internal\StorageDataClient;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;

class IdleStorageDataClientWrapper implements LoggerAwareInterface {

private StorageDataClient $client;
private LoggerInterface $logger;
private object $clientFactory;
private ?int $maxIdleMillis;
private int $lastAccessTime;

public function __construct(object $clientFactory, IStorageConfiguration $configuration) {
$this->clientFactory = $clientFactory;
$this->client = ($clientFactory->callback)();
$this->logger = $configuration->getLoggerFactory()->getLogger(get_class($this));
$this->maxIdleMillis = $configuration->getTransportStrategy()->getMaxIdleMillis();
$this->lastAccessTime = $this->getMilliseconds();
}

public function setLogger(LoggerInterface $logger): void
{
$this->logger = $logger;
}

public function getClient(): StorageDataClient {
if ($this->maxIdleMillis === null) {
return $this->client;
}
$this->logger->debug("Checking to see if client has been idle for more than {$this->maxIdleMillis}");
if ($this->getMilliseconds() - $this->lastAccessTime > $this->maxIdleMillis) {
$this->logger->debug("Client has been idle for more than {$this->maxIdleMillis}; reconnecting");
$this->client->close();
$this->client = ($this->clientFactory->callback)();
}
$this->lastAccessTime = $this->getMilliseconds();
return $this->client;
}

public function close(): void {
$this->client->close();
}

private function getMilliseconds(): int {
return (int)(gettimeofday(true) * 1000);
}
}
Loading
Loading