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: typesafety for client options (new surface only) #450

Merged
merged 29 commits into from
Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f636d3b
feat: add classes for client options
bshaffer Mar 31, 2023
0041313
drop php 7.3 and below
bshaffer Mar 31, 2023
fc0aa58
fix lint errors
bshaffer Mar 31, 2023
5e8c3fd
revert changes to protobuf conflict test (not sure why this is failing)
bshaffer Mar 31, 2023
c9b8971
Delete CallIOptions.php
bshaffer Mar 31, 2023
e0059a3
Update OptionsTrait.php
bshaffer Mar 31, 2023
0c94606
use TransportOptions in ClientOptions
bshaffer Mar 31, 2023
9a802c2
Merge branch 'add-options-classes' of github.com:googleapis/gax-php i…
bshaffer Mar 31, 2023
1f70084
add documentation, remove lint ignore
bshaffer Apr 3, 2023
2d85717
Update ClientOptions.php
bshaffer Apr 3, 2023
97fd34f
Merge branch 'main' into add-options-classes
bshaffer Jun 20, 2023
31c8148
fix cs
bshaffer Jun 20, 2023
93f5bc0
add offsetGet returntypewillchange annotation
bshaffer Jul 5, 2023
e63c21a
only use client options for V2 surface
bshaffer Jul 5, 2023
a7c5104
only use ClientOptions for V2 surfaces
bshaffer Jul 5, 2023
c251784
add back file validation
bshaffer Jul 5, 2023
f3b5814
fix variable exception message
bshaffer Jul 6, 2023
157a355
add CallOptions and tests
bshaffer Jul 6, 2023
1583557
add copyright
bshaffer Jul 6, 2023
951c1f8
add OptionsTraitTest
bshaffer Jul 6, 2023
519c8e7
better comments
bshaffer Jul 6, 2023
bcbbbcd
Update src/GapicClientTrait.php
bshaffer Jul 6, 2023
fb56ba2
add caching for new client
bshaffer Jul 6, 2023
bc182fc
Merge branch 'main' into add-options-classes
bshaffer Aug 15, 2023
e3dfd71
remove unnecessary change to tests
bshaffer Aug 15, 2023
2604fdf
Merge branch 'main' into add-options-classes
bshaffer Aug 23, 2023
68d6191
typehint nested options setters
bshaffer Aug 23, 2023
d3a7bba
Merge branch 'add-options-classes' of github.com:googleapis/gax-php i…
bshaffer Aug 23, 2023
bde66d6
better call options info
bshaffer Aug 24, 2023
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: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
php: [ "7.0", 7.1, 7.2, 7.3, 7.4, "8.0", 8.1, 8.2 ]
php: [ 7.4, "8.0", 8.1, 8.2 ]
extensions: [""]
tools: [""]
include:
- php: "7.0"
- php: "7.4"
extensions: "protobuf,grpc-1.49.0"
tools: "pecl"
- php: 8.1
Expand Down
54 changes: 25 additions & 29 deletions src/GapicClientTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
use Google\ApiCore\Transport\GrpcTransport;
use Google\ApiCore\Transport\RestTransport;
use Google\ApiCore\Transport\TransportInterface;
use Google\ApiCore\Options\ClientOptions;
use Google\ApiCore\Options\TransportOptions;
use Google\Auth\CredentialsLoader;
use Google\Auth\FetchAuthTokenInterface;
use Google\LongRunning\Operation;
Expand Down Expand Up @@ -362,37 +364,29 @@ private function setClientOptions(array $options)
'libName',
'libVersion',
]);

$clientConfig = $options['clientConfig'];
if (is_string($clientConfig)) {
$clientConfig = json_decode(file_get_contents($clientConfig), true);
}
$options = new ClientOptions($options);
$this->serviceName = $options['serviceName'];
$this->retrySettings = RetrySettings::load(
$this->serviceName,
$clientConfig,
$options['clientConfig'],
$options['disableRetries']
);

$headerInfo = [
'libName' => $options['libName'],
'libVersion' => $options['libVersion'],
'gapicVersion' => $options['gapicVersion'],
];
// Edge case: If the client has the gRPC extension installed, but is
// a REST-only library, then the grpcVersion header should not be set.
if ($this->transport instanceof GrpcTransport) {
$options['grpcVersion'] = phpversion('grpc');
unset($options['restVersion']);
$headerInfo['grpcVersion'] = phpversion('grpc');
} elseif ($this->transport instanceof RestTransport
|| $this->transport instanceof GrpcFallbackTransport) {
unset($options['grpcVersion']);
$options['restVersion'] = Version::getApiCoreVersion();
$headerInfo['restVersion'] = Version::getApiCoreVersion();
}
$this->agentHeader = AgentHeader::buildAgentHeader($headerInfo);

$this->agentHeader = AgentHeader::buildAgentHeader(
$this->pluckArray([
'libName',
'libVersion',
'gapicVersion'
], $options)
);
self::validateFileExists($options['descriptorsConfigPath']);
$descriptors = require($options['descriptorsConfigPath']);
$this->descriptors = $descriptors['interfaces'][$this->serviceName];

Expand Down Expand Up @@ -442,15 +436,15 @@ private function createCredentialsWrapper($credentials, array $credentialsConfig
/**
* @param string $apiEndpoint
* @param string $transport
* @param array $transportConfig
* @param TransportOptions $transportConfig
* @param callable $clientCertSource
* @return TransportInterface
* @throws ValidationException
*/
private function createTransport(
string $apiEndpoint,
$transport,
array $transportConfig,
TransportOptions $transportConfig,
callable $clientCertSource = null
) {
if (!is_string($transport)) {
Expand All @@ -467,23 +461,25 @@ private function createTransport(
implode(', ', $supportedTransports)
));
}
$configForSpecifiedTransport = isset($transportConfig[$transport])
? $transportConfig[$transport]
: [];
$configForSpecifiedTransport['clientCertSource'] = $clientCertSource;
switch ($transport) {
case 'grpc':
return GrpcTransport::build($apiEndpoint, $configForSpecifiedTransport);
$grpcTransportConfig = $transportConfig['grpc'];
$grpcTransportConfig->setClientCertSource($clientCertSource);
return GrpcTransport::build($apiEndpoint, $grpcTransportConfig->toArray());
case 'grpc-fallback':
return GrpcFallbackTransport::build($apiEndpoint, $configForSpecifiedTransport);
$grpcFallbackTransportConfig = $transportConfig['grpcFallback'];
$grpcFallbackTransportConfig->setClientCertSource($clientCertSource);
return GrpcFallbackTransport::build($apiEndpoint, $grpcFallbackTransportConfig->toArray());
case 'rest':
if (!isset($configForSpecifiedTransport['restClientConfigPath'])) {
$restTransportConfig = $transportConfig['rest'];
$restTransportConfig->setClientCertSource($clientCertSource);
if (!isset($restTransportConfig['restClientConfigPath'])) {
throw new ValidationException(
"The 'restClientConfigPath' config is required for 'rest' transport."
);
}
$restConfigPath = $configForSpecifiedTransport['restClientConfigPath'];
return RestTransport::build($apiEndpoint, $restConfigPath, $configForSpecifiedTransport);
$restConfigPath = $restTransportConfig['restClientConfigPath'];
return RestTransport::build($apiEndpoint, $restConfigPath, $restTransportConfig->toArray());
default:
throw new ValidationException(
"Unexpected 'transport' option: $transport. " .
Expand Down
274 changes: 274 additions & 0 deletions src/Options/ClientOptions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
<?php

namespace Google\ApiCore\Options;

use ArrayAccess;
use Closure;
use InvalidArgumentException;
use Google\ApiCore\CredentialsWrapper;
use Google\ApiCore\Transport\TransportInterface;
use Google\Auth\FetchAuthTokenInterface;

/**
* The ClientOptions class adds typing to the associative array of options
* passed into each API client constructor. To use this class directly, pass
* the result of {@see ClientOptions::toArray} to the client constructor:
*
* ```
* use Google\ApiCore\ClientOptions;
* use Google\Cloud\SecretManager\Client\SecretManagerClient;
*
* $options = new ClientOptions([
* 'credentials' => '/path/to/my/credentials.json'
* ]);
* $secretManager = new SecretManagerClient($options->toArray());
* ```
*
* Note: It's possible to pass an associative array to the API clients as well,
* as ClientOptions will still be used internally for validation.
*/
class ClientOptions implements ArrayAccess
{
use OptionsTrait;

private ?string $apiEndpoint;

private bool $disableRetries;

private array $clientConfig;

/** @var string|array|FetchAuthTokenInterface|CredentialsWrapper|null */
private $credentials;

private array $credentialsConfig;

/** @var string|TransportInterface|null $transport */
private $transport;

private TransportOptions $transportConfig;

private ?string $versionFile;

private ?string $descriptorsConfigPath;

private ?string $serviceName;

private ?string $libName;

private ?string $libVersion;

private ?string $gapicVersion;

private ?Closure $clientCertSource;

/**
* @param array $options {
* @type string $apiEndpoint
* The address of the API remote host, for example "example.googleapis.com. May also
* include the port, for example "example.googleapis.com:443"
* @type bool $disableRetries
* Determines whether or not retries defined by the client configuration should be
* disabled. Defaults to `false`.
* @type string|array $clientConfig
* Client method configuration, including retry settings. This option can be either a
* path to a JSON file, or a PHP array containing the decoded JSON data.
* By default this settings points to the default client config file, which is provided
* in the resources folder.
* @type string|array|FetchAuthTokenInterface|CredentialsWrapper $credentials
* The credentials to be used by the client to authorize API calls. This option
* accepts either a path to a credentials file, or a decoded credentials file as a
* PHP array.
* *Advanced usage*: In addition, this option can also accept a pre-constructed
* \Google\Auth\FetchAuthTokenInterface object or \Google\ApiCore\CredentialsWrapper
* object. Note that when one of these objects are provided, any settings in
* $authConfig will be ignored.
* @type array $credentialsConfig
* Options used to configure credentials, including auth token caching, for the client.
* For a full list of supporting configuration options, see
* \Google\ApiCore\CredentialsWrapper::build.
* @type string|TransportInterface|null $transport
* The transport used for executing network requests. May be either the string `rest`,
* `grpc`, or 'grpc-fallback'. Defaults to `grpc` if gRPC support is detected on the system.
* *Advanced usage*: Additionally, it is possible to pass in an already instantiated
* TransportInterface object. Note that when this objects is provided, any settings in
* $transportConfig, and any `$apiEndpoint` setting, will be ignored.
* @type array|TransportOptions $transportConfig
* Configuration options that will be used to construct the transport. Options for
* each supported transport type should be passed in a key for that transport. For
* example:
* $transportConfig = [
* 'grpc' => [...],
* 'rest' => [...],
* 'grpc-fallback' => [...],
* ];
* See the GrpcTransport::build and RestTransport::build
* methods for the supported options.
* @type string $versionFile
* The path to a file which contains the current version of the client.
* @type string $descriptorsConfigPath
* The path to a descriptor configuration file.
* @type string $serviceName
* The name of the service.
* @type string $libName
* The name of the client application.
* @type string $libVersion
* The version of the client application.
* @type string $gapicVersion
* The code generator version of the GAPIC library.
* @type callable $clientCertSource
* A callable which returns the client cert as a string.
* }
*/
public function __construct(array $options)
{
$this->fromArray($options);
}

/**
* Sets the array of options as class properites.
*
* @param array $arr See the constructor for the list of supported options.
*/
private function fromArray(array $arr): void
{
$this->setApiEndpoint($arr['apiEndpoint'] ?? null);
$this->setDisableRetries($arr['disableRetries'] ?? false);
$this->setClientConfig($arr['clientConfig'] ?? []);
$this->setCredentials($arr['credentials']);
$this->setCredentialsConfig($arr['credentialsConfig'] ?? []);
$this->setTransport($arr['transport'] ?? null);
$this->setTransportConfig($arr['transportConfig'] ?? []);
$this->setVersionFile($arr['versionFile'] ?? null);
$this->setDescriptorsConfigPath($arr['descriptorsConfigPath']);
$this->setServiceName($arr['serviceName'] ?? null);
$this->setLibName($arr['libName'] ?? null);
$this->setLibVersion($arr['libVersion'] ?? null);
$this->setGapicVersion($arr['gapicVersion'] ?? null);
$this->setClientCertSource($arr['clientCertSource'] ?? null);
}

/**
* @param ?string $apiEndpoint
*/
public function setApiEndpoint(?string $apiEndpoint): void
{
$this->apiEndpoint = $apiEndpoint;
}

/**
* @param bool $disableRetries
*/
public function setDisableRetries(bool $disableRetries): void
{
$this->disableRetries = $disableRetries;
}

/**
* @param string|array $clientConfig
* @throws InvalidArgumentException
*/
public function setClientConfig($clientConfig): void
{
if (is_string($clientConfig)) {
$this->clientConfig = json_decode(file_get_contents($clientConfig), true);
} elseif (is_array($clientConfig)) {
$this->clientConfig = $clientConfig;
} else {
throw new InvalidArgumentException('Invalid client config');
}
}

/**
* @param string|array|FetchAuthTokenInterface|CredentialsWrapper|null $credentials
*/
public function setCredentials($credentials): void
{
$this->credentials = $credentials;
}

/**
* @param array $credentialsConfig
*/
public function setCredentialsConfig(array $credentialsConfig): void
{
$this->credentialsConfig = $credentialsConfig;
}

/**
* @param string|TransportInterface|null $transport
*/
public function setTransport($transport): void
{
$this->transport = $transport;
}

/**
* @param array $transportConfig
*/
public function setTransportConfig(array $transportConfig): void
{
$this->transportConfig = new TransportOptions($transportConfig);
}

/**
* @param ?string $versionFile
*/
public function setVersionFile(?string $versionFile): void
{
$this->versionFile = $versionFile;
}

/**
* @param ?string $descriptorsConfigPath
*/
private function setDescriptorsConfigPath(?string $descriptorsConfigPath)
{
if (!is_null($descriptorsConfigPath)) {
self::validateFileExists($descriptorsConfigPath);
}
$this->descriptorsConfigPath = $descriptorsConfigPath;
}

/**
* @param ?string $serviceName
*/
public function setServiceName(?string $serviceName): void
{
$this->serviceName = $serviceName;
}

/**
* @param ?string $libName
*/
public function setLibName(?string $libName): void
{
$this->libName = $libName;
}

/**
* @param ?string $libVersion
*/
public function setLibVersion(?string $libVersion): void
{
$this->libVersion = $libVersion;
}

/**
* @param ?string $gapicVersion
*/
public function setGapicVersion(?string $gapicVersion): void
{
$this->gapicVersion = $gapicVersion;
}

/**
* @param ?callable $clientCertSource
*/
public function setClientCertSource(?callable $clientCertSource)
{
if (!is_null($clientCertSource)) {
$this->clientCertSource = Closure::fromCallable($clientCertSource);
}
$this->clientCertSource = $clientCertSource;
}
}
Loading