Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c3c460b
test(envconfig): Add TOML stubs
roxblnfk Oct 24, 2025
7f828be
chore(envconfig): Add package `internal/toml`
roxblnfk Oct 24, 2025
25f32f2
feat(envconfig): Add `ConfigProfile` class
roxblnfk Oct 24, 2025
2cc1297
feat(envconfig): Add `ConfigToml`
roxblnfk Oct 24, 2025
116654f
feat(envconfig): Add `ConfigTls`
roxblnfk Oct 24, 2025
e19fbd2
test(envconfig): Test and fix `ConfigToml`
roxblnfk Oct 24, 2025
3cedc1f
style(php-cs-fixer): fix coding standards
Oct 24, 2025
78aacf4
chore(envconfig): Update TOML configuration examples and clarify opti…
roxblnfk Oct 27, 2025
1988b93
refactor: rename `CloneWith::with` to `CloneWith::cloneWith`
roxblnfk Oct 27, 2025
5375dae
feat(envconfig): Add merging functionality to `ConfigProfile` and `Co…
roxblnfk Oct 27, 2025
71c80c5
feat(envconfig): Add `ConfigEnv`
roxblnfk Oct 27, 2025
c63fb6f
style(php-cs-fixer): fix coding standards
Oct 27, 2025
c413421
feat(envconfig): Add `ConfigClient` and `EnvProvider`
roxblnfk Oct 28, 2025
824dd78
feat(envconfig): Enhance gRPC metadata handling with key normalizatio…
roxblnfk Oct 28, 2025
a35518d
feat(envconfig): Add default config file detection
roxblnfk Oct 28, 2025
89f5dfc
feat(envconfig): Add codec configuration support with validation and …
roxblnfk Oct 28, 2025
2e20d1e
chore: Fix psalm issues
roxblnfk Oct 28, 2025
4276c4b
feat: Add TomlParserNotFound exception
roxblnfk Oct 28, 2025
93d2bf4
feat: Update psalm baseline
roxblnfk Oct 28, 2025
ded957f
Merge branch 'master' into env-config
roxblnfk Nov 3, 2025
b27a04e
refactor: ConfigToml: remove strict non-mode;
roxblnfk Nov 17, 2025
d7ca3ef
refactor(ConfigToml): Add `fromString()` factory
roxblnfk Nov 17, 2025
4db97ab
feat: Add method `ConfigClient::toToml()`
roxblnfk Nov 17, 2025
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 @@ -61,6 +61,7 @@
"dereuromark/composer-prefer-lowest": "^0.1.10",
"doctrine/annotations": "^1.14.4 || ^2.0.2",
"internal/dload": "^1.2.0",
"internal/toml": "^1.0.3",
"jetbrains/phpstorm-attributes": "dev-master",
"laminas/laminas-code": "^4.16",
"phpunit/phpunit": "10.5.45",
Expand All @@ -84,7 +85,8 @@
"ext-grpc": "For Client calls",
"ext-protobuf": "For better performance",
"buggregator/trap": "For better debugging",
"roadrunner/psr-logger": "RoadRunner PSR-3 logger integration"
"roadrunner/psr-logger": "RoadRunner PSR-3 logger integration",
"internal/toml": "To load TOML config files"
},
"scripts": {
"get:binaries": [
Expand Down
48 changes: 48 additions & 0 deletions src/Common/EnvConfig/Client/ConfigCodec.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace Temporal\Common\EnvConfig\Client;

/**
* Remote codec configuration.
*
* Specifies endpoint and authentication for remote data encoding/decoding.
* Remote codecs allow offloading payload encoding/decoding to an external service.
*
* @internal
*/
final class ConfigCodec
{
/** @var non-empty-string|null $endpoint Endpoint URL for the remote codec service */
public readonly ?string $endpoint;

/** @var non-empty-string|null $auth Authorization header value for codec authentication */
public readonly ?string $auth;

/**
* @param string|null $endpoint Endpoint URL for the remote codec service
* @param string|null $auth Authorization header value for codec authentication
*/
public function __construct(
?string $endpoint = null,
?string $auth = null,
) {
$this->auth = $auth === '' ? null : $auth;
$this->endpoint = $endpoint === '' ? null : $endpoint;
}

/**
* Merge this codec config with another, with the other config's values taking precedence.
*
* @param self $from Codec config to merge (values from this take precedence)
* @return self New merged codec config
*/
public function mergeWith(self $from): self
{
return new self(
endpoint: $from->endpoint ?? $this->endpoint,
auth: $from->auth ?? $this->auth,
);
}
}
183 changes: 183 additions & 0 deletions src/Common/EnvConfig/Client/ConfigEnv.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
<?php

declare(strict_types=1);

namespace Temporal\Common\EnvConfig\Client;

use Temporal\Common\EnvConfig\EnvProvider;

/**
* Environment variable configuration parser for Temporal client.
*
* Reads Temporal client configuration from environment variables following
* the naming convention: TEMPORAL_* (e.g., TEMPORAL_ADDRESS, TEMPORAL_NAMESPACE).
*
* Supported environment variables:
* - TEMPORAL_ADDRESS - Temporal server address (host:port)
* - TEMPORAL_NAMESPACE - Temporal namespace
* - TEMPORAL_API_KEY - API key for authentication
* - TEMPORAL_PROFILE - Active profile name
* - TEMPORAL_CONFIG_FILE - Path to TOML configuration file
* - TEMPORAL_TLS - Enable/disable TLS (boolean: true/false, 1/0, yes/no, on/off)
* - TEMPORAL_TLS_CLIENT_CERT_PATH - Path to client certificate file
* - TEMPORAL_TLS_CLIENT_CERT_DATA - Client certificate data (PEM format)
* - TEMPORAL_TLS_CLIENT_KEY_PATH - Path to client private key file
* - TEMPORAL_TLS_CLIENT_KEY_DATA - Client private key data (PEM format)
* - TEMPORAL_TLS_SERVER_CA_CERT_PATH - Path to server CA certificate file
* - TEMPORAL_TLS_SERVER_CA_CERT_DATA - Server CA certificate data (PEM format)
* - TEMPORAL_TLS_SERVER_NAME - Server name for TLS verification (SNI override)
* - TEMPORAL_CODEC_ENDPOINT - Remote codec endpoint URL (NOT SUPPORTED - throws exception)
* - TEMPORAL_CODEC_AUTH - Authorization header for remote codec (NOT SUPPORTED - throws exception)
* - TEMPORAL_GRPC_META_* - gRPC metadata headers (e.g., TEMPORAL_GRPC_META_X_CUSTOM_HEADER)
*
* TLS Configuration Rules:
* - Cannot specify both *_PATH and *_DATA variants for the same certificate (throws exception)
* - *_PATH takes precedence over *_DATA if both are set (with strict validation)
*
* Codec Configuration:
* - Remote codec configuration is NOT SUPPORTED in PHP SDK
* - If TEMPORAL_CODEC_ENDPOINT or TEMPORAL_CODEC_AUTH is set, an exception will be thrown
*
* @link https://github.com/temporalio/proposals/blob/master/all-sdk/external-client-configuration.md#environment-variables
* @internal
*/
final class ConfigEnv
{
/**
* Current active profile name from TEMPORAL_PROFILE
* @var non-empty-lowercase-string|null
*/
public readonly ?string $currentProfile;

/**
* Path to TOML configuration file from TEMPORAL_CONFIG_FILE
* @var non-empty-string|null
*/
public readonly ?string $configFile;

/**
* @param ConfigProfile $profile Profile constructed from environment variables
* @param string|null $currentProfile Current active profile name
* @param string|null $configFile Path to TOML configuration file
*/
private function __construct(
/**
* Profile constructed from environment variables
*/
public readonly ConfigProfile $profile,
?string $currentProfile = null,
?string $configFile = null,
) {
$this->currentProfile = $currentProfile === '' || $currentProfile === null
? null
: \strtolower($currentProfile);
$this->configFile = $configFile === '' ? null : $configFile;
}

public static function fromEnvProvider(EnvProvider $env): self
{
return new self(
new ConfigProfile(
address: $env->get('TEMPORAL_ADDRESS'),
namespace: $env->get('TEMPORAL_NAMESPACE'),
apiKey: $env->get('TEMPORAL_API_KEY'),
tlsConfig: self::fetchTlsConfig($env),
grpcMeta: self::fetchGrpcMeta($env),
codecConfig: self::fetchCodecConfig($env),
),
$env->get('TEMPORAL_PROFILE'),
$env->get('TEMPORAL_CONFIG_FILE'),
);
}

private static function fetchTlsConfig(EnvProvider $env): ?ConfigTls
{
$tls = $env->get('TEMPORAL_TLS');
$tlsVars = $env->getByPrefix('TEMPORAL_TLS_', stripPrefix: true);

// If no TLS-related variables are set, return null
if ($tls === null && $tlsVars === []) {
return null;
}

// Parse TEMPORAL_TLS as boolean
$disabled = null;
if ($tls !== null) {
$tlsEnabled = \filter_var($tls, \FILTER_VALIDATE_BOOLEAN, \FILTER_NULL_ON_FAILURE);
$disabled = $tlsEnabled === null ? null : !$tlsEnabled;
}

// Check for conflicts: *_PATH and *_DATA cannot be used together
isset($tlsVars['SERVER_CA_CERT_PATH'], $tlsVars['SERVER_CA_CERT_DATA']) and throw new \InvalidArgumentException(
'Cannot specify both TEMPORAL_TLS_SERVER_CA_CERT_PATH and TEMPORAL_TLS_SERVER_CA_CERT_DATA.',
);
isset($tlsVars['CLIENT_KEY_PATH'], $tlsVars['CLIENT_KEY_DATA']) and throw new \InvalidArgumentException(
'Cannot specify both TEMPORAL_TLS_CLIENT_KEY_PATH and TEMPORAL_TLS_CLIENT_KEY_DATA.',
);
isset($tlsVars['CLIENT_CERT_PATH'], $tlsVars['CLIENT_CERT_DATA']) and throw new \InvalidArgumentException(
'Cannot specify both TEMPORAL_TLS_CLIENT_CERT_PATH and TEMPORAL_TLS_CLIENT_CERT_DATA.',
);

// Priority: *_PATH over *_DATA (same as ConfigToml)
return new ConfigTls(
disabled: $disabled,
rootCerts: $tlsVars['SERVER_CA_CERT_PATH'] ?? $tlsVars['SERVER_CA_CERT_DATA'] ?? null,
privateKey: $tlsVars['CLIENT_KEY_PATH'] ?? $tlsVars['CLIENT_KEY_DATA'] ?? null,
certChain: $tlsVars['CLIENT_CERT_PATH'] ?? $tlsVars['CLIENT_CERT_DATA'] ?? null,
serverName: $tlsVars['SERVER_NAME'] ?? null,
);
}

/**
* Fetch gRPC metadata from environment variables.
*
* Reads all environment variables with prefix TEMPORAL_GRPC_META_
* and converts them to gRPC metadata headers.
*
* Header names are transformed:
* - Converted to lowercase
* - Underscores (_) are replaced with hyphens (-)
*
* Example: TEMPORAL_GRPC_META_X_CUSTOM_HEADER=value
* Results in: ['x-custom-header' => 'value']
*
* @return array<non-empty-string, string>
*/
private static function fetchGrpcMeta(EnvProvider $env): array
{
$meta = $env->getByPrefix('TEMPORAL_GRPC_META_', stripPrefix: true);
$result = [];

foreach ($meta as $key => $value) {
// Transform header name: lowercase and replace _ with -
/** @var non-empty-string $headerName */
$headerName = \str_replace('_', '-', $key);
$result[$headerName] = $value;
}

return $result;
}

/**
* Fetch codec configuration from environment variables.
*
* Reads TEMPORAL_CODEC_ENDPOINT and TEMPORAL_CODEC_AUTH environment variables.
*
* @return ConfigCodec|null Codec configuration or null if no codec env vars are set
*/
private static function fetchCodecConfig(EnvProvider $env): ?ConfigCodec
{
$endpoint = $env->get('TEMPORAL_CODEC_ENDPOINT');
$auth = $env->get('TEMPORAL_CODEC_AUTH');

// Return null if both are not set
if ($endpoint === null && $auth === null) {
return null;
}

return new ConfigCodec(
endpoint: $endpoint,
auth: $auth,
);
}
}
Loading
Loading