diff --git a/src/Bootloader/TemporalBridgeBootloader.php b/src/Bootloader/TemporalBridgeBootloader.php index acd5946..fffbbb1 100644 --- a/src/Bootloader/TemporalBridgeBootloader.php +++ b/src/Bootloader/TemporalBridgeBootloader.php @@ -157,7 +157,7 @@ protected function initConfig(EnvironmentInterface $env): void [ 'connection' => $env->get('TEMPORAL_CONNECTION', 'default'), 'connections' => [ - 'default' => ConnectionConfig::create( + 'default' => ConnectionConfig::new( address: $env->get('TEMPORAL_ADDRESS', '127.0.0.1:7233'), ), ], @@ -173,7 +173,8 @@ protected function initConfig(EnvironmentInterface $env): void protected function initServiceClient(TemporalConfig $config): ServiceClientInterface { - $connection = $config->getConnection($config->getDefaultConnection()); + $client = $config->getClientConfig($config->getDefaultClient()); + $connection = $client->connection; return $connection->isSecure() ? ServiceClient::createSSL( diff --git a/src/Config/ClientConfig.php b/src/Config/ClientConfig.php new file mode 100644 index 0000000..11e5dd5 --- /dev/null +++ b/src/Config/ClientConfig.php @@ -0,0 +1,60 @@ +withTls( + * privateKey: '/my-project.key', + * certChain: '/my-project.pem', + * ), + * (new ClientOptions()) + * ->withNamespace('default'), + * Context::default() + * ->withTimeout(4.5) + * ->withRetryOptions( + * RpcRetryOptions::new() + * ->withMaximumAttempts(5) + * ->withInitialInterval(3) + * ->withMaximumInterval(10) + * ->withBackoffCoefficient(1.6) + * ), + * ), + * ), + */ +final class ClientConfig +{ + private function __construct( + public readonly ConnectionConfig $connection, + public readonly ClientOptions $options, + public readonly ContextInterface $context, + ) {} + + /** + * Create a new client configuration. + * + * @param ConnectionConfig $connection + * @param ClientOptions|null $options + * @param ContextInterface|null $context Default Service Client context. + */ + public static function new( + ConnectionConfig $connection, + ?ClientOptions $options = null, + ?ContextInterface $context = null, + ): self { + return new self( + $connection, + $options ?? new ClientOptions(), + $context ?? Context::default(), + ); + } +} diff --git a/src/Config/ConnectionConfig.php b/src/Config/ConnectionConfig.php index f3959a0..002c7a3 100644 --- a/src/Config/ConnectionConfig.php +++ b/src/Config/ConnectionConfig.php @@ -5,19 +5,19 @@ namespace Spiral\TemporalBridge\Config; /** - * Temporal connection configuration. + * Temporal connection and credentials configuration. * * How to connect to local Temporal server: * - * ConnectionConfig::create('localhost:7233'), + * ConnectionConfig::new('localhost:7233'), * * How to connect to Temporal Cloud: * - * ConnectionConfig::createCloud( - * address: 'foo-bar-default.baz.tmprl.cloud:7233', - * privateKey: '/my-project.key', - * certChain: '/my-project.pem', - * ), + * ConnectionConfig::new('foo-bar-default.baz.tmprl.cloud:7233') + * ->withTls( + * privateKey: '/my-project.key', + * certChain: '/my-project.pem', + * ), */ final class ConnectionConfig { @@ -45,29 +45,12 @@ public function isSecure(): bool /** * @param non-empty-string $address */ - public static function create( + public static function new( string $address, ): self { return new self($address); } - /** - * Used to connect to Temporal Cloud. - * - * @link https://docs.temporal.io/cloud/get-started - * - * @param non-empty-string $address - * @param non-empty-string $privateKey Client private key string or file in PEM format. - * @param non-empty-string $certChain Client certificate chain string or file in PEM format. - */ - public static function createCloud( - string $address, - string $privateKey, - string $certChain, - ): self { - return new self($address, new TlsConfig(privateKey: $privateKey, certChain: $certChain)); - } - /** * Set the TLS configuration for the connection. * diff --git a/src/Config/TemporalConfig.php b/src/Config/TemporalConfig.php index 6a19365..7c06060 100644 --- a/src/Config/TemporalConfig.php +++ b/src/Config/TemporalConfig.php @@ -21,14 +21,11 @@ * } * * @property array{ - * address?: non-empty-string|null, - * connection: non-empty-string, - * connections: array, - * temporalNamespace: non-empty-string, + * client: non-empty-string, + * clients: array, * defaultWorker: non-empty-string, * workers: array, * interceptors?: TInterceptor[], - * clientOptions?: ClientOptions * } $config */ final class TemporalConfig extends InjectableConfig @@ -37,54 +34,76 @@ final class TemporalConfig extends InjectableConfig public const CONFIG = 'temporal'; protected array $config = [ - 'connection' => 'default', - 'connections' => [], - 'temporalNamespace' => 'default', + 'client' => 'default', + 'clients' => [], 'defaultWorker' => WorkerFactoryInterface::DEFAULT_TASK_QUEUE, 'workers' => [], 'interceptors' => [], - 'clientOptions' => null, ]; + public function __construct(array $config = []) + { + // Legacy support. Will be removed in further versions. + // If you read this, please remove `address` option from your configuration and use `clients` instead. + $address = $config['address'] ?? null; + if ($address !== null) { + \trigger_error( + 'Temporal options `address`, `clientOptions`, `temporalNamespace` are deprecated.', + \E_USER_DEPRECATED, + ); + + // Create a default client configuration from the legacy options. + $namespace = $config['temporalNamespace'] ?? 'default'; + $clientOptions = ($config['clientOptions'] ?? new ClientOptions()) + ->withNamespace($namespace); + + $config['client'] = 'default'; + $config['clients']['default'] = ClientConfig::new( + ConnectionConfig::new(address: $address), + $clientOptions, + ); + } + + parent::__construct($config); + } + /** + * Get default namespace for Temporal client. + * * @return non-empty-string + * + * @deprecated */ public function getTemporalNamespace(): string { - return $this->config['temporalNamespace']; + $client = $this->getDefaultClient(); + return match(true) { + isset($this->config['clients'][$client]) => $this->config['clients'][$client]->options->namespace, + isset($this->config['temporalNamespace']) => $this->config['temporalNamespace'], + default => 'default', + }; } - public function getDefaultConnection(): string + public function getDefaultClient(): string { - return $this->config['connection'] ?? 'default'; + return $this->config['client'] ?? 'default'; } - public function getConnection(string $name): ConnectionConfig + public function getClientConfig(string $name): ClientConfig { - // Legacy support. Will be removed in further versions. - // If you read this, please remove address from your configuration and use connections instead. - $address = $this->config['address'] ?? null; - if ($address !== null) { - \trigger_error( - 'Using `address` is deprecated, use `connections` instead.', - \E_USER_DEPRECATED, - ); - return ConnectionConfig::create(address: $address); - } - - if (isset($this->config['connections'][$name])) { - return $this->config['connections'][$name]; - } - - throw new \InvalidArgumentException(\sprintf('Connection `%s` is not defined.', $name)); + return $this->config['clients'][$name] ?? throw new \InvalidArgumentException( + "Temporal client config `{$name}` is not defined.", + ); } /** + * Get default connection address. + * * @deprecated */ public function getAddress(): string { - return $this->getConnection($this->getDefaultConnection())->address; + return $this->getClientConfig($this->getDefaultClient())->connection->address; } /** @@ -111,8 +130,18 @@ public function getInterceptors(): array return $this->config['interceptors'] ?? []; } + /** + * Get default client options. + * + * @deprecated + */ public function getClientOptions(): ClientOptions { - return $this->config['clientOptions'] ?? (new ClientOptions())->withNamespace($this->getTemporalNamespace()); + $client = $this->getDefaultClient(); + return match(true) { + isset($this->config['clients'][$client]) => $this->config['clients'][$client]->options, + isset($this->config['clientOptions']) => $this->config['clientOptions'], + default => (new ClientOptions())->withNamespace($this->getTemporalNamespace()), + }; } } diff --git a/tests/app/config/temporal.php b/tests/app/config/temporal.php index a921227..20735d1 100644 --- a/tests/app/config/temporal.php +++ b/tests/app/config/temporal.php @@ -2,24 +2,20 @@ declare(strict_types=1); +use Spiral\TemporalBridge\Config\ClientConfig; use Spiral\TemporalBridge\Config\ConnectionConfig; return [ - 'connection' => env('TEMPORAL_CONNECTION', 'default'), - 'connections' => [ - 'default' => ConnectionConfig::create( - address: 'localhost:7233', - ), - 'ssl' => ConnectionConfig::create(address: 'ssl:7233') - ->withTls( - rootCerts: '/path/to/crt', - privateKey: '/path/to/clientKey', - certChain: '/path/to/clientPem', - ), - 'temporal_cloud' => ConnectionConfig::createCloud( - address: 'ssl:7233', - privateKey: '/path/to/clientKey', - certChain: '/path/to/clientPem', + 'client' => env('TEMPORAL_CONNECTION', 'default'), + 'clients' => [ + 'default' => ClientConfig::new(ConnectionConfig::new('localhost:7233')), + 'ssl' => ClientConfig::new( + ConnectionConfig::new(address: 'ssl:7233') + ->withTls( + rootCerts: '/path/to/crt', + privateKey: '/path/to/clientKey', + certChain: '/path/to/clientPem', + ) ), ], ]; diff --git a/tests/src/Bootloader/TemporalBridgeBootloaderTest.php b/tests/src/Bootloader/TemporalBridgeBootloaderTest.php index da33b58..443bf14 100644 --- a/tests/src/Bootloader/TemporalBridgeBootloaderTest.php +++ b/tests/src/Bootloader/TemporalBridgeBootloaderTest.php @@ -141,7 +141,7 @@ public function testSecureConnection(): void public function testNonExistsConnection(): void { $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('Connection `test` is not defined.'); + $this->expectExceptionMessage('Temporal client config `test` is not defined.'); $this->getContainer()->get(ServiceClientInterface::class); } diff --git a/tests/src/Commands/Scaffolder/ActivityCommandTest.php b/tests/src/Commands/Scaffolder/ActivityCommandTest.php index 143406d..1103805 100644 --- a/tests/src/Commands/Scaffolder/ActivityCommandTest.php +++ b/tests/src/Commands/Scaffolder/ActivityCommandTest.php @@ -25,22 +25,22 @@ public function testGenerate(): void $this->assertTrue(\str_ends_with($path, '/app/src/Endpoint/Temporal/Activity/PaymentActivity.php')); $this->assertSame( <<<'PHP' -assertTrue(\str_ends_with($path, '/app/src/Endpoint/Temporal/Activity/PaymentActivity.php')); $this->assertSame( <<<'PHP' -assertTrue(\str_ends_with($path, '/app/src/Endpoint/Temporal/Activity/PaymentActivity.php')); $this->assertSame( <<<'PHP' -assertTrue(\str_ends_with($path, '/app/src/Endpoint/Temporal/Activity/PaymentActivity.php')); $this->assertSame( <<<'PHP' - - */ - #[ActivityMethod(name: 'pay')] - public function pay(): mixed - { - // TODO: Implement activity method - } - - /** - * @return PromiseInterface - */ - #[ActivityMethod(name: 'refund')] - public function refund(): void - { - // TODO: Implement activity method - } - - /** - * @return PromiseInterface - */ - #[ActivityMethod(name: 'getPaymentStatus')] - public function getPaymentStatus(): bool - { - // TODO: Implement activity method - } -} - -PHP, + + */ + #[ActivityMethod(name: 'pay')] + public function pay(): mixed + { + // TODO: Implement activity method + } + + /** + * @return PromiseInterface + */ + #[ActivityMethod(name: 'refund')] + public function refund(): void + { + // TODO: Implement activity method + } + + /** + * @return PromiseInterface + */ + #[ActivityMethod(name: 'getPaymentStatus')] + public function getPaymentStatus(): bool + { + // TODO: Implement activity method + } + } + + PHP, $body, ); return true; diff --git a/tests/src/Commands/Scaffolder/WorkflowCommandTest.php b/tests/src/Commands/Scaffolder/WorkflowCommandTest.php index f1aa4e4..ce6c874 100644 --- a/tests/src/Commands/Scaffolder/WorkflowCommandTest.php +++ b/tests/src/Commands/Scaffolder/WorkflowCommandTest.php @@ -25,29 +25,29 @@ public function testGenerate(): void $this->assertTrue(\str_ends_with($path, '/app/src/Endpoint/Temporal/Workflow/PaymentWorkflow.php')); $this->assertSame( <<<'PHP' -assertTrue(\str_ends_with($path, '/app/src/Endpoint/Temporal/Workflow/PaymentWorkflow.php')); $this->assertSame( <<<'PHP' -assertTrue(\str_ends_with($path, '/app/src/Endpoint/Temporal/Workflow/PaymentWorkflow.php')); $this->assertSame( <<<'PHP' -assertTrue(\str_ends_with($path, '/app/src/Endpoint/Temporal/Workflow/PaymentWorkflow.php')); $this->assertSame( <<<'PHP' -assertTrue(\str_ends_with($path, '/app/src/Endpoint/Temporal/Workflow/PaymentWorkflow.php')); $this->assertSame( <<<'PHP' -withTls( rootCerts: 'crt', @@ -28,7 +28,7 @@ public function testCreateSecure(): void public function testCreateInsecure(): void { - $config = ConnectionConfig::create( + $config = ConnectionConfig::new( address: 'localhost:1111', ); @@ -36,23 +36,9 @@ public function testCreateInsecure(): void $this->assertSame('localhost:1111', $config->address); } - public function testCreateCloud(): void - { - $config = ConnectionConfig::createCloud( - address: 'localhost:1111', - privateKey: 'clientKey', - certChain: 'clientPem', - ); - - $this->assertTrue($config->isSecure()); - $this->assertSame('localhost:1111', $config->address); - $this->assertSame('clientKey', $config->tlsConfig->privateKey); - $this->assertSame('clientPem', $config->tlsConfig->certChain); - } - public function testWithAuthKey(): void { - $config = ConnectionConfig::create( + $config = ConnectionConfig::new( address: 'localhost:1111', )->withTls( certChain: 'clientPem', @@ -67,7 +53,7 @@ public function testWithAuthKey(): void public function testWithAuthKeyNull(): void { - $config = ConnectionConfig::create(address: 'localhost:1111') + $config = ConnectionConfig::new(address: 'localhost:1111') ->withTls() ->withAuthKey('authKey'); @@ -82,7 +68,7 @@ public function testWithAuthKeyNull(): void public function testWithAuthKeyStringable(): void { - $config = ConnectionConfig::create(address: 'localhost:1111') + $config = ConnectionConfig::new(address: 'localhost:1111') ->withTls() ->withAuthKey( $key = new class() implements \Stringable { diff --git a/tests/src/Config/TemporalConfigTest.php b/tests/src/Config/TemporalConfigTest.php index a4ebc33..3602c5b 100644 --- a/tests/src/Config/TemporalConfigTest.php +++ b/tests/src/Config/TemporalConfigTest.php @@ -4,6 +4,7 @@ namespace Spiral\TemporalBridge\Tests\Config; +use Spiral\TemporalBridge\Config\ClientConfig; use Spiral\TemporalBridge\Config\ConnectionConfig; use Spiral\TemporalBridge\Config\TemporalConfig; use Spiral\TemporalBridge\Tests\TestCase; @@ -35,28 +36,30 @@ public function testGetConnectionFromAddress(): void 'address' => 'localhost:1111', ]); - $connection = $config->getConnection('default'); - $this->assertSame(ConnectionConfig::class, $connection::class); + $client = $config->getClientConfig('default'); + $this->assertSame(ClientConfig::class, $client::class); - $this->assertSame('localhost:1111', $connection->address); + $this->assertSame('localhost:1111', $client->connection->address); } public function testGetTlsConnection(): void { $config = new TemporalConfig([ - 'connections' => [ - 'default' => ConnectionConfig::create( - address: 'localhost:2222', - )->withTls( - rootCerts: 'crt', - privateKey: 'clientKey', - certChain: 'clientPem', - serverName: 'localhost', + 'clients' => [ + 'default' => ClientConfig::new( + ConnectionConfig::new(address: 'localhost:2222') + ->withTls( + rootCerts: 'crt', + privateKey: 'clientKey', + certChain: 'clientPem', + serverName: 'localhost', + ), ), ], ]); - $connection = $config->getConnection('default'); + $client = $config->getClientConfig('default'); + $connection = $client->connection; $this->assertTrue($connection->isSecure()); $this->assertSame('localhost:2222', $connection->address);