diff --git a/.gitattributes b/.gitattributes index 48426be69..d9e5606d3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,18 +1,18 @@ * text=auto -.github/ export-ignore -config/ export-ignore -resources/scripts/ export-ignore -runtime/ export-ignore -tests/ export-ignore +.github/ export-ignore +config/ export-ignore +resources/scripts/ export-ignore +runtime/ export-ignore +tests/ export-ignore -.editorconfig export-ignore -.gitattributes export-ignore -.gitignore export-ignore -.php_cs.dist export-ignore -dload.xml export-ignore -Makefile export-ignore -phpunit.xml.dist export-ignore -psalm.xml export-ignore -psalm-baseline.xml export-ignore -phpdoc.dist.xml export-ignore +.editorconfig export-ignore +.gitattributes export-ignore +.gitignore export-ignore +.php-cs-fixer.dist.php export-ignore +dload.xml export-ignore +Makefile export-ignore +phpunit.xml.dist export-ignore +psalm.xml export-ignore +psalm-baseline.xml export-ignore +phpdoc.dist.xml export-ignore diff --git a/.github/workflows/cs-fix.yml b/.github/workflows/cs-fix.yml new file mode 100644 index 000000000..0395b27bd --- /dev/null +++ b/.github/workflows/cs-fix.yml @@ -0,0 +1,12 @@ +on: + push: + branches: + - '*' + +name: Fix Code Style + +jobs: + cs-fix: + permissions: + contents: write + uses: spiral/gh-actions/.github/workflows/cs-fix.yml@master diff --git a/.github/workflows/security-check.yml b/.github/workflows/security-check.yml index 3f645e14a..943e2297d 100644 --- a/.github/workflows/security-check.yml +++ b/.github/workflows/security-check.yml @@ -2,6 +2,10 @@ name: Security on: [push, pull_request] +concurrency: + cancel-in-progress: ${{ !contains(github.ref, 'release/')}} + group: tests-${{ github.workflow }}-${{ github.ref }} + jobs: security: name: Security Checks (PHP ${{ matrix.php }}, OS ${{ matrix.os }}) diff --git a/.github/workflows/code-style.yml b/.github/workflows/static-analysis.yml similarity index 88% rename from .github/workflows/code-style.yml rename to .github/workflows/static-analysis.yml index 94972a61f..3c5ae10cc 100644 --- a/.github/workflows/code-style.yml +++ b/.github/workflows/static-analysis.yml @@ -1,7 +1,11 @@ -name: Code Style +name: Static Analysis on: [push, pull_request] +concurrency: + cancel-in-progress: ${{ !contains(github.ref, 'release/')}} + group: tests-${{ github.workflow }}-${{ github.ref }} + jobs: psalm: name: Psalm Validation (PHP ${{ matrix.php }}, OS ${{ matrix.os }}) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 49d5d0d20..caae9bdb0 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -2,6 +2,10 @@ name: Testing on: [push, pull_request] +concurrency: + cancel-in-progress: ${{ !contains(github.ref, 'release/')}} + group: tests-${{ github.workflow }}-${{ github.ref }} + jobs: unit: name: Unit Testing diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 000000000..29e9b2bd8 --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,13 @@ +include(__DIR__ . '/src') + ->include(__DIR__ . '/testing/src') + ->include(__FILE__) + ->exclude(__DIR__ . '/src/Client/GRPC/ServiceClientInterface.php') + ->allowRisky(true) + ->build(); diff --git a/composer.json b/composer.json index e934dda5f..17a550a75 100644 --- a/composer.json +++ b/composer.json @@ -55,11 +55,11 @@ "composer/composer": "^2.0", "dereuromark/composer-prefer-lowest": "^0.1.10", "doctrine/annotations": "^1.14|^2.0.0", - "friendsofphp/php-cs-fixer": "^3.0", "internal/dload": "^1.0", "jetbrains/phpstorm-attributes": "dev-master@dev", "laminas/laminas-code": "^4.0", "phpunit/phpunit": "^10.5", + "spiral/code-style": "^2.1.2", "spiral/core": "^3.13", "symfony/var-dumper": "^6.0 || ^7.0", "vimeo/psalm": "^4.30 || ^5.4" @@ -81,15 +81,13 @@ }, "scripts": { "post-update-cmd": "Temporal\\Worker\\Transport\\RoadRunnerVersionChecker::postUpdate", + "cs:diff": "php-cs-fixer fix --dry-run -v --diff --show-progress dots", + "cs:fix": "php-cs-fixer fix -v", "psalm": "psalm", "psalm:baseline": "psalm --set-baseline=psalm-baseline.xml", "test:unit": "phpunit --testsuite=Unit --color=always --testdox", "test:func": "phpunit --testsuite=Functional --color=always --testdox", - "test:accept": "phpunit --testsuite=Acceptance --color=always --testdox", - "phpcs": [ - "@putenv PHP_CS_FIXER_IGNORE_ENV=1", - "php-cs-fixer fix src" - ] + "test:accept": "phpunit --testsuite=Acceptance --color=always --testdox" }, "extra": { "branch-alias": { diff --git a/psalm-baseline.xml b/psalm-baseline.xml index f37454a61..b0bb1673b 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,5 +1,5 @@ - + @@ -109,11 +109,6 @@ - - - getSeconds()]]> - - getSuccess()]]> @@ -132,12 +127,12 @@ newUntypedRunningWorkflowStub($workflowID, $runID, $workflow->getID()), - $workflow + $workflow, )]]> newUntypedWorkflowStub($workflow->getID(), $options), - $workflow + $workflow, )]]> @@ -171,7 +166,7 @@ - interval]]> + interval]]> @@ -217,43 +212,14 @@ - - ]]> - - - - - - - - - - - - - - - - - - - - - - - payloads[$index]]]> - values]]> - - payloads]]> - @@ -303,7 +269,7 @@ getMessage(), $e->getCode(), - $e + $e, )]]> $info->workflowExecution->getRunID(), 'activityId' => $info->id, 'activityType' => $info->type->name, - ] + ], ), $e === null ? 0 : $e->getCode(), - $e + $e, )]]> getCode(), $e)]]> @@ -346,7 +312,7 @@ - + getFailure()]]> @@ -368,14 +334,8 @@ - - - - - - @@ -387,9 +347,9 @@ - RetryState::name($value)]]> - TimeoutType::name($value)]]> - TimeoutType::name($value)]]> + RetryState::name($value)]]> + TimeoutType::name($value)]]> + TimeoutType::name($value)]]> @@ -422,13 +382,13 @@ retryOptions ? $options->retryOptions->toWorkflowRetryPolicy() : null]]> toMemo($this->converter)]]> toSearchAttributes($this->converter)]]> - workflowExecutionTimeout)]]> - workflowRunTimeout)]]> - workflowTaskTimeout)]]> + + + @@ -496,7 +456,7 @@ - + @@ -575,10 +535,6 @@ - - - - @@ -676,6 +632,11 @@ + + + + + @@ -701,20 +662,14 @@ - getSeconds(), $input->getNanos() / 1000), )]]> - + - - getFields(), $this->converter)]]> - - - - @@ -723,7 +678,7 @@ modify( - \sprintf('+%d seconds +%d microseconds', $input->getSeconds(), $input->getNanos() / 1000) + \sprintf('+%d seconds +%d microseconds', $input->getSeconds(), $input->getNanos() / 1000), )]]> @@ -738,25 +693,11 @@ format]]> format]]> - - - format)->totalMicroseconds * 1000]]> - - DateInterval::toDuration($value), - \is_int($value) => (new Duration())->setSeconds($value), - \is_string($value) => (new Duration())->setSeconds((int)$value), - \is_float($value) => (new Duration()) - ->setSeconds((int)$value) - ->setNanos(($value * 1000000000) % 1000000000), - default => throw new \InvalidArgumentException('Invalid value type.'), - }]]> - @@ -775,11 +716,11 @@ - + - reflection->getName() === stdClass::class - ? (object)$data + reflection->getName() === \stdClass::class + ? (object) $data : $this->marshaller->unmarshal($data, $this->reflection->newInstanceWithoutConstructor())]]> @@ -808,7 +749,6 @@ - >]]> @@ -908,24 +848,6 @@ - - - - - - - - - - - - - - - - - - @@ -1066,12 +988,6 @@ - - - - []]> - - @@ -1176,7 +1092,7 @@ workflow]]> - then( + execution->promise()->then( function (WorkflowExecution $execution) use ($name, $args) { $request = new SignalExternalWorkflow( $this->getOptions()->namespace, @@ -1188,7 +1104,7 @@ ); return $this->request($request); - } + }, )]]> start(...$args)->then(fn() => $this->getResult($returnType))]]> @@ -1315,22 +1231,6 @@ |T>]]> - - - - - - - - errorMessage)]]> - - - errorMessage)]]> - - - - - @@ -1343,9 +1243,6 @@ - - - getOptions()['name']]]> @@ -1371,8 +1268,6 @@ maxDepth]]> maxDepth]]> - - getCode()]]> @@ -1380,9 +1275,6 @@ - - getFields(), $this->dataConverter)]]> - $this->parseRequest($command, $info), @@ -1406,10 +1298,6 @@ - - getHistoryLength())]]> - getHistorySize())]]> - getCode()]]> getCode()]]> @@ -1422,9 +1310,6 @@ getCommand()]]> - - - getFailure()]]> getPayloads()]]> diff --git a/src/Activity/ActivityOptionsInterface.php b/src/Activity/ActivityOptionsInterface.php index b70e124b1..895c6eaf0 100644 --- a/src/Activity/ActivityOptionsInterface.php +++ b/src/Activity/ActivityOptionsInterface.php @@ -2,7 +2,6 @@ declare(strict_types=1); - namespace Temporal\Activity; use Temporal\Common\MethodRetry; diff --git a/src/Activity/LocalActivityOptions.php b/src/Activity/LocalActivityOptions.php index 7cdcae0da..f533bd036 100644 --- a/src/Activity/LocalActivityOptions.php +++ b/src/Activity/LocalActivityOptions.php @@ -110,9 +110,9 @@ public function mergeWith(MethodRetry $retry = null): self #[Pure] public function withScheduleToCloseTimeout($timeout): self { - assert(DateInterval::assert($timeout)); + \assert(DateInterval::assert($timeout)); $timeout = DateInterval::parse($timeout, DateInterval::FORMAT_SECONDS); - assert($timeout->totalMicroseconds >= 0); + \assert($timeout->totalMicroseconds >= 0); $self = clone $this; $self->scheduleToCloseTimeout = $timeout; @@ -132,9 +132,9 @@ public function withScheduleToCloseTimeout($timeout): self #[Pure] public function withStartToCloseTimeout($timeout): self { - assert(DateInterval::assert($timeout)); + \assert(DateInterval::assert($timeout)); $timeout = DateInterval::parse($timeout, DateInterval::FORMAT_SECONDS); - assert($timeout->totalMicroseconds >= 0); + \assert($timeout->totalMicroseconds >= 0); $self = clone $this; $self->startToCloseTimeout = $timeout; diff --git a/src/Client/ActivityCompletionClientInterface.php b/src/Client/ActivityCompletionClientInterface.php index 5b9718a7d..d8252acd0 100644 --- a/src/Client/ActivityCompletionClientInterface.php +++ b/src/Client/ActivityCompletionClientInterface.php @@ -43,7 +43,7 @@ public function completeExceptionally( string $workflowId, ?string $runId, string $activityId, - \Throwable $error + \Throwable $error, ): void; /** @@ -62,7 +62,7 @@ public function reportCancellation( string $workflowId, ?string $runId, string $activityId, - $details = null + $details = null, ): void; /** @@ -83,7 +83,7 @@ public function recordHeartbeat( string $workflowId, ?string $runId, string $activityId, - $details = null + $details = null, ); /** diff --git a/src/Client/ClientOptions.php b/src/Client/ClientOptions.php index d55fef4f4..6958fbbda 100644 --- a/src/Client/ClientOptions.php +++ b/src/Client/ClientOptions.php @@ -87,9 +87,9 @@ public function withIdentity(string $identity): self #[Pure] public function withQueryRejectionCondition( #[ExpectedValues(valuesFromClass: QueryRejectCondition::class)] - int $condition + int $condition, ): self { - assert(Assert::enum($condition, QueryRejectCondition::class)); + \assert(Assert::enum($condition, QueryRejectCondition::class)); $self = clone $this; diff --git a/src/Client/Common/BackoffThrottler.php b/src/Client/Common/BackoffThrottler.php index 3feab220d..d9ca2dfae 100644 --- a/src/Client/Common/BackoffThrottler.php +++ b/src/Client/Common/BackoffThrottler.php @@ -83,6 +83,6 @@ public function calculateSleepTime(int $failureCount, int $initialInterval): int $this->maxInterval, ); - return \abs((int)$sleepTime); + return \abs((int) $sleepTime); } } diff --git a/src/Client/Common/ClientContextInterface.php b/src/Client/Common/ClientContextInterface.php index 2cd6592ad..0996d47ad 100644 --- a/src/Client/Common/ClientContextInterface.php +++ b/src/Client/Common/ClientContextInterface.php @@ -4,8 +4,6 @@ namespace Temporal\Client\Common; -use Temporal\Common\RetryOptions; - interface ClientContextInterface { /** diff --git a/src/Client/Common/ClientContextTrait.php b/src/Client/Common/ClientContextTrait.php index 0810afadf..57f6e2f32 100644 --- a/src/Client/Common/ClientContextTrait.php +++ b/src/Client/Common/ClientContextTrait.php @@ -5,7 +5,6 @@ namespace Temporal\Client\Common; use Temporal\Client\GRPC\ServiceClientInterface; -use Temporal\Common\RetryOptions; use Temporal\Internal\Support\DateInterval; /** @@ -30,7 +29,7 @@ public function withTimeout(float $timeout): static /** @psalm-suppress InvalidOperand */ $timeout *= 1000; $new->client = $new->client->withContext( - $context->withTimeout((int)$timeout, DateInterval::FORMAT_MILLISECONDS), + $context->withTimeout((int) $timeout, DateInterval::FORMAT_MILLISECONDS), ); return $new; diff --git a/src/Client/Common/Paginator.php b/src/Client/Common/Paginator.php index ba89c5db3..22626130a 100644 --- a/src/Client/Common/Paginator.php +++ b/src/Client/Common/Paginator.php @@ -4,11 +4,7 @@ namespace Temporal\Client\Common; -use Closure; -use Countable; -use Generator; use IteratorAggregate; -use Traversable; /** * Paginator that allows to iterate over all pages. @@ -16,23 +12,25 @@ * @template TItem * @implements IteratorAggregate */ -final class Paginator implements IteratorAggregate, Countable +final class Paginator implements \IteratorAggregate, \Countable { /** @var list */ private array $collection; + /** @var self|null */ private ?self $nextPage = null; + private ?int $totalItems = null; /** - * @param Generator> $loader + * @param \Generator> $loader * @param int<1, max> $pageNumber - * @param null|Closure(): int<0, max> $counter + * @param null|\Closure(): int<0, max> $counter */ private function __construct( - private readonly Generator $loader, + private readonly \Generator $loader, private readonly int $pageNumber, - private ?Closure $counter, + private ?\Closure $counter, ) { $this->collection = $loader->current(); } @@ -40,14 +38,14 @@ private function __construct( /** * @template TInitItem * - * @param Generator> $loader + * @param \Generator> $loader * @param null|callable(): int<0, max> $counter Returns total number of items. * * @return self * * @internal */ - public static function createFromGenerator(Generator $loader, ?callable $counter): self + public static function createFromGenerator(\Generator $loader, ?callable $counter): self { return new self($loader, 1, $counter === null ? null : $counter(...)); } @@ -85,9 +83,9 @@ public function getPageItems(): array /** * Iterate all items from current page and all next pages. * - * @return Traversable + * @return \Traversable */ - public function getIterator(): Traversable + public function getIterator(): \Traversable { $paginator = $this; while ($paginator !== null) { diff --git a/src/Client/Common/RpcRetryOptions.php b/src/Client/Common/RpcRetryOptions.php index 31d715288..5261d764d 100644 --- a/src/Client/Common/RpcRetryOptions.php +++ b/src/Client/Common/RpcRetryOptions.php @@ -36,7 +36,7 @@ final class RpcRetryOptions extends RetryOptions */ public static function fromRetryOptions(RetryOptions $options): self { - return $options instanceof self ? $options : (new self()) + return $options instanceof self ? $options : (new self()) ->withInitialInterval($options->initialInterval) ->withBackoffCoefficient($options->backoffCoefficient) ->withMaximumInterval($options->maximumInterval) @@ -59,7 +59,7 @@ public static function fromRetryOptions(RetryOptions $options): self public function withCongestionInitialInterval($interval): self { $interval === null || DateInterval::assert($interval) or throw new \InvalidArgumentException( - 'Invalid interval value.' + 'Invalid interval value.', ); $self = clone $this; @@ -82,7 +82,7 @@ public function withCongestionInitialInterval($interval): self public function withMaximumJitterCoefficient(?float $coefficient): self { $coefficient === null || ($coefficient >= 0.0 && $coefficient < 1.0) or throw new \InvalidArgumentException( - 'Maximum jitter coefficient must be in range [0, 1).' + 'Maximum jitter coefficient must be in range [0, 1).', ); $self = clone $this; diff --git a/src/Client/Common/ServerCapabilities.php b/src/Client/Common/ServerCapabilities.php index 19031940e..a8d99dc4d 100644 --- a/src/Client/Common/ServerCapabilities.php +++ b/src/Client/Common/ServerCapabilities.php @@ -56,8 +56,7 @@ public function __construct( public readonly bool $eagerWorkflowStart = false, public readonly bool $sdkMetadata = false, public readonly bool $countGroupByExecutionStatus = false, - ) { - } + ) {} /** * True if signal and query headers are supported. diff --git a/src/Client/CountWorkflowExecutions.php b/src/Client/CountWorkflowExecutions.php index 4f048dce1..5e2e9ea35 100644 --- a/src/Client/CountWorkflowExecutions.php +++ b/src/Client/CountWorkflowExecutions.php @@ -8,7 +8,5 @@ /** * @deprecated use {@see \Temporal\Client\Workflow\CountWorkflowExecutions} instead. Will be removed in the future. */ - class CountWorkflowExecutions - { - } + class CountWorkflowExecutions {} } diff --git a/src/Client/GRPC/BaseClient.php b/src/Client/GRPC/BaseClient.php index 38af5ffa4..201a6eac8 100644 --- a/src/Client/GRPC/BaseClient.php +++ b/src/Client/GRPC/BaseClient.php @@ -12,10 +12,6 @@ namespace Temporal\Client\GRPC; use Carbon\CarbonInterval; -use Closure; -use DateTimeImmutable; -use Exception; -use Fiber; use Grpc\UnaryCall; use Temporal\Api\Workflowservice\V1\GetSystemInfoRequest; use Temporal\Api\Workflowservice\V1\WorkflowServiceClient; @@ -38,21 +34,21 @@ abstract class BaseClient implements ServiceClientInterface StatusCode::UNKNOWN, ]; - /** @var null|Closure(string $method, object $arg, ContextInterface $ctx): object */ - private ?Closure $invokePipeline = null; + /** @var null|\Closure(string $method, object $arg, ContextInterface $ctx): object */ + private ?\Closure $invokePipeline = null; private Connection $connection; private ContextInterface $context; private \Stringable|string $apiKey = ''; /** - * @param WorkflowServiceClient|Closure(): WorkflowServiceClient $workflowService Service Client or its factory + * @param WorkflowServiceClient|\Closure(): WorkflowServiceClient $workflowService Service Client or its factory * * @private Use static factory methods instead * @see self::create() * @see self::createSSL() */ - final public function __construct(WorkflowServiceClient|Closure $workflowService) + final public function __construct(WorkflowServiceClient|\Closure $workflowService) { if ($workflowService instanceof WorkflowServiceClient) { \trigger_error( @@ -66,41 +62,6 @@ final public function __construct(WorkflowServiceClient|Closure $workflowService $this->context = Context::default(); } - public function getContext(): ContextInterface - { - return $this->context; - } - - public function withContext(ContextInterface $context): static - { - $clone = clone $this; - $clone->context = $context; - return $clone; - } - - /** - * Set the authentication token for the service client. - * - * This is the equivalent of providing an "Authorization" header with "Bearer " + the given key. - * This will overwrite any "Authorization" header that may be on the context before each request to the - * Temporal service. - * You may pass your own {@see \Stringable} implementation to be able to change the key dynamically. - */ - public function withAuthKey(\Stringable|string $key): static - { - $clone = clone $this; - $clone->apiKey = $key; - return $clone; - } - - /** - * Close the communication channel associated with this stub. - */ - public function close(): void - { - $this->connection->disconnect(); - } - /** * @param non-empty-string $address Temporal service address in format `host:port` * @return static @@ -114,7 +75,7 @@ public static function create(string $address): static return new static(static fn(): WorkflowServiceClient => new WorkflowServiceClient( $address, - ['credentials' => \Grpc\ChannelCredentials::createInsecure()] + ['credentials' => \Grpc\ChannelCredentials::createInsecure()], )); } @@ -135,7 +96,7 @@ public static function createSSL( string $crt = null, string $clientKey = null, string $clientPem = null, - string $overrideServerName = null + string $overrideServerName = null, ): static { if (!\extension_loaded('grpc')) { throw new \RuntimeException('The gRPC extension is required to use Temporal Client.'); @@ -156,7 +117,7 @@ public static function createSSL( $loadCert($crt), $loadCert($clientKey), $loadCert($clientPem), - ) + ), ]; if ($overrideServerName !== null) { @@ -167,6 +128,41 @@ public static function createSSL( return new static(static fn(): WorkflowServiceClient => new WorkflowServiceClient($address, $options)); } + public function getContext(): ContextInterface + { + return $this->context; + } + + public function withContext(ContextInterface $context): static + { + $clone = clone $this; + $clone->context = $context; + return $clone; + } + + /** + * Set the authentication token for the service client. + * + * This is the equivalent of providing an "Authorization" header with "Bearer " + the given key. + * This will overwrite any "Authorization" header that may be on the context before each request to the + * Temporal service. + * You may pass your own {@see \Stringable} implementation to be able to change the key dynamically. + */ + public function withAuthKey(\Stringable|string $key): static + { + $clone = clone $this; + $clone->apiKey = $key; + return $clone; + } + + /** + * Close the communication channel associated with this stub. + */ + public function close(): void + { + $this->connection->disconnect(); + } + /** * @param null|Pipeline $pipeline * @@ -230,7 +226,8 @@ public function setServerCapabilities(ServerCapabilities $capabilities): void /** * Note: Experimental */ - public function getConnection(): ConnectionInterface { + public function getConnection(): ConnectionInterface + { return $this->connection; } @@ -248,7 +245,7 @@ protected function invoke(string $method, object $arg, ?ContextInterface $ctx = $ctx ??= $this->context; // Add the API key to the context - $key = (string)$this->apiKey; + $key = (string) $this->apiKey; if ($key !== '') { $ctx = $ctx->withMetadata([ 'Authorization' => ["Bearer $key"], @@ -270,7 +267,7 @@ protected function invoke(string $method, object $arg, ?ContextInterface $ctx = * * @return object * - * @throws Exception + * @throws \Exception */ private function call(string $method, object $arg, ContextInterface $ctx): object { @@ -316,25 +313,25 @@ private function call(string $method, object $arg, ContextInterface $ctx): objec throw $e; } - if ($ctx->getDeadline() !== null && new DateTimeImmutable() > $ctx->getDeadline()) { + if ($ctx->getDeadline() !== null && new \DateTimeImmutable() > $ctx->getDeadline()) { // Deadline is reached throw new TimeoutException('Call timeout has been reached'); } // Init interval values in milliseconds $initialIntervalMs ??= $retryOption->initialInterval === null - ? (int)CarbonInterval::millisecond(50)->totalMilliseconds - : (int)(new CarbonInterval($retryOption->initialInterval))->totalMilliseconds; + ? (int) CarbonInterval::millisecond(50)->totalMilliseconds + : (int) (new CarbonInterval($retryOption->initialInterval))->totalMilliseconds; $congestionInitialIntervalMs ??= $retryOption->congestionInitialInterval === null - ? (int)CarbonInterval::millisecond(1000)->totalMilliseconds - : (int)(new CarbonInterval($retryOption->congestionInitialInterval))->totalMilliseconds; + ? (int) CarbonInterval::millisecond(1000)->totalMilliseconds + : (int) (new CarbonInterval($retryOption->congestionInitialInterval))->totalMilliseconds; $throttler ??= new BackoffThrottler( maxInterval: $retryOption->maximumInterval !== null - ? (int)(new CarbonInterval($retryOption->maximumInterval))->totalMilliseconds + ? (int) (new CarbonInterval($retryOption->maximumInterval))->totalMilliseconds : $initialIntervalMs * 200, maxJitterCoefficient: $retryOption->maximumJitterCoefficient, - backoffCoefficient: $retryOption->backoffCoefficient + backoffCoefficient: $retryOption->backoffCoefficient, ); // Initial interval always depends on the *most recent* failure. @@ -355,15 +352,15 @@ private function call(string $method, object $arg, ContextInterface $ctx): objec */ private function usleep(int $param): void { - if (Fiber::getCurrent() === null) { + if (\Fiber::getCurrent() === null) { \usleep($param); return; } - $deadline = \microtime(true) + (float)($param / 1_000_000); + $deadline = \microtime(true) + (float) ($param / 1_000_000); while (\microtime(true) < $deadline) { - Fiber::suspend(); + \Fiber::suspend(); } } } diff --git a/src/Client/GRPC/Connection/Connection.php b/src/Client/GRPC/Connection/Connection.php index 520f03d59..de6db09f3 100644 --- a/src/Client/GRPC/Connection/Connection.php +++ b/src/Client/GRPC/Connection/Connection.php @@ -4,8 +4,6 @@ namespace Temporal\Client\GRPC\Connection; -use Closure; -use Fiber; use Temporal\Api\Workflowservice\V1\WorkflowServiceClient; use Temporal\Client\Common\ServerCapabilities; @@ -23,19 +21,14 @@ final class Connection implements ConnectionInterface private bool $closed = true; /** - * @param Closure(): WorkflowServiceClient $clientFactory Service Client factory + * @param \Closure(): WorkflowServiceClient $clientFactory Service Client factory */ public function __construct( - public Closure $clientFactory, + public \Closure $clientFactory, ) { $this->initClient(); } - public function __destruct() - { - $this->disconnect(); - } - public function isConnected(): bool { return ConnectionState::from($this->workflowService->getConnectivityState(false)) === ConnectionState::Ready; @@ -57,11 +50,11 @@ public function connect(float $timeout): void // Start connecting $this->getState(true); - $isFiber = Fiber::getCurrent() !== null; + $isFiber = \Fiber::getCurrent() !== null; do { // Wait a bit if ($isFiber) { - Fiber::suspend(); + \Fiber::suspend(); } else { $this->workflowService->waitForReady(50); } @@ -72,13 +65,13 @@ public function connect(float $timeout): void $alive or throw new \RuntimeException('Failed to connect to Temporal service. Timeout exceeded.'); $state === ConnectionState::Idle and throw new \RuntimeException( - 'Failed to connect to Temporal service. Channel is in idle state.' + 'Failed to connect to Temporal service. Channel is in idle state.', ); $state === ConnectionState::TransientFailure and throw new \RuntimeException( - 'Failed to connect to Temporal service. Channel is in transient failure state.' + 'Failed to connect to Temporal service. Channel is in transient failure state.', ); $state === ConnectionState::Shutdown and throw new \RuntimeException( - 'Failed to connect to Temporal service. Channel is in shutdown state.' + 'Failed to connect to Temporal service. Channel is in shutdown state.', ); } @@ -102,6 +95,11 @@ public function getWorkflowService(): WorkflowServiceClient return $this->workflowService; } + public function __destruct() + { + $this->disconnect(); + } + private function getState(bool $tryToConnect = false): ConnectionState { return ConnectionState::from($this->workflowService->getConnectivityState($tryToConnect)); @@ -131,6 +129,6 @@ private function initClient(): void private function waitForReady(float $timeout): bool { /** @psalm-suppress InvalidOperand */ - return $this->workflowService->waitForReady((int)($timeout * 1_000_000)); + return $this->workflowService->waitForReady((int) ($timeout * 1_000_000)); } } diff --git a/src/Client/GRPC/Context.php b/src/Client/GRPC/Context.php index 12b9034d6..9d35914e8 100644 --- a/src/Client/GRPC/Context.php +++ b/src/Client/GRPC/Context.php @@ -37,6 +37,11 @@ private function __construct() ]; } + public static function default(): self + { + return new self(); + } + public function withTimeout($timeout, string $format = DateInterval::FORMAT_SECONDS): self { $internal = DateInterval::parse($timeout, $format); @@ -104,9 +109,4 @@ public function getRetryOptions(): RetryOptions { return $this->retryOptions; } - - public static function default(): self - { - return new self(); - } } diff --git a/src/Client/GRPC/ServiceClient.php b/src/Client/GRPC/ServiceClient.php index 76e961d65..a1bb3b070 100644 --- a/src/Client/GRPC/ServiceClient.php +++ b/src/Client/GRPC/ServiceClient.php @@ -1,4 +1,6 @@ invoke("RegisterNamespace", $arg, $ctx); } @@ -45,7 +46,7 @@ public function RegisterNamespace(V1\RegisterNamespaceRequest $arg, ContextInter * @return V1\DescribeNamespaceResponse * @throws ServiceClientException */ - public function DescribeNamespace(V1\DescribeNamespaceRequest $arg, ContextInterface $ctx = null) : V1\DescribeNamespaceResponse + public function DescribeNamespace(V1\DescribeNamespaceRequest $arg, ContextInterface $ctx = null): V1\DescribeNamespaceResponse { return $this->invoke("DescribeNamespace", $arg, $ctx); } @@ -58,7 +59,7 @@ public function DescribeNamespace(V1\DescribeNamespaceRequest $arg, ContextInter * @return V1\ListNamespacesResponse * @throws ServiceClientException */ - public function ListNamespaces(V1\ListNamespacesRequest $arg, ContextInterface $ctx = null) : V1\ListNamespacesResponse + public function ListNamespaces(V1\ListNamespacesRequest $arg, ContextInterface $ctx = null): V1\ListNamespacesResponse { return $this->invoke("ListNamespaces", $arg, $ctx); } @@ -73,7 +74,7 @@ public function ListNamespaces(V1\ListNamespacesRequest $arg, ContextInterface $ * @return V1\UpdateNamespaceResponse * @throws ServiceClientException */ - public function UpdateNamespace(V1\UpdateNamespaceRequest $arg, ContextInterface $ctx = null) : V1\UpdateNamespaceResponse + public function UpdateNamespace(V1\UpdateNamespaceRequest $arg, ContextInterface $ctx = null): V1\UpdateNamespaceResponse { return $this->invoke("UpdateNamespace", $arg, $ctx); } @@ -95,7 +96,7 @@ public function UpdateNamespace(V1\UpdateNamespaceRequest $arg, ContextInterface * @return V1\DeprecateNamespaceResponse * @throws ServiceClientException */ - public function DeprecateNamespace(V1\DeprecateNamespaceRequest $arg, ContextInterface $ctx = null) : V1\DeprecateNamespaceResponse + public function DeprecateNamespace(V1\DeprecateNamespaceRequest $arg, ContextInterface $ctx = null): V1\DeprecateNamespaceResponse { return $this->invoke("DeprecateNamespace", $arg, $ctx); } @@ -114,7 +115,7 @@ public function DeprecateNamespace(V1\DeprecateNamespaceRequest $arg, ContextInt * @return V1\StartWorkflowExecutionResponse * @throws ServiceClientException */ - public function StartWorkflowExecution(V1\StartWorkflowExecutionRequest $arg, ContextInterface $ctx = null) : V1\StartWorkflowExecutionResponse + public function StartWorkflowExecution(V1\StartWorkflowExecutionRequest $arg, ContextInterface $ctx = null): V1\StartWorkflowExecutionResponse { return $this->invoke("StartWorkflowExecution", $arg, $ctx); } @@ -129,7 +130,7 @@ public function StartWorkflowExecution(V1\StartWorkflowExecutionRequest $arg, Co * @return V1\GetWorkflowExecutionHistoryResponse * @throws ServiceClientException */ - public function GetWorkflowExecutionHistory(V1\GetWorkflowExecutionHistoryRequest $arg, ContextInterface $ctx = null) : V1\GetWorkflowExecutionHistoryResponse + public function GetWorkflowExecutionHistory(V1\GetWorkflowExecutionHistoryRequest $arg, ContextInterface $ctx = null): V1\GetWorkflowExecutionHistoryResponse { return $this->invoke("GetWorkflowExecutionHistory", $arg, $ctx); } @@ -146,7 +147,7 @@ public function GetWorkflowExecutionHistory(V1\GetWorkflowExecutionHistoryReques * @return V1\GetWorkflowExecutionHistoryReverseResponse * @throws ServiceClientException */ - public function GetWorkflowExecutionHistoryReverse(V1\GetWorkflowExecutionHistoryReverseRequest $arg, ContextInterface $ctx = null) : V1\GetWorkflowExecutionHistoryReverseResponse + public function GetWorkflowExecutionHistoryReverse(V1\GetWorkflowExecutionHistoryReverseRequest $arg, ContextInterface $ctx = null): V1\GetWorkflowExecutionHistoryReverseResponse { return $this->invoke("GetWorkflowExecutionHistoryReverse", $arg, $ctx); } @@ -170,7 +171,7 @@ public function GetWorkflowExecutionHistoryReverse(V1\GetWorkflowExecutionHistor * @return V1\PollWorkflowTaskQueueResponse * @throws ServiceClientException */ - public function PollWorkflowTaskQueue(V1\PollWorkflowTaskQueueRequest $arg, ContextInterface $ctx = null) : V1\PollWorkflowTaskQueueResponse + public function PollWorkflowTaskQueue(V1\PollWorkflowTaskQueueRequest $arg, ContextInterface $ctx = null): V1\PollWorkflowTaskQueueResponse { return $this->invoke("PollWorkflowTaskQueue", $arg, $ctx); } @@ -194,7 +195,7 @@ public function PollWorkflowTaskQueue(V1\PollWorkflowTaskQueueRequest $arg, Cont * @return V1\RespondWorkflowTaskCompletedResponse * @throws ServiceClientException */ - public function RespondWorkflowTaskCompleted(V1\RespondWorkflowTaskCompletedRequest $arg, ContextInterface $ctx = null) : V1\RespondWorkflowTaskCompletedResponse + public function RespondWorkflowTaskCompleted(V1\RespondWorkflowTaskCompletedRequest $arg, ContextInterface $ctx = null): V1\RespondWorkflowTaskCompletedResponse { return $this->invoke("RespondWorkflowTaskCompleted", $arg, $ctx); } @@ -222,7 +223,7 @@ public function RespondWorkflowTaskCompleted(V1\RespondWorkflowTaskCompletedRequ * @return V1\RespondWorkflowTaskFailedResponse * @throws ServiceClientException */ - public function RespondWorkflowTaskFailed(V1\RespondWorkflowTaskFailedRequest $arg, ContextInterface $ctx = null) : V1\RespondWorkflowTaskFailedResponse + public function RespondWorkflowTaskFailed(V1\RespondWorkflowTaskFailedRequest $arg, ContextInterface $ctx = null): V1\RespondWorkflowTaskFailedResponse { return $this->invoke("RespondWorkflowTaskFailed", $arg, $ctx); } @@ -256,7 +257,7 @@ public function RespondWorkflowTaskFailed(V1\RespondWorkflowTaskFailedRequest $a * @return V1\PollActivityTaskQueueResponse * @throws ServiceClientException */ - public function PollActivityTaskQueue(V1\PollActivityTaskQueueRequest $arg, ContextInterface $ctx = null) : V1\PollActivityTaskQueueResponse + public function PollActivityTaskQueue(V1\PollActivityTaskQueueRequest $arg, ContextInterface $ctx = null): V1\PollActivityTaskQueueResponse { return $this->invoke("PollActivityTaskQueue", $arg, $ctx); } @@ -279,7 +280,7 @@ public function PollActivityTaskQueue(V1\PollActivityTaskQueueRequest $arg, Cont * @return V1\RecordActivityTaskHeartbeatResponse * @throws ServiceClientException */ - public function RecordActivityTaskHeartbeat(V1\RecordActivityTaskHeartbeatRequest $arg, ContextInterface $ctx = null) : V1\RecordActivityTaskHeartbeatResponse + public function RecordActivityTaskHeartbeat(V1\RecordActivityTaskHeartbeatRequest $arg, ContextInterface $ctx = null): V1\RecordActivityTaskHeartbeatResponse { return $this->invoke("RecordActivityTaskHeartbeat", $arg, $ctx); } @@ -297,7 +298,7 @@ public function RecordActivityTaskHeartbeat(V1\RecordActivityTaskHeartbeatReques * @return V1\RecordActivityTaskHeartbeatByIdResponse * @throws ServiceClientException */ - public function RecordActivityTaskHeartbeatById(V1\RecordActivityTaskHeartbeatByIdRequest $arg, ContextInterface $ctx = null) : V1\RecordActivityTaskHeartbeatByIdResponse + public function RecordActivityTaskHeartbeatById(V1\RecordActivityTaskHeartbeatByIdRequest $arg, ContextInterface $ctx = null): V1\RecordActivityTaskHeartbeatByIdResponse { return $this->invoke("RecordActivityTaskHeartbeatById", $arg, $ctx); } @@ -319,7 +320,7 @@ public function RecordActivityTaskHeartbeatById(V1\RecordActivityTaskHeartbeatBy * @return V1\RespondActivityTaskCompletedResponse * @throws ServiceClientException */ - public function RespondActivityTaskCompleted(V1\RespondActivityTaskCompletedRequest $arg, ContextInterface $ctx = null) : V1\RespondActivityTaskCompletedResponse + public function RespondActivityTaskCompleted(V1\RespondActivityTaskCompletedRequest $arg, ContextInterface $ctx = null): V1\RespondActivityTaskCompletedResponse { return $this->invoke("RespondActivityTaskCompleted", $arg, $ctx); } @@ -337,7 +338,7 @@ public function RespondActivityTaskCompleted(V1\RespondActivityTaskCompletedRequ * @return V1\RespondActivityTaskCompletedByIdResponse * @throws ServiceClientException */ - public function RespondActivityTaskCompletedById(V1\RespondActivityTaskCompletedByIdRequest $arg, ContextInterface $ctx = null) : V1\RespondActivityTaskCompletedByIdResponse + public function RespondActivityTaskCompletedById(V1\RespondActivityTaskCompletedByIdRequest $arg, ContextInterface $ctx = null): V1\RespondActivityTaskCompletedByIdResponse { return $this->invoke("RespondActivityTaskCompletedById", $arg, $ctx); } @@ -358,7 +359,7 @@ public function RespondActivityTaskCompletedById(V1\RespondActivityTaskCompleted * @return V1\RespondActivityTaskFailedResponse * @throws ServiceClientException */ - public function RespondActivityTaskFailed(V1\RespondActivityTaskFailedRequest $arg, ContextInterface $ctx = null) : V1\RespondActivityTaskFailedResponse + public function RespondActivityTaskFailed(V1\RespondActivityTaskFailedRequest $arg, ContextInterface $ctx = null): V1\RespondActivityTaskFailedResponse { return $this->invoke("RespondActivityTaskFailed", $arg, $ctx); } @@ -376,7 +377,7 @@ public function RespondActivityTaskFailed(V1\RespondActivityTaskFailedRequest $a * @return V1\RespondActivityTaskFailedByIdResponse * @throws ServiceClientException */ - public function RespondActivityTaskFailedById(V1\RespondActivityTaskFailedByIdRequest $arg, ContextInterface $ctx = null) : V1\RespondActivityTaskFailedByIdResponse + public function RespondActivityTaskFailedById(V1\RespondActivityTaskFailedByIdRequest $arg, ContextInterface $ctx = null): V1\RespondActivityTaskFailedByIdResponse { return $this->invoke("RespondActivityTaskFailedById", $arg, $ctx); } @@ -397,7 +398,7 @@ public function RespondActivityTaskFailedById(V1\RespondActivityTaskFailedByIdRe * @return V1\RespondActivityTaskCanceledResponse * @throws ServiceClientException */ - public function RespondActivityTaskCanceled(V1\RespondActivityTaskCanceledRequest $arg, ContextInterface $ctx = null) : V1\RespondActivityTaskCanceledResponse + public function RespondActivityTaskCanceled(V1\RespondActivityTaskCanceledRequest $arg, ContextInterface $ctx = null): V1\RespondActivityTaskCanceledResponse { return $this->invoke("RespondActivityTaskCanceled", $arg, $ctx); } @@ -415,7 +416,7 @@ public function RespondActivityTaskCanceled(V1\RespondActivityTaskCanceledReques * @return V1\RespondActivityTaskCanceledByIdResponse * @throws ServiceClientException */ - public function RespondActivityTaskCanceledById(V1\RespondActivityTaskCanceledByIdRequest $arg, ContextInterface $ctx = null) : V1\RespondActivityTaskCanceledByIdResponse + public function RespondActivityTaskCanceledById(V1\RespondActivityTaskCanceledByIdRequest $arg, ContextInterface $ctx = null): V1\RespondActivityTaskCanceledByIdResponse { return $this->invoke("RespondActivityTaskCanceledById", $arg, $ctx); } @@ -437,7 +438,7 @@ public function RespondActivityTaskCanceledById(V1\RespondActivityTaskCanceledBy * @return V1\RequestCancelWorkflowExecutionResponse * @throws ServiceClientException */ - public function RequestCancelWorkflowExecution(V1\RequestCancelWorkflowExecutionRequest $arg, ContextInterface $ctx = null) : V1\RequestCancelWorkflowExecutionResponse + public function RequestCancelWorkflowExecution(V1\RequestCancelWorkflowExecutionRequest $arg, ContextInterface $ctx = null): V1\RequestCancelWorkflowExecutionResponse { return $this->invoke("RequestCancelWorkflowExecution", $arg, $ctx); } @@ -455,7 +456,7 @@ public function RequestCancelWorkflowExecution(V1\RequestCancelWorkflowExecution * @return V1\SignalWorkflowExecutionResponse * @throws ServiceClientException */ - public function SignalWorkflowExecution(V1\SignalWorkflowExecutionRequest $arg, ContextInterface $ctx = null) : V1\SignalWorkflowExecutionResponse + public function SignalWorkflowExecution(V1\SignalWorkflowExecutionRequest $arg, ContextInterface $ctx = null): V1\SignalWorkflowExecutionResponse { return $this->invoke("SignalWorkflowExecution", $arg, $ctx); } @@ -482,7 +483,7 @@ public function SignalWorkflowExecution(V1\SignalWorkflowExecutionRequest $arg, * @return V1\SignalWithStartWorkflowExecutionResponse * @throws ServiceClientException */ - public function SignalWithStartWorkflowExecution(V1\SignalWithStartWorkflowExecutionRequest $arg, ContextInterface $ctx = null) : V1\SignalWithStartWorkflowExecutionResponse + public function SignalWithStartWorkflowExecution(V1\SignalWithStartWorkflowExecutionRequest $arg, ContextInterface $ctx = null): V1\SignalWithStartWorkflowExecutionResponse { return $this->invoke("SignalWithStartWorkflowExecution", $arg, $ctx); } @@ -500,7 +501,7 @@ public function SignalWithStartWorkflowExecution(V1\SignalWithStartWorkflowExecu * @return V1\ResetWorkflowExecutionResponse * @throws ServiceClientException */ - public function ResetWorkflowExecution(V1\ResetWorkflowExecutionRequest $arg, ContextInterface $ctx = null) : V1\ResetWorkflowExecutionResponse + public function ResetWorkflowExecution(V1\ResetWorkflowExecutionRequest $arg, ContextInterface $ctx = null): V1\ResetWorkflowExecutionResponse { return $this->invoke("ResetWorkflowExecution", $arg, $ctx); } @@ -517,7 +518,7 @@ public function ResetWorkflowExecution(V1\ResetWorkflowExecutionRequest $arg, Co * @return V1\TerminateWorkflowExecutionResponse * @throws ServiceClientException */ - public function TerminateWorkflowExecution(V1\TerminateWorkflowExecutionRequest $arg, ContextInterface $ctx = null) : V1\TerminateWorkflowExecutionResponse + public function TerminateWorkflowExecution(V1\TerminateWorkflowExecutionRequest $arg, ContextInterface $ctx = null): V1\TerminateWorkflowExecutionResponse { return $this->invoke("TerminateWorkflowExecution", $arg, $ctx); } @@ -539,7 +540,7 @@ public function TerminateWorkflowExecution(V1\TerminateWorkflowExecutionRequest * @return V1\DeleteWorkflowExecutionResponse * @throws ServiceClientException */ - public function DeleteWorkflowExecution(V1\DeleteWorkflowExecutionRequest $arg, ContextInterface $ctx = null) : V1\DeleteWorkflowExecutionResponse + public function DeleteWorkflowExecution(V1\DeleteWorkflowExecutionRequest $arg, ContextInterface $ctx = null): V1\DeleteWorkflowExecutionResponse { return $this->invoke("DeleteWorkflowExecution", $arg, $ctx); } @@ -556,7 +557,7 @@ public function DeleteWorkflowExecution(V1\DeleteWorkflowExecutionRequest $arg, * @return V1\ListOpenWorkflowExecutionsResponse * @throws ServiceClientException */ - public function ListOpenWorkflowExecutions(V1\ListOpenWorkflowExecutionsRequest $arg, ContextInterface $ctx = null) : V1\ListOpenWorkflowExecutionsResponse + public function ListOpenWorkflowExecutions(V1\ListOpenWorkflowExecutionsRequest $arg, ContextInterface $ctx = null): V1\ListOpenWorkflowExecutionsResponse { return $this->invoke("ListOpenWorkflowExecutions", $arg, $ctx); } @@ -573,7 +574,7 @@ public function ListOpenWorkflowExecutions(V1\ListOpenWorkflowExecutionsRequest * @return V1\ListClosedWorkflowExecutionsResponse * @throws ServiceClientException */ - public function ListClosedWorkflowExecutions(V1\ListClosedWorkflowExecutionsRequest $arg, ContextInterface $ctx = null) : V1\ListClosedWorkflowExecutionsResponse + public function ListClosedWorkflowExecutions(V1\ListClosedWorkflowExecutionsRequest $arg, ContextInterface $ctx = null): V1\ListClosedWorkflowExecutionsResponse { return $this->invoke("ListClosedWorkflowExecutions", $arg, $ctx); } @@ -587,7 +588,7 @@ public function ListClosedWorkflowExecutions(V1\ListClosedWorkflowExecutionsRequ * @return V1\ListWorkflowExecutionsResponse * @throws ServiceClientException */ - public function ListWorkflowExecutions(V1\ListWorkflowExecutionsRequest $arg, ContextInterface $ctx = null) : V1\ListWorkflowExecutionsResponse + public function ListWorkflowExecutions(V1\ListWorkflowExecutionsRequest $arg, ContextInterface $ctx = null): V1\ListWorkflowExecutionsResponse { return $this->invoke("ListWorkflowExecutions", $arg, $ctx); } @@ -601,7 +602,7 @@ public function ListWorkflowExecutions(V1\ListWorkflowExecutionsRequest $arg, Co * @return V1\ListArchivedWorkflowExecutionsResponse * @throws ServiceClientException */ - public function ListArchivedWorkflowExecutions(V1\ListArchivedWorkflowExecutionsRequest $arg, ContextInterface $ctx = null) : V1\ListArchivedWorkflowExecutionsResponse + public function ListArchivedWorkflowExecutions(V1\ListArchivedWorkflowExecutionsRequest $arg, ContextInterface $ctx = null): V1\ListArchivedWorkflowExecutionsResponse { return $this->invoke("ListArchivedWorkflowExecutions", $arg, $ctx); } @@ -618,7 +619,7 @@ public function ListArchivedWorkflowExecutions(V1\ListArchivedWorkflowExecutions * @return V1\ScanWorkflowExecutionsResponse * @throws ServiceClientException */ - public function ScanWorkflowExecutions(V1\ScanWorkflowExecutionsRequest $arg, ContextInterface $ctx = null) : V1\ScanWorkflowExecutionsResponse + public function ScanWorkflowExecutions(V1\ScanWorkflowExecutionsRequest $arg, ContextInterface $ctx = null): V1\ScanWorkflowExecutionsResponse { return $this->invoke("ScanWorkflowExecutions", $arg, $ctx); } @@ -632,7 +633,7 @@ public function ScanWorkflowExecutions(V1\ScanWorkflowExecutionsRequest $arg, Co * @return V1\CountWorkflowExecutionsResponse * @throws ServiceClientException */ - public function CountWorkflowExecutions(V1\CountWorkflowExecutionsRequest $arg, ContextInterface $ctx = null) : V1\CountWorkflowExecutionsResponse + public function CountWorkflowExecutions(V1\CountWorkflowExecutionsRequest $arg, ContextInterface $ctx = null): V1\CountWorkflowExecutionsResponse { return $this->invoke("CountWorkflowExecutions", $arg, $ctx); } @@ -650,7 +651,7 @@ public function CountWorkflowExecutions(V1\CountWorkflowExecutionsRequest $arg, * @return V1\GetSearchAttributesResponse * @throws ServiceClientException */ - public function GetSearchAttributes(V1\GetSearchAttributesRequest $arg, ContextInterface $ctx = null) : V1\GetSearchAttributesResponse + public function GetSearchAttributes(V1\GetSearchAttributesRequest $arg, ContextInterface $ctx = null): V1\GetSearchAttributesResponse { return $this->invoke("GetSearchAttributes", $arg, $ctx); } @@ -672,7 +673,7 @@ public function GetSearchAttributes(V1\GetSearchAttributesRequest $arg, ContextI * @return V1\RespondQueryTaskCompletedResponse * @throws ServiceClientException */ - public function RespondQueryTaskCompleted(V1\RespondQueryTaskCompletedRequest $arg, ContextInterface $ctx = null) : V1\RespondQueryTaskCompletedResponse + public function RespondQueryTaskCompleted(V1\RespondQueryTaskCompletedRequest $arg, ContextInterface $ctx = null): V1\RespondQueryTaskCompletedResponse { return $this->invoke("RespondQueryTaskCompleted", $arg, $ctx); } @@ -696,7 +697,7 @@ public function RespondQueryTaskCompleted(V1\RespondQueryTaskCompletedRequest $a * @return V1\ResetStickyTaskQueueResponse * @throws ServiceClientException */ - public function ResetStickyTaskQueue(V1\ResetStickyTaskQueueRequest $arg, ContextInterface $ctx = null) : V1\ResetStickyTaskQueueResponse + public function ResetStickyTaskQueue(V1\ResetStickyTaskQueueRequest $arg, ContextInterface $ctx = null): V1\ResetStickyTaskQueueResponse { return $this->invoke("ResetStickyTaskQueue", $arg, $ctx); } @@ -709,7 +710,7 @@ public function ResetStickyTaskQueue(V1\ResetStickyTaskQueueRequest $arg, Contex * @return V1\QueryWorkflowResponse * @throws ServiceClientException */ - public function QueryWorkflow(V1\QueryWorkflowRequest $arg, ContextInterface $ctx = null) : V1\QueryWorkflowResponse + public function QueryWorkflow(V1\QueryWorkflowRequest $arg, ContextInterface $ctx = null): V1\QueryWorkflowResponse { return $this->invoke("QueryWorkflow", $arg, $ctx); } @@ -723,7 +724,7 @@ public function QueryWorkflow(V1\QueryWorkflowRequest $arg, ContextInterface $ct * @return V1\DescribeWorkflowExecutionResponse * @throws ServiceClientException */ - public function DescribeWorkflowExecution(V1\DescribeWorkflowExecutionRequest $arg, ContextInterface $ctx = null) : V1\DescribeWorkflowExecutionResponse + public function DescribeWorkflowExecution(V1\DescribeWorkflowExecutionRequest $arg, ContextInterface $ctx = null): V1\DescribeWorkflowExecutionResponse { return $this->invoke("DescribeWorkflowExecution", $arg, $ctx); } @@ -740,7 +741,7 @@ public function DescribeWorkflowExecution(V1\DescribeWorkflowExecutionRequest $a * @return V1\DescribeTaskQueueResponse * @throws ServiceClientException */ - public function DescribeTaskQueue(V1\DescribeTaskQueueRequest $arg, ContextInterface $ctx = null) : V1\DescribeTaskQueueResponse + public function DescribeTaskQueue(V1\DescribeTaskQueueRequest $arg, ContextInterface $ctx = null): V1\DescribeTaskQueueResponse { return $this->invoke("DescribeTaskQueue", $arg, $ctx); } @@ -753,7 +754,7 @@ public function DescribeTaskQueue(V1\DescribeTaskQueueRequest $arg, ContextInter * @return V1\GetClusterInfoResponse * @throws ServiceClientException */ - public function GetClusterInfo(V1\GetClusterInfoRequest $arg, ContextInterface $ctx = null) : V1\GetClusterInfoResponse + public function GetClusterInfo(V1\GetClusterInfoRequest $arg, ContextInterface $ctx = null): V1\GetClusterInfoResponse { return $this->invoke("GetClusterInfo", $arg, $ctx); } @@ -766,7 +767,7 @@ public function GetClusterInfo(V1\GetClusterInfoRequest $arg, ContextInterface $ * @return V1\GetSystemInfoResponse * @throws ServiceClientException */ - public function GetSystemInfo(V1\GetSystemInfoRequest $arg, ContextInterface $ctx = null) : V1\GetSystemInfoResponse + public function GetSystemInfo(V1\GetSystemInfoRequest $arg, ContextInterface $ctx = null): V1\GetSystemInfoResponse { return $this->invoke("GetSystemInfo", $arg, $ctx); } @@ -780,7 +781,7 @@ public function GetSystemInfo(V1\GetSystemInfoRequest $arg, ContextInterface $ct * @return V1\ListTaskQueuePartitionsResponse * @throws ServiceClientException */ - public function ListTaskQueuePartitions(V1\ListTaskQueuePartitionsRequest $arg, ContextInterface $ctx = null) : V1\ListTaskQueuePartitionsResponse + public function ListTaskQueuePartitions(V1\ListTaskQueuePartitionsRequest $arg, ContextInterface $ctx = null): V1\ListTaskQueuePartitionsResponse { return $this->invoke("ListTaskQueuePartitions", $arg, $ctx); } @@ -793,7 +794,7 @@ public function ListTaskQueuePartitions(V1\ListTaskQueuePartitionsRequest $arg, * @return V1\CreateScheduleResponse * @throws ServiceClientException */ - public function CreateSchedule(V1\CreateScheduleRequest $arg, ContextInterface $ctx = null) : V1\CreateScheduleResponse + public function CreateSchedule(V1\CreateScheduleRequest $arg, ContextInterface $ctx = null): V1\CreateScheduleResponse { return $this->invoke("CreateSchedule", $arg, $ctx); } @@ -806,7 +807,7 @@ public function CreateSchedule(V1\CreateScheduleRequest $arg, ContextInterface $ * @return V1\DescribeScheduleResponse * @throws ServiceClientException */ - public function DescribeSchedule(V1\DescribeScheduleRequest $arg, ContextInterface $ctx = null) : V1\DescribeScheduleResponse + public function DescribeSchedule(V1\DescribeScheduleRequest $arg, ContextInterface $ctx = null): V1\DescribeScheduleResponse { return $this->invoke("DescribeSchedule", $arg, $ctx); } @@ -819,7 +820,7 @@ public function DescribeSchedule(V1\DescribeScheduleRequest $arg, ContextInterfa * @return V1\UpdateScheduleResponse * @throws ServiceClientException */ - public function UpdateSchedule(V1\UpdateScheduleRequest $arg, ContextInterface $ctx = null) : V1\UpdateScheduleResponse + public function UpdateSchedule(V1\UpdateScheduleRequest $arg, ContextInterface $ctx = null): V1\UpdateScheduleResponse { return $this->invoke("UpdateSchedule", $arg, $ctx); } @@ -832,7 +833,7 @@ public function UpdateSchedule(V1\UpdateScheduleRequest $arg, ContextInterface $ * @return V1\PatchScheduleResponse * @throws ServiceClientException */ - public function PatchSchedule(V1\PatchScheduleRequest $arg, ContextInterface $ctx = null) : V1\PatchScheduleResponse + public function PatchSchedule(V1\PatchScheduleRequest $arg, ContextInterface $ctx = null): V1\PatchScheduleResponse { return $this->invoke("PatchSchedule", $arg, $ctx); } @@ -845,7 +846,7 @@ public function PatchSchedule(V1\PatchScheduleRequest $arg, ContextInterface $ct * @return V1\ListScheduleMatchingTimesResponse * @throws ServiceClientException */ - public function ListScheduleMatchingTimes(V1\ListScheduleMatchingTimesRequest $arg, ContextInterface $ctx = null) : V1\ListScheduleMatchingTimesResponse + public function ListScheduleMatchingTimes(V1\ListScheduleMatchingTimesRequest $arg, ContextInterface $ctx = null): V1\ListScheduleMatchingTimesResponse { return $this->invoke("ListScheduleMatchingTimes", $arg, $ctx); } @@ -858,7 +859,7 @@ public function ListScheduleMatchingTimes(V1\ListScheduleMatchingTimesRequest $a * @return V1\DeleteScheduleResponse * @throws ServiceClientException */ - public function DeleteSchedule(V1\DeleteScheduleRequest $arg, ContextInterface $ctx = null) : V1\DeleteScheduleResponse + public function DeleteSchedule(V1\DeleteScheduleRequest $arg, ContextInterface $ctx = null): V1\DeleteScheduleResponse { return $this->invoke("DeleteSchedule", $arg, $ctx); } @@ -871,7 +872,7 @@ public function DeleteSchedule(V1\DeleteScheduleRequest $arg, ContextInterface $ * @return V1\ListSchedulesResponse * @throws ServiceClientException */ - public function ListSchedules(V1\ListSchedulesRequest $arg, ContextInterface $ctx = null) : V1\ListSchedulesResponse + public function ListSchedules(V1\ListSchedulesRequest $arg, ContextInterface $ctx = null): V1\ListSchedulesResponse { return $this->invoke("ListSchedules", $arg, $ctx); } @@ -906,7 +907,7 @@ public function ListSchedules(V1\ListSchedulesRequest $arg, ContextInterface $ct * @return V1\UpdateWorkerBuildIdCompatibilityResponse * @throws ServiceClientException */ - public function UpdateWorkerBuildIdCompatibility(V1\UpdateWorkerBuildIdCompatibilityRequest $arg, ContextInterface $ctx = null) : V1\UpdateWorkerBuildIdCompatibilityResponse + public function UpdateWorkerBuildIdCompatibility(V1\UpdateWorkerBuildIdCompatibilityRequest $arg, ContextInterface $ctx = null): V1\UpdateWorkerBuildIdCompatibilityResponse { return $this->invoke("UpdateWorkerBuildIdCompatibility", $arg, $ctx); } @@ -920,7 +921,7 @@ public function UpdateWorkerBuildIdCompatibility(V1\UpdateWorkerBuildIdCompatibi * @return V1\GetWorkerBuildIdCompatibilityResponse * @throws ServiceClientException */ - public function GetWorkerBuildIdCompatibility(V1\GetWorkerBuildIdCompatibilityRequest $arg, ContextInterface $ctx = null) : V1\GetWorkerBuildIdCompatibilityResponse + public function GetWorkerBuildIdCompatibility(V1\GetWorkerBuildIdCompatibilityRequest $arg, ContextInterface $ctx = null): V1\GetWorkerBuildIdCompatibilityResponse { return $this->invoke("GetWorkerBuildIdCompatibility", $arg, $ctx); } @@ -938,7 +939,7 @@ public function GetWorkerBuildIdCompatibility(V1\GetWorkerBuildIdCompatibilityRe * @return V1\UpdateWorkerVersioningRulesResponse * @throws ServiceClientException */ - public function UpdateWorkerVersioningRules(V1\UpdateWorkerVersioningRulesRequest $arg, ContextInterface $ctx = null) : V1\UpdateWorkerVersioningRulesResponse + public function UpdateWorkerVersioningRules(V1\UpdateWorkerVersioningRulesRequest $arg, ContextInterface $ctx = null): V1\UpdateWorkerVersioningRulesResponse { return $this->invoke("UpdateWorkerVersioningRules", $arg, $ctx); } @@ -953,7 +954,7 @@ public function UpdateWorkerVersioningRules(V1\UpdateWorkerVersioningRulesReques * @return V1\GetWorkerVersioningRulesResponse * @throws ServiceClientException */ - public function GetWorkerVersioningRules(V1\GetWorkerVersioningRulesRequest $arg, ContextInterface $ctx = null) : V1\GetWorkerVersioningRulesResponse + public function GetWorkerVersioningRules(V1\GetWorkerVersioningRulesRequest $arg, ContextInterface $ctx = null): V1\GetWorkerVersioningRulesResponse { return $this->invoke("GetWorkerVersioningRules", $arg, $ctx); } @@ -986,7 +987,7 @@ public function GetWorkerVersioningRules(V1\GetWorkerVersioningRulesRequest $arg * @return V1\GetWorkerTaskReachabilityResponse * @throws ServiceClientException */ - public function GetWorkerTaskReachability(V1\GetWorkerTaskReachabilityRequest $arg, ContextInterface $ctx = null) : V1\GetWorkerTaskReachabilityResponse + public function GetWorkerTaskReachability(V1\GetWorkerTaskReachabilityRequest $arg, ContextInterface $ctx = null): V1\GetWorkerTaskReachabilityResponse { return $this->invoke("GetWorkerTaskReachability", $arg, $ctx); } @@ -999,7 +1000,7 @@ public function GetWorkerTaskReachability(V1\GetWorkerTaskReachabilityRequest $a * @return V1\UpdateWorkflowExecutionResponse * @throws ServiceClientException */ - public function UpdateWorkflowExecution(V1\UpdateWorkflowExecutionRequest $arg, ContextInterface $ctx = null) : V1\UpdateWorkflowExecutionResponse + public function UpdateWorkflowExecution(V1\UpdateWorkflowExecutionRequest $arg, ContextInterface $ctx = null): V1\UpdateWorkflowExecutionResponse { return $this->invoke("UpdateWorkflowExecution", $arg, $ctx); } @@ -1019,7 +1020,7 @@ public function UpdateWorkflowExecution(V1\UpdateWorkflowExecutionRequest $arg, * @return V1\PollWorkflowExecutionUpdateResponse * @throws ServiceClientException */ - public function PollWorkflowExecutionUpdate(V1\PollWorkflowExecutionUpdateRequest $arg, ContextInterface $ctx = null) : V1\PollWorkflowExecutionUpdateResponse + public function PollWorkflowExecutionUpdate(V1\PollWorkflowExecutionUpdateRequest $arg, ContextInterface $ctx = null): V1\PollWorkflowExecutionUpdateResponse { return $this->invoke("PollWorkflowExecutionUpdate", $arg, $ctx); } @@ -1032,7 +1033,7 @@ public function PollWorkflowExecutionUpdate(V1\PollWorkflowExecutionUpdateReques * @return V1\StartBatchOperationResponse * @throws ServiceClientException */ - public function StartBatchOperation(V1\StartBatchOperationRequest $arg, ContextInterface $ctx = null) : V1\StartBatchOperationResponse + public function StartBatchOperation(V1\StartBatchOperationRequest $arg, ContextInterface $ctx = null): V1\StartBatchOperationResponse { return $this->invoke("StartBatchOperation", $arg, $ctx); } @@ -1045,7 +1046,7 @@ public function StartBatchOperation(V1\StartBatchOperationRequest $arg, ContextI * @return V1\StopBatchOperationResponse * @throws ServiceClientException */ - public function StopBatchOperation(V1\StopBatchOperationRequest $arg, ContextInterface $ctx = null) : V1\StopBatchOperationResponse + public function StopBatchOperation(V1\StopBatchOperationRequest $arg, ContextInterface $ctx = null): V1\StopBatchOperationResponse { return $this->invoke("StopBatchOperation", $arg, $ctx); } @@ -1058,7 +1059,7 @@ public function StopBatchOperation(V1\StopBatchOperationRequest $arg, ContextInt * @return V1\DescribeBatchOperationResponse * @throws ServiceClientException */ - public function DescribeBatchOperation(V1\DescribeBatchOperationRequest $arg, ContextInterface $ctx = null) : V1\DescribeBatchOperationResponse + public function DescribeBatchOperation(V1\DescribeBatchOperationRequest $arg, ContextInterface $ctx = null): V1\DescribeBatchOperationResponse { return $this->invoke("DescribeBatchOperation", $arg, $ctx); } @@ -1071,7 +1072,7 @@ public function DescribeBatchOperation(V1\DescribeBatchOperationRequest $arg, Co * @return V1\ListBatchOperationsResponse * @throws ServiceClientException */ - public function ListBatchOperations(V1\ListBatchOperationsRequest $arg, ContextInterface $ctx = null) : V1\ListBatchOperationsResponse + public function ListBatchOperations(V1\ListBatchOperationsRequest $arg, ContextInterface $ctx = null): V1\ListBatchOperationsResponse { return $this->invoke("ListBatchOperations", $arg, $ctx); } @@ -1086,7 +1087,7 @@ public function ListBatchOperations(V1\ListBatchOperationsRequest $arg, ContextI * @return V1\PollNexusTaskQueueResponse * @throws ServiceClientException */ - public function PollNexusTaskQueue(V1\PollNexusTaskQueueRequest $arg, ContextInterface $ctx = null) : V1\PollNexusTaskQueueResponse + public function PollNexusTaskQueue(V1\PollNexusTaskQueueRequest $arg, ContextInterface $ctx = null): V1\PollNexusTaskQueueResponse { return $this->invoke("PollNexusTaskQueue", $arg, $ctx); } @@ -1102,7 +1103,7 @@ public function PollNexusTaskQueue(V1\PollNexusTaskQueueRequest $arg, ContextInt * @return V1\RespondNexusTaskCompletedResponse * @throws ServiceClientException */ - public function RespondNexusTaskCompleted(V1\RespondNexusTaskCompletedRequest $arg, ContextInterface $ctx = null) : V1\RespondNexusTaskCompletedResponse + public function RespondNexusTaskCompleted(V1\RespondNexusTaskCompletedRequest $arg, ContextInterface $ctx = null): V1\RespondNexusTaskCompletedResponse { return $this->invoke("RespondNexusTaskCompleted", $arg, $ctx); } @@ -1118,9 +1119,8 @@ public function RespondNexusTaskCompleted(V1\RespondNexusTaskCompletedRequest $a * @return V1\RespondNexusTaskFailedResponse * @throws ServiceClientException */ - public function RespondNexusTaskFailed(V1\RespondNexusTaskFailedRequest $arg, ContextInterface $ctx = null) : V1\RespondNexusTaskFailedResponse + public function RespondNexusTaskFailed(V1\RespondNexusTaskFailedRequest $arg, ContextInterface $ctx = null): V1\RespondNexusTaskFailedResponse { return $this->invoke("RespondNexusTaskFailed", $arg, $ctx); } } - diff --git a/src/Client/Interceptor/SystemInfoInterceptor.php b/src/Client/Interceptor/SystemInfoInterceptor.php index 8abbd0369..85d06c19b 100644 --- a/src/Client/Interceptor/SystemInfoInterceptor.php +++ b/src/Client/Interceptor/SystemInfoInterceptor.php @@ -21,9 +21,8 @@ final class SystemInfoInterceptor implements GrpcClientInterceptor { public function __construct( - private readonly ServiceClientInterface $serviceClient - ) { - } + private readonly ServiceClientInterface $serviceClient, + ) {} /** * @param non-empty-string $method diff --git a/src/Client/Schedule/Action/ScheduleAction.php b/src/Client/Schedule/Action/ScheduleAction.php index 5839ec32e..d073a39b4 100644 --- a/src/Client/Schedule/Action/ScheduleAction.php +++ b/src/Client/Schedule/Action/ScheduleAction.php @@ -9,6 +9,4 @@ * * @see \Temporal\Api\Schedule\V1\ScheduleAction */ -abstract class ScheduleAction -{ -} +abstract class ScheduleAction {} diff --git a/src/Client/Schedule/Action/StartWorkflowAction.php b/src/Client/Schedule/Action/StartWorkflowAction.php index ad6e7d1a7..5dbd596f2 100644 --- a/src/Client/Schedule/Action/StartWorkflowAction.php +++ b/src/Client/Schedule/Action/StartWorkflowAction.php @@ -95,7 +95,9 @@ final class StartWorkflowAction extends ScheduleAction public readonly EncodedCollection $memo; /** - * Search attributes + * Search Attributes. + * + * For supported operations on different server versions see {@link https://docs.temporal.io/visibility} */ #[Marshal(name: 'search_attributes', type: EncodedCollectionType::class, of: SearchAttributes::class)] public readonly EncodedCollection $searchAttributes; diff --git a/src/Client/Schedule/BackfillPeriod.php b/src/Client/Schedule/BackfillPeriod.php index e799a52a4..53679f934 100644 --- a/src/Client/Schedule/BackfillPeriod.php +++ b/src/Client/Schedule/BackfillPeriod.php @@ -4,8 +4,6 @@ namespace Temporal\Client\Schedule; -use DateTimeImmutable; -use DateTimeInterface; use Temporal\Api\Schedule\V1\BackfillRequest; use Temporal\Client\Schedule\Policy\ScheduleOverlapPolicy; use Temporal\Internal\Support\DateTime; @@ -21,29 +19,28 @@ final class BackfillPeriod use CloneWith; /** - * @param DateTimeInterface $startTime Start of the range to evaluate schedule in. - * @param DateTimeInterface $endTime End of the range to evaluate schedule in. + * @param \DateTimeInterface $startTime Start of the range to evaluate schedule in. + * @param \DateTimeInterface $endTime End of the range to evaluate schedule in. */ private function __construct( - public readonly DateTimeInterface $startTime, - public readonly DateTimeInterface $endTime, + public readonly \DateTimeInterface $startTime, + public readonly \DateTimeInterface $endTime, public readonly ScheduleOverlapPolicy $overlapPolicy = ScheduleOverlapPolicy::Unspecified, - ) { - } + ) {} /** - * @param DateTimeInterface|string $startTime Start of the range to evaluate schedule in. - * @param DateTimeInterface|string $endTime End of the range to evaluate schedule in. + * @param \DateTimeInterface|string $startTime Start of the range to evaluate schedule in. + * @param \DateTimeInterface|string $endTime End of the range to evaluate schedule in. * @param ScheduleOverlapPolicy $overlapPolicy Policy for overlaps. */ public static function new( - DateTimeInterface|string $startTime, - DateTimeInterface|string $endTime, + \DateTimeInterface|string $startTime, + \DateTimeInterface|string $endTime, ScheduleOverlapPolicy $overlapPolicy = ScheduleOverlapPolicy::Unspecified, ): self { return new self( - DateTime::parse($startTime, class: DateTimeImmutable::class), - DateTime::parse($endTime, class: DateTimeImmutable::class), + DateTime::parse($startTime, class: \DateTimeImmutable::class), + DateTime::parse($endTime, class: \DateTimeImmutable::class), $overlapPolicy, ); } @@ -51,19 +48,19 @@ public static function new( /** * Start of the range to evaluate schedule in. */ - public function withStartTime(DateTimeInterface|string $dateTime): self + public function withStartTime(\DateTimeInterface|string $dateTime): self { /** @see self::$startTime */ - return $this->with('startTime', DateTime::parse($dateTime, class: DateTimeImmutable::class)); + return $this->with('startTime', DateTime::parse($dateTime, class: \DateTimeImmutable::class)); } /** * End of the range to evaluate schedule in. */ - public function withEndTime(DateTimeInterface|string $dateTime): self + public function withEndTime(\DateTimeInterface|string $dateTime): self { /** @see self::$endTime */ - return $this->with('endTime', DateTime::parse($dateTime, class: DateTimeImmutable::class)); + return $this->with('endTime', DateTime::parse($dateTime, class: \DateTimeImmutable::class)); } /** diff --git a/src/Client/Schedule/Info/ScheduleActionResult.php b/src/Client/Schedule/Info/ScheduleActionResult.php index 361357105..0623a82b8 100644 --- a/src/Client/Schedule/Info/ScheduleActionResult.php +++ b/src/Client/Schedule/Info/ScheduleActionResult.php @@ -4,7 +4,6 @@ namespace Temporal\Client\Schedule\Info; -use DateTimeImmutable; use Temporal\Client\Schedule\Action\StartWorkflowAction; use Temporal\Internal\Marshaller\Meta\Marshal; use Temporal\Workflow\WorkflowExecution; @@ -20,13 +19,13 @@ final class ScheduleActionResult * Time that the action should have been taken (according to the schedule, including jitter). */ #[Marshal(name: 'schedule_time')] - public readonly DateTimeImmutable $scheduleTime; + public readonly \DateTimeImmutable $scheduleTime; /** * Time that the action was taken (real time). */ #[Marshal(name: 'actual_time')] - public readonly DateTimeImmutable $actualTime; + public readonly \DateTimeImmutable $actualTime; /** * If action was {@see StartWorkflowAction}: @@ -37,7 +36,5 @@ final class ScheduleActionResult /** * The DTO is a result of a query, so it is not possible to create it manually. */ - private function __construct() - { - } + private function __construct() {} } diff --git a/src/Client/Schedule/Info/ScheduleDescription.php b/src/Client/Schedule/Info/ScheduleDescription.php index d1cf70649..d86f915f1 100644 --- a/src/Client/Schedule/Info/ScheduleDescription.php +++ b/src/Client/Schedule/Info/ScheduleDescription.php @@ -39,8 +39,8 @@ final class ScheduleDescription public readonly EncodedCollection $memo; /** - * Indexed info that can be used in query of List schedules APIs. - * The key and value type must be registered on Temporal server side. + * Additional indexed information used for search and visibility. + * The key and its value type are registered on Temporal server side * Use GetSearchAttributes API to get valid key and corresponding value type. * For supported operations on different server versions see {@link https://docs.temporal.io/visibility}. */ @@ -58,7 +58,5 @@ final class ScheduleDescription /** * @internal The DTO is a result of a query, so it is not possible to create it manually. */ - public function __construct() - { - } + public function __construct() {} } diff --git a/src/Client/Schedule/Info/ScheduleInfo.php b/src/Client/Schedule/Info/ScheduleInfo.php index 6603913b8..f717a5f73 100644 --- a/src/Client/Schedule/Info/ScheduleInfo.php +++ b/src/Client/Schedule/Info/ScheduleInfo.php @@ -4,7 +4,6 @@ namespace Temporal\Client\Schedule\Info; -use DateTimeImmutable; use Temporal\Internal\Marshaller\Meta\Marshal; use Temporal\Internal\Marshaller\Meta\MarshalArray; use Temporal\Workflow\WorkflowExecution; @@ -57,27 +56,25 @@ final class ScheduleInfo /** * Next 10 scheduled Action times. * - * @var DateTimeImmutable[] + * @var \DateTimeImmutable[] */ - #[MarshalArray(name: 'future_action_times', of: DateTimeImmutable::class)] + #[MarshalArray(name: 'future_action_times', of: \DateTimeImmutable::class)] public readonly array $nextActionTimes; /** * When the schedule was created. */ #[Marshal(name: 'create_time')] - public readonly DateTimeImmutable $createdAt; + public readonly \DateTimeImmutable $createdAt; /** * When a schedule was last updated. */ #[Marshal(name: 'update_time')] - public readonly ?DateTimeImmutable $lastUpdateAt; + public readonly ?\DateTimeImmutable $lastUpdateAt; /** * The DTO is a result of a query, so it is not possible to create it manually. */ - private function __construct() - { - } + private function __construct() {} } diff --git a/src/Client/Schedule/Info/ScheduleListEntry.php b/src/Client/Schedule/Info/ScheduleListEntry.php index 505935da3..0e388bf5f 100644 --- a/src/Client/Schedule/Info/ScheduleListEntry.php +++ b/src/Client/Schedule/Info/ScheduleListEntry.php @@ -41,7 +41,5 @@ final class ScheduleListEntry /** * @internal The DTO is a result of a query, so it is not possible to create it manually. */ - public function __construct() - { - } + public function __construct() {} } diff --git a/src/Client/Schedule/Info/ScheduleListInfo.php b/src/Client/Schedule/Info/ScheduleListInfo.php index ecb4a2cca..641dca227 100644 --- a/src/Client/Schedule/Info/ScheduleListInfo.php +++ b/src/Client/Schedule/Info/ScheduleListInfo.php @@ -45,7 +45,5 @@ final class ScheduleListInfo /** * The DTO is a result of a query, so it is not possible to create it manually. */ - private function __construct() - { - } + private function __construct() {} } diff --git a/src/Client/Schedule/ScheduleHandle.php b/src/Client/Schedule/ScheduleHandle.php index d6d9c097b..1e5cbaaea 100644 --- a/src/Client/Schedule/ScheduleHandle.php +++ b/src/Client/Schedule/ScheduleHandle.php @@ -4,11 +4,8 @@ namespace Temporal\Client\Schedule; -use ArrayIterator; -use Countable; -use DateTimeImmutable; -use DateTimeInterface; use Google\Protobuf\Timestamp; +use Temporal\Api\Common\V1\SearchAttributes; use Temporal\Api\Schedule\V1\BackfillRequest; use Temporal\Api\Schedule\V1\SchedulePatch; use Temporal\Api\Schedule\V1\TriggerImmediatelyRequest; @@ -22,13 +19,14 @@ use Temporal\Client\GRPC\ServiceClientInterface; use Temporal\Client\Schedule\Info\ScheduleDescription; use Temporal\Client\Schedule\Policy\ScheduleOverlapPolicy; +use Temporal\Client\Schedule\Update\ScheduleUpdate; +use Temporal\Client\Schedule\Update\ScheduleUpdateInput; use Temporal\Common\Uuid; use Temporal\DataConverter\DataConverterInterface; use Temporal\Exception\InvalidArgumentException; use Temporal\Internal\Mapper\ScheduleMapper; use Temporal\Internal\Marshaller\MarshallerInterface; use Temporal\Internal\Marshaller\ProtoToArrayConverter; -use Traversable; final class ScheduleHandle { @@ -57,28 +55,73 @@ public function getID(): string /** * Update the Schedule. * + * Examples: + * + * Add a search attribute to the schedule: + * ``` + * $handle->update(function (ScheduleUpdateInput $input): ScheduleUpdate { + * return ScheduleUpdate::new($input->description->schedule) + * ->withSearchAttributes($input->description->searchAttributes + * ->withValue('foo', 'bar'), + * ->withValue('bar', 42), + * ); + * }); + * ``` + * + * Pause a described schedule: + * ``` + * $description = $handle->describe(); + * $schedule = $description->schedule; + * $handle->update( + * $schedule + * ->withState($schedule->state->withPaused(true)), + * $description->conflictToken, + * ); + * ``` + * * NOTE: If two Update calls are made in parallel to the same Schedule there is the potential * for a race condition. Use $conflictToken to avoid this. * - * @param Schedule $schedule The new Schedule to update to. + * @param Schedule|\Closure(ScheduleUpdateInput): ScheduleUpdate $schedule The new Schedule to update to or + * a closure that will be passed the current ScheduleDescription and should return a ScheduleUpdate. * @param string|null $conflictToken Can be the value of {@see ScheduleDescription::$conflictToken}, * which will cause this request to fail if the schedule has been modified * between the {@see self::describe()} and this Update. * If missing, the schedule will be updated unconditionally. */ public function update( - Schedule $schedule, + Schedule|\Closure $schedule, ?string $conflictToken = null, ): void { - $mapper = new ScheduleMapper($this->converter, $this->marshaller); - $scheduleMessage = $mapper->toMessage($schedule); - $request = (new UpdateScheduleRequest()) ->setScheduleId($this->id) ->setNamespace($this->namespace) ->setConflictToken((string) $conflictToken) ->setIdentity($this->clientOptions->identity) - ->setSchedule($scheduleMessage); + ->setRequestId(Uuid::v4()); + + if ($schedule instanceof \Closure) { + $description = $this->describe(); + $update = $schedule(new ScheduleUpdateInput($description)); + $update instanceof ScheduleUpdate or throw new InvalidArgumentException( + 'Closure for the schedule update method must return a ScheduleUpdate.', + ); + + $schedule = $update->schedule; + + // Search attributes + if ($update->searchAttributes !== null) { + $update->searchAttributes->setDataConverter($this->converter); + $payloads = $update->searchAttributes->toPayloadArray(); + $encodedSa = (new SearchAttributes())->setIndexedFields($payloads); + $request->setSearchAttributes($encodedSa); + } + } + + $mapper = new ScheduleMapper($this->converter, $this->marshaller); + $scheduleMessage = $mapper->toMessage($schedule); + $request->setSchedule($scheduleMessage); + $this->client->UpdateSchedule($request); } @@ -102,12 +145,12 @@ public function describe(): ScheduleDescription /** * Lists matching times within a range. * - * @return Countable&Traversable + * @return \Countable&\Traversable */ public function listScheduleMatchingTimes( - DateTimeInterface $startTime, - DateTimeInterface $endTime, - ): Countable&Traversable { + \DateTimeInterface $startTime, + \DateTimeInterface $endTime, + ): \Countable&\Traversable { $request = (new ListScheduleMatchingTimesRequest()) ->setScheduleId($this->id) ->setNamespace($this->namespace) @@ -115,15 +158,15 @@ public function listScheduleMatchingTimes( ->setEndTime((new Timestamp())->setSeconds($endTime->getTimestamp())); $response = $this->client->ListScheduleMatchingTimes($request); - /** @var list $list */ + /** @var list<\DateTimeInterface> $list */ $list = []; foreach ($response->getStartTime() as $timestamp) { \assert($timestamp instanceof Timestamp); - $list[] = new \DateTimeImmutable('@' . $timestamp->getSeconds()); + $list[] = new \DateTimeImmutable("@{$timestamp->getSeconds()}"); } - return new ArrayIterator($list); + return new \ArrayIterator($list); } /** @@ -137,7 +180,7 @@ public function backfill(iterable $periods): void $backfill = []; foreach ($periods as $period) { $period instanceof BackfillPeriod or throw new InvalidArgumentException( - 'Backfill periods must be of type BackfillPeriod.' + 'Backfill periods must be of type BackfillPeriod.', ); $backfill[] = (new BackfillRequest()) diff --git a/src/Client/Schedule/ScheduleOptions.php b/src/Client/Schedule/ScheduleOptions.php index 447f79590..ffdbb0d26 100644 --- a/src/Client/Schedule/ScheduleOptions.php +++ b/src/Client/Schedule/ScheduleOptions.php @@ -17,7 +17,6 @@ final class ScheduleOptions use CloneWith; public readonly string $namespace; - public readonly bool $triggerImmediately; /** @@ -26,7 +25,6 @@ final class ScheduleOptions public readonly array $backfills; public readonly EncodedCollection $memo; - public readonly EncodedCollection $searchAttributes; private function __construct() diff --git a/src/Client/Schedule/Spec/CalendarSpec.php b/src/Client/Schedule/Spec/CalendarSpec.php index 702db808d..287188358 100644 --- a/src/Client/Schedule/Spec/CalendarSpec.php +++ b/src/Client/Schedule/Spec/CalendarSpec.php @@ -61,8 +61,7 @@ private function __construct( public readonly string $dayOfWeek, #[Marshal(name: 'comment')] public readonly string $comment, - ) { - } + ) {} /** * @param string $second Expression to match seconds. @@ -89,37 +88,37 @@ public static function new( public function withSecond(string|int $second): self { - return $this->with('second', (string)$second); + return $this->with('second', (string) $second); } public function withMinute(string|int $minute): self { - return $this->with('minute', (string)$minute); + return $this->with('minute', (string) $minute); } public function withHour(string|int $hour): self { - return $this->with('hour', (string)$hour); + return $this->with('hour', (string) $hour); } public function withDayOfMonth(string|int $dayOfMonth): self { - return $this->with('dayOfMonth', (string)$dayOfMonth); + return $this->with('dayOfMonth', (string) $dayOfMonth); } public function withMonth(string|int $month): self { - return $this->with('month', (string)$month); + return $this->with('month', (string) $month); } public function withYear(string|int $year): self { - return $this->with('year', (string)$year); + return $this->with('year', (string) $year); } public function withDayOfWeek(string|int $dayOfWeek): self { - return $this->with('dayOfWeek', (string)$dayOfWeek); + return $this->with('dayOfWeek', (string) $dayOfWeek); } public function withComment(string $comment): self diff --git a/src/Client/Schedule/Spec/IntervalSpec.php b/src/Client/Schedule/Spec/IntervalSpec.php index 8bade28ef..0a624e4b7 100644 --- a/src/Client/Schedule/Spec/IntervalSpec.php +++ b/src/Client/Schedule/Spec/IntervalSpec.php @@ -30,18 +30,16 @@ final class IntervalSpec private function __construct( #[Marshal(name: 'interval', of: Duration::class)] public readonly \DateInterval $interval, - #[Marshal(name: 'phase', of: Duration::class)] public readonly \DateInterval $phase, - ) { - } + ) {} public static function new(mixed $interval, mixed $phase = null): self { - assert(DateInterval::assert($interval)); + \assert(DateInterval::assert($interval)); $interval = DateInterval::parse($interval, DateInterval::FORMAT_SECONDS); - assert($phase === null or DateInterval::assert($phase)); + \assert($phase === null or DateInterval::assert($phase)); $phase = DateInterval::parse($phase ?? new \DateInterval('PT0S'), DateInterval::FORMAT_SECONDS); return new self($interval, $phase); @@ -49,7 +47,7 @@ public static function new(mixed $interval, mixed $phase = null): self public function withInterval(mixed $interval): self { - assert(DateInterval::assert($interval)); + \assert(DateInterval::assert($interval)); $interval = DateInterval::parse($interval, DateInterval::FORMAT_SECONDS); return $this->with('interval', $interval); @@ -57,7 +55,7 @@ public function withInterval(mixed $interval): self public function withPhase(mixed $phase): self { - assert(DateInterval::assert($phase)); + \assert(DateInterval::assert($phase)); $phase = DateInterval::parse($phase, DateInterval::FORMAT_SECONDS); return $this->with('phase', $phase); diff --git a/src/Client/Schedule/Spec/Range.php b/src/Client/Schedule/Spec/Range.php index b99a23e61..e09c8c76d 100644 --- a/src/Client/Schedule/Spec/Range.php +++ b/src/Client/Schedule/Spec/Range.php @@ -32,9 +32,8 @@ private function __construct( #[Marshal] public readonly int $end, #[Marshal] - public readonly int $step - ) { - } + public readonly int $step, + ) {} /** * @param int $start Start of range (inclusive). diff --git a/src/Client/Schedule/Spec/ScheduleSpec.php b/src/Client/Schedule/Spec/ScheduleSpec.php index 3c921f373..d1a688836 100644 --- a/src/Client/Schedule/Spec/ScheduleSpec.php +++ b/src/Client/Schedule/Spec/ScheduleSpec.php @@ -4,7 +4,6 @@ namespace Temporal\Client\Schedule\Spec; -use DateTimeInterface; use Google\Protobuf\Duration; use Google\Protobuf\Timestamp; use Temporal\Internal\Marshaller\Meta\Marshal; @@ -94,13 +93,13 @@ final class ScheduleSpec * (Together, startTime and endTime make an inclusive interval.) */ #[MarshalDateTime(name: 'start_time', to: Timestamp::class, nullable: true)] - public readonly ?DateTimeInterface $startTime; + public readonly ?\DateTimeInterface $startTime; /** * If endTime is set, any timestamps after endTime will be skipped. */ #[MarshalDateTime(name: 'end_time', to: Timestamp::class, nullable: true)] - public readonly ?DateTimeInterface $endTime; + public readonly ?\DateTimeInterface $endTime; /** * All timestamps will be incremented by a random value from 0 to this @@ -165,7 +164,7 @@ public function withAddedStructuredCalendar(StructuredCalendarSpec $structuredCa public function withCronStringList(\Stringable|string ...$cron): self { /** @see self::$cronStringList */ - return $this->with('cronStringList', \array_map(static fn($item) => (string)$item, $cron)); + return $this->with('cronStringList', \array_map(static fn($item) => (string) $item, $cron)); } /** @@ -178,7 +177,7 @@ public function withCronStringList(\Stringable|string ...$cron): self public function withAddedCronString(\Stringable|string $cron): self { $value = $this->cronStringList; - $value[] = (string)$cron; + $value[] = (string) $cron; /** @see self::$cronStringList */ return $this->with('cronStringList', $value); @@ -290,7 +289,7 @@ public function withAddedExcludeStructuredCalendar(StructuredCalendarSpec $struc * If startTime is set, any timestamps before startTime will be skipped. * (Together, startTime and endTime make an inclusive interval.) */ - public function withStartTime(DateTimeInterface|string|null $dateTime): self + public function withStartTime(\DateTimeInterface|string|null $dateTime): self { /** @see self::$startTime */ return $this->with('startTime', $dateTime === null ? null : DateTime::parse($dateTime)); @@ -299,7 +298,7 @@ public function withStartTime(DateTimeInterface|string|null $dateTime): self /** * If endTime is set, any timestamps after endTime will be skipped. */ - public function withEndTime(DateTimeInterface|string|null $dateTime): self + public function withEndTime(\DateTimeInterface|string|null $dateTime): self { /** @see self::$endTime */ return $this->with('endTime', $dateTime === null ? null : DateTime::parse($dateTime)); diff --git a/src/Client/Schedule/Update/ScheduleUpdate.php b/src/Client/Schedule/Update/ScheduleUpdate.php new file mode 100644 index 000000000..afb66be6a --- /dev/null +++ b/src/Client/Schedule/Update/ScheduleUpdate.php @@ -0,0 +1,59 @@ +searchAttributes = null; + } + + /** + * @param Schedule $schedule Schedule to replace the existing schedule with. + */ + public static function new(Schedule $schedule): self + { + return new self($schedule); + } + + public function withSchedule(Schedule $schedule): self + { + /** @see self::$schedule */ + return $this->with('schedule', $schedule); + } + + /** + * @param ?EncodedCollection $searchAttributes Search attributes to replace the existing search attributes with. + * If null, it will not change the existing search attributes. + */ + public function withSearchAttributes(?EncodedCollection $searchAttributes = null): self + { + /** @see self::$searchAttributes */ + return $this->with('searchAttributes', $searchAttributes); + } +} diff --git a/src/Client/Schedule/Update/ScheduleUpdateInput.php b/src/Client/Schedule/Update/ScheduleUpdateInput.php new file mode 100644 index 000000000..37a11216b --- /dev/null +++ b/src/Client/Schedule/Update/ScheduleUpdateInput.php @@ -0,0 +1,24 @@ +client = $serviceClient; $this->clientOptions = $options ?? new ClientOptions(); @@ -68,7 +68,7 @@ public function __construct( public static function create( ServiceClientInterface $serviceClient, ClientOptions $options = null, - DataConverterInterface $converter = null + DataConverterInterface $converter = null, ): ScheduleClientInterface { return new self($serviceClient, $options, $converter); } @@ -94,7 +94,7 @@ public function createSchedule( $backfillRequests = []; foreach ($options->backfills as $period) { $period instanceof BackfillPeriod or throw new \InvalidArgumentException( - 'Backfill periods must be of type BackfillPeriod.' + 'Backfill periods must be of type BackfillPeriod.', ); $backfillRequests[] = (new BackfillRequest()) @@ -118,7 +118,7 @@ public function createSchedule( ->setInitialPatch($initialPatch) ->setMemo((new Memo())->setFields($options->memo->toPayloadArray())) ->setSearchAttributes( - (new SearchAttributes())->setIndexedFields($options->searchAttributes->toPayloadArray()) + (new SearchAttributes())->setIndexedFields($options->searchAttributes->toPayloadArray()), ); $this->client->CreateSchedule($request); diff --git a/src/Client/ScheduleClientInterface.php b/src/Client/ScheduleClientInterface.php index 7addb4392..00a999bdc 100644 --- a/src/Client/ScheduleClientInterface.php +++ b/src/Client/ScheduleClientInterface.php @@ -49,5 +49,5 @@ public function getHandle(string $scheduleID, ?string $namespace = null): Schedu * * @return Paginator */ - public function listSchedules(?string $namespace = null, int $pageSize = 0,): Paginator; + public function listSchedules(?string $namespace = null, int $pageSize = 0): Paginator; } diff --git a/src/Client/ServerCapabilities.php b/src/Client/ServerCapabilities.php index 6ecb85d58..08867c5bc 100644 --- a/src/Client/ServerCapabilities.php +++ b/src/Client/ServerCapabilities.php @@ -15,7 +15,5 @@ /** * @deprecated use {@see \Temporal\Client\Common\ServerCapabilities} instead. Will be removed in the future. */ - class ServerCapabilities - { - } + class ServerCapabilities {} } diff --git a/src/Client/Update/UpdateHandle.php b/src/Client/Update/UpdateHandle.php index 7d107ad7c..4183e5314 100644 --- a/src/Client/Update/UpdateHandle.php +++ b/src/Client/Update/UpdateHandle.php @@ -36,9 +36,7 @@ public function __construct( private readonly mixed $resultType, private readonly string $updateId, private ValuesInterface|WorkflowUpdateException|null $result, - ) { - } - + ) {} /** * Gets the workflow execution this update request was sent to. @@ -110,12 +108,12 @@ private function fetchResult(int|float|null $timeout = null): void ->setUpdateRef( (new \Temporal\Api\Update\V1\UpdateRef()) ->setUpdateId($this->getId()) - ->setWorkflowExecution($this->getExecution()->toProtoWorkflowExecution()) + ->setWorkflowExecution($this->getExecution()->toProtoWorkflowExecution()), ) ->setNamespace($this->clientOptions->namespace) ->setIdentity($this->clientOptions->identity) ->setWaitPolicy( - (new \Temporal\Api\Update\V1\WaitPolicy())->setLifecycleStage(LifecycleStage::StageCompleted->value) + (new \Temporal\Api\Update\V1\WaitPolicy())->setLifecycleStage(LifecycleStage::StageCompleted->value), ); try { diff --git a/src/Client/Workflow/CountWorkflowExecutions.php b/src/Client/Workflow/CountWorkflowExecutions.php index f3b5ff8a1..9c987abd8 100644 --- a/src/Client/Workflow/CountWorkflowExecutions.php +++ b/src/Client/Workflow/CountWorkflowExecutions.php @@ -15,8 +15,7 @@ final class CountWorkflowExecutions { public function __construct( public readonly int $count, - ) { - } + ) {} } \class_alias(CountWorkflowExecutions::class, 'Temporal\Client\CountWorkflowExecutions'); diff --git a/src/Client/Workflow/WorkflowExecutionDescription.php b/src/Client/Workflow/WorkflowExecutionDescription.php index 82dfe1011..0fb8ff5d1 100644 --- a/src/Client/Workflow/WorkflowExecutionDescription.php +++ b/src/Client/Workflow/WorkflowExecutionDescription.php @@ -20,6 +20,5 @@ final class WorkflowExecutionDescription */ public function __construct( public readonly WorkflowExecutionInfo $info, - ) { - } + ) {} } diff --git a/src/Client/Workflow/WorkflowExecutionHistory.php b/src/Client/Workflow/WorkflowExecutionHistory.php index e3d835adf..e3665ee7f 100644 --- a/src/Client/Workflow/WorkflowExecutionHistory.php +++ b/src/Client/Workflow/WorkflowExecutionHistory.php @@ -4,14 +4,12 @@ namespace Temporal\Client\Workflow; -use Generator; use IteratorAggregate; use Temporal\Api\History\V1\History; use Temporal\Api\History\V1\HistoryEvent; use Temporal\Api\Workflowservice\V1\GetWorkflowExecutionHistoryResponse; use Temporal\Client\Common\Paginator; use Temporal\Testing\Replay\WorkflowReplayer; -use Traversable; /** * Provides a wrapper with convenience methods over raw protobuf object representing @@ -22,22 +20,21 @@ * @implements IteratorAggregate * @internal */ -final class WorkflowExecutionHistory implements IteratorAggregate +final class WorkflowExecutionHistory implements \IteratorAggregate { /** * @param Paginator $paginator */ public function __construct( - private readonly Paginator $paginator - ) { - } + private readonly Paginator $paginator, + ) {} /** * Returns an iterator of HistoryEvent objects. * - * @return Generator + * @return \Generator */ - public function getEvents(): Generator + public function getEvents(): \Generator { foreach ($this->paginator as $response) { $history = $response->getHistory(); @@ -52,9 +49,9 @@ public function getEvents(): Generator } /** - * @return Traversable + * @return \Traversable */ - public function getIterator(): Traversable + public function getIterator(): \Traversable { return $this->getEvents(); } diff --git a/src/Client/WorkflowClient.php b/src/Client/WorkflowClient.php index 3af0f616b..0065915f6 100644 --- a/src/Client/WorkflowClient.php +++ b/src/Client/WorkflowClient.php @@ -12,7 +12,6 @@ namespace Temporal\Client; use Doctrine\Common\Annotations\Reader; -use Generator; use Spiral\Attributes\AnnotationReader; use Spiral\Attributes\AttributeReader; use Spiral\Attributes\Composite\SelectiveReader; @@ -61,6 +60,7 @@ class WorkflowClient implements WorkflowClientInterface private DataConverterInterface $converter; private ?WorkflowStarter $starter = null; private WorkflowReader $reader; + /** @var Pipeline */ private Pipeline $interceptorPipeline; @@ -84,11 +84,6 @@ public function __construct( $this->reader = new WorkflowReader($this->createReader()); } - public function __clone() - { - $this->starter = null; - } - /** * @param ServiceClientInterface $serviceClient * @param ClientOptions|null $options @@ -140,7 +135,7 @@ public function start($workflow, ...$args): WorkflowRunInterface if ($workflowType === null) { throw new InvalidArgumentException( - \sprintf('Unable to start untyped workflow without given workflowType') + \sprintf('Unable to start untyped workflow without given workflowType'), ); } @@ -173,7 +168,7 @@ public function startWithSignal( $workflow, string $signal, array $signalArgs = [], - array $startArgs = [] + array $startArgs = [], ): WorkflowRunInterface { if ($workflow instanceof WorkflowProxy && !$workflow->hasHandler()) { throw new InvalidArgumentException('Unable to start workflow without workflow handler'); @@ -203,7 +198,7 @@ public function startWithSignal( if ($workflowType === null) { throw new InvalidArgumentException( - \sprintf('Unable to start untyped workflow without given workflowType') + \sprintf('Unable to start untyped workflow without given workflowType'), ); } @@ -239,7 +234,7 @@ public function newWorkflowStub( return new WorkflowProxy( $this, $this->newUntypedWorkflowStub($workflow->getID(), $options), - $workflow + $workflow, ); } @@ -272,7 +267,7 @@ public function newRunningWorkflowStub(string $class, string $workflowID, ?strin return new WorkflowProxy( $this, $this->newUntypedRunningWorkflowStub($workflowID, $runID, $workflow->getID()), - $workflow + $workflow, ); } @@ -282,7 +277,7 @@ public function newRunningWorkflowStub(string $class, string $workflowID, ?strin public function newUntypedRunningWorkflowStub( string $workflowID, ?string $runID = null, - ?string $workflowType = null + ?string $workflowType = null, ): WorkflowStubInterface { $untyped = new WorkflowStub( $this->client, @@ -323,7 +318,7 @@ public function listWorkflowExecutions( ->setQuery($query); $mapper = new WorkflowExecutionInfoMapper($this->converter); - $loader = function (ListWorkflowExecutionsRequest $request) use ($mapper): Generator { + $loader = function (ListWorkflowExecutionsRequest $request) use ($mapper): \Generator { do { $response = $this->client->ListWorkflowExecutions($request); $nextPageToken = $response->getNextPageToken(); @@ -357,7 +352,7 @@ public function countWorkflowExecutions( ); return new CountWorkflowExecutions( - count: (int)$response->getCount(), + count: (int) $response->getCount(), ); } @@ -380,11 +375,12 @@ public function getWorkflowHistory( ->setHistoryEventFilterType($historyEventFilterType) ->setSkipArchival($skipArchival) ->setMaximumPageSize($pageSize) - ->setExecution((new \Temporal\Api\Common\V1\WorkflowExecution()) - ->setWorkflowId($execution->getID()) - ->setRunId( - $execution->getRunID() ?? throw new InvalidArgumentException('Execution Run ID is required.'), - ), + ->setExecution( + (new \Temporal\Api\Common\V1\WorkflowExecution()) + ->setWorkflowId($execution->getID()) + ->setRunId( + $execution->getRunID() ?? throw new InvalidArgumentException('Execution Run ID is required.'), + ), ); $loader = function (GetWorkflowExecutionHistoryRequest $request): \Generator { @@ -403,6 +399,11 @@ public function getWorkflowHistory( return new WorkflowExecutionHistory($paginator); } + public function __clone() + { + $this->starter = null; + } + /** * @return ReaderInterface */ diff --git a/src/Client/WorkflowClientInterface.php b/src/Client/WorkflowClientInterface.php index 04abb139d..2a95eae8c 100644 --- a/src/Client/WorkflowClientInterface.php +++ b/src/Client/WorkflowClientInterface.php @@ -50,7 +50,7 @@ public function startWithSignal( $workflow, string $signal, array $signalArgs = [], - array $startArgs = [] + array $startArgs = [], ): WorkflowRunInterface; /** @@ -105,7 +105,7 @@ public function newUntypedWorkflowStub( public function newRunningWorkflowStub( string $class, string $workflowID, - ?string $runID = null + ?string $runID = null, ): object; /** @@ -119,7 +119,7 @@ public function newRunningWorkflowStub( public function newUntypedRunningWorkflowStub( string $workflowID, ?string $runID = null, - ?string $workflowType = null + ?string $workflowType = null, ): WorkflowStubInterface; /** diff --git a/src/Client/WorkflowOptions.php b/src/Client/WorkflowOptions.php index 0bd973de5..9716c1f07 100644 --- a/src/Client/WorkflowOptions.php +++ b/src/Client/WorkflowOptions.php @@ -12,7 +12,6 @@ namespace Temporal\Client; use Carbon\CarbonInterval; -use JetBrains\PhpStorm\ExpectedValues; use JetBrains\PhpStorm\Pure; use Temporal\Api\Common\V1\Memo; use Temporal\Api\Common\V1\SearchAttributes; @@ -254,9 +253,9 @@ public function withEagerStart(bool $value = true): self #[Pure] public function withWorkflowExecutionTimeout($timeout): self { - assert(DateInterval::assert($timeout)); + \assert(DateInterval::assert($timeout)); $timeout = DateInterval::parse($timeout, DateInterval::FORMAT_SECONDS); - assert($timeout->totalMicroseconds >= 0); + \assert($timeout->totalMicroseconds >= 0); $self = clone $this; $self->workflowExecutionTimeout = $timeout; @@ -276,9 +275,9 @@ public function withWorkflowExecutionTimeout($timeout): self #[Pure] public function withWorkflowRunTimeout($timeout): self { - assert(DateInterval::assert($timeout)); + \assert(DateInterval::assert($timeout)); $timeout = DateInterval::parse($timeout, DateInterval::FORMAT_SECONDS); - assert($timeout->totalMicroseconds >= 0); + \assert($timeout->totalMicroseconds >= 0); $self = clone $this; $self->workflowRunTimeout = $timeout; @@ -297,9 +296,9 @@ public function withWorkflowRunTimeout($timeout): self #[Pure] public function withWorkflowTaskTimeout($timeout): self { - assert(DateInterval::assert($timeout)); + \assert(DateInterval::assert($timeout)); $timeout = DateInterval::parse($timeout, DateInterval::FORMAT_SECONDS); - assert($timeout->totalMicroseconds >= 0 && $timeout->totalSeconds <= 60); + \assert($timeout->totalMicroseconds >= 0 && $timeout->totalSeconds <= 60); $self = clone $this; $self->workflowTaskTimeout = $timeout; @@ -320,7 +319,7 @@ public function withWorkflowTaskTimeout($timeout): self #[Pure] public function withWorkflowStartDelay($delay): self { - assert(DateInterval::assert($delay)); + \assert(DateInterval::assert($delay)); $delay = DateInterval::parse($delay, DateInterval::FORMAT_SECONDS); $self = clone $this; diff --git a/src/Common/CronSchedule.php b/src/Common/CronSchedule.php index 7025268cb..956930a59 100644 --- a/src/Common/CronSchedule.php +++ b/src/Common/CronSchedule.php @@ -68,6 +68,6 @@ public function __construct(string $interval) */ public function __toString(): string { - return (string)$this->interval; + return (string) $this->interval; } } diff --git a/src/Common/MethodRetry.php b/src/Common/MethodRetry.php index 8ae356270..658b454dd 100644 --- a/src/Common/MethodRetry.php +++ b/src/Common/MethodRetry.php @@ -45,7 +45,7 @@ public function __construct( $maximumInterval = self::DEFAULT_MAXIMUM_INTERVAL, int $maximumAttempts = self::DEFAULT_MAXIMUM_ATTEMPTS, float $backoffCoefficient = self::DEFAULT_BACKOFF_COEFFICIENT, - array $nonRetryableExceptions = self::DEFAULT_NON_RETRYABLE_EXCEPTIONS + array $nonRetryableExceptions = self::DEFAULT_NON_RETRYABLE_EXCEPTIONS, ) { parent::__construct(); diff --git a/src/Common/RetryOptions.php b/src/Common/RetryOptions.php index c59b92a50..1a4b65269 100644 --- a/src/Common/RetryOptions.php +++ b/src/Common/RetryOptions.php @@ -132,7 +132,7 @@ public function mergeWith(MethodRetry $retry = null): self #[Pure] public function withInitialInterval($interval): self { - assert(DateInterval::assert($interval) || $interval === null); + \assert(DateInterval::assert($interval) || $interval === null); $self = clone $this; $self->initialInterval = DateInterval::parseOrNull($interval, DateInterval::FORMAT_SECONDS); @@ -148,7 +148,7 @@ public function withInitialInterval($interval): self #[Pure] public function withBackoffCoefficient(float $coefficient): self { - assert($coefficient >= 1.0); + \assert($coefficient >= 1.0); $self = clone $this; $self->backoffCoefficient = $coefficient; @@ -164,7 +164,7 @@ public function withBackoffCoefficient(float $coefficient): self #[Pure] public function withMaximumInterval($interval): self { - assert(DateInterval::assert($interval) || $interval === null); + \assert(DateInterval::assert($interval) || $interval === null); $self = clone $this; $self->maximumInterval = DateInterval::parseOrNull($interval, DateInterval::FORMAT_SECONDS); @@ -180,7 +180,7 @@ public function withMaximumInterval($interval): self #[Pure] public function withMaximumAttempts(int $attempts): self { - assert($attempts >= 0); + \assert($attempts >= 0); $self = clone $this; $self->maximumAttempts = $attempts; @@ -197,7 +197,7 @@ public function withMaximumAttempts(int $attempts): self #[Pure] public function withNonRetryableExceptions(array $exceptions): self { - assert(Assert::valuesSubclassOfOrSameClass($exceptions, \Throwable::class)); + \assert(Assert::valuesSubclassOfOrSameClass($exceptions, \Throwable::class)); $self = clone $this; $self->nonRetryableExceptions = $exceptions; diff --git a/src/Common/TaskQueue/TaskQueue.php b/src/Common/TaskQueue/TaskQueue.php index d1b332347..da340acec 100644 --- a/src/Common/TaskQueue/TaskQueue.php +++ b/src/Common/TaskQueue/TaskQueue.php @@ -18,11 +18,6 @@ final class TaskQueue implements \Stringable #[Marshal] public readonly string $name; - public function __toString(): string - { - return $this->name; - } - private function __construct(string $name) { $this->name = $name; @@ -38,4 +33,9 @@ public function withName(string $name): self /** @see self::$name */ return $this->with('name', $name); } + + public function __toString(): string + { + return $this->name; + } } diff --git a/src/Common/Uuid.php b/src/Common/Uuid.php index a45c4fc8a..17f7612da 100644 --- a/src/Common/Uuid.php +++ b/src/Common/Uuid.php @@ -63,10 +63,10 @@ private static function bytes(): string { $bytes = \random_bytes(16); - $timeHi = (int)\unpack('n*', \substr($bytes, 6, 2))[1]; + $timeHi = (int) \unpack('n*', \substr($bytes, 6, 2))[1]; $timeHiAndVersion = \pack('n*', self::version($timeHi, 4)); - $clockSeqHi = (int)\unpack('n*', \substr($bytes, 8, 2))[1]; + $clockSeqHi = (int) \unpack('n*', \substr($bytes, 8, 2))[1]; $clockSeqHiAndReserved = \pack('n*', self::variant($clockSeqHi)); $bytes = \substr_replace($bytes, $timeHiAndVersion, 6, 2); diff --git a/src/Common/WorkerVersionStamp.php b/src/Common/WorkerVersionStamp.php index 7acf1cab1..d34365a13 100644 --- a/src/Common/WorkerVersionStamp.php +++ b/src/Common/WorkerVersionStamp.php @@ -20,6 +20,5 @@ public function __construct( /** @deprecated that field was removed {@link https://github.com/temporalio/api/pull/393} */ public string $bundleId = '', public bool $useVersioning = false, - ) { - } + ) {} } diff --git a/src/Common/WorkflowIdConflictPolicy.php b/src/Common/WorkflowIdConflictPolicy.php index f0f84cd19..98a122bb5 100644 --- a/src/Common/WorkflowIdConflictPolicy.php +++ b/src/Common/WorkflowIdConflictPolicy.php @@ -36,5 +36,4 @@ enum WorkflowIdConflictPolicy: int * Terminate the running workflow before starting a new one. */ case TerminateExisting = 3; - } diff --git a/src/DataConverter/Bytes.php b/src/DataConverter/Bytes.php index fd12da565..fa8b86006 100644 --- a/src/DataConverter/Bytes.php +++ b/src/DataConverter/Bytes.php @@ -27,25 +27,25 @@ public function __construct(string $data) } /** - * @return string + * @return int */ - public function __toString() + public function getSize(): int { - return $this->data; + return \strlen($this->data); } /** - * @return int + * @return string */ - public function getSize(): int + public function getData(): string { - return strlen($this->data); + return $this->data; } /** * @return string */ - public function getData(): string + public function __toString() { return $this->data; } diff --git a/src/DataConverter/DataConverter.php b/src/DataConverter/DataConverter.php index 37c302137..6ba436aaa 100644 --- a/src/DataConverter/DataConverter.php +++ b/src/DataConverter/DataConverter.php @@ -31,6 +31,20 @@ public function __construct(PayloadConverterInterface ...$converter) } } + /** + * @return DataConverterInterface + */ + public static function createDefault(): DataConverterInterface + { + return new DataConverter( + new NullConverter(), + new BinaryConverter(), + new ProtoJsonConverter(), + new ProtoConverter(), + new JsonConverter(), + ); + } + /** * {@inheritDoc} */ @@ -42,12 +56,12 @@ public function fromPayload(Payload $payload, $type) $encoding = $meta[EncodingKeys::METADATA_ENCODING_KEY]; if (!isset($this->converters[$encoding])) { - throw new DataConverterException(sprintf('Undefined payload encoding %s', $encoding)); + throw new DataConverterException(\sprintf('Undefined payload encoding %s', $encoding)); } $type = Type::create($type); if (\in_array($type->getName(), [Type::TYPE_VOID, Type::TYPE_NULL, Type::TYPE_FALSE, Type::TYPE_TRUE], true)) { - return match($type->getName()) { + return match ($type->getName()) { Type::TYPE_VOID, Type::TYPE_NULL => null, Type::TYPE_TRUE => true, Type::TYPE_FALSE => false, @@ -74,21 +88,7 @@ public function toPayload($value): Payload } throw new DataConverterException( - \sprintf('Unable to convert value of type %s to Payload', \get_debug_type($value)) - ); - } - - /** - * @return DataConverterInterface - */ - public static function createDefault(): DataConverterInterface - { - return new DataConverter( - new NullConverter(), - new BinaryConverter(), - new ProtoJsonConverter(), - new ProtoConverter(), - new JsonConverter() + \sprintf('Unable to convert value of type %s to Payload', \get_debug_type($value)), ); } } diff --git a/src/DataConverter/EncodedCollection.php b/src/DataConverter/EncodedCollection.php index ab2cc48c3..b245ffd4f 100644 --- a/src/DataConverter/EncodedCollection.php +++ b/src/DataConverter/EncodedCollection.php @@ -11,20 +11,18 @@ namespace Temporal\DataConverter; -use ArrayAccess; -use Countable; -use IteratorAggregate; use Temporal\Api\Common\V1\Payload; -use Traversable; /** - * @psalm-type TPayloadsCollection = Traversable&ArrayAccess&Countable + * Assoc collection of typed values. + * * @psalm-type TKey = array-key * @psalm-type TValue = mixed + * @psalm-type TPayloadsCollection = \Traversable&\ArrayAccess&\Countable * - * @implements IteratorAggregate + * @implements \IteratorAggregate */ -class EncodedCollection implements IteratorAggregate, Countable +class EncodedCollection implements \IteratorAggregate, \Countable { /** * @var DataConverterInterface|null @@ -34,22 +32,52 @@ class EncodedCollection implements IteratorAggregate, Countable /** * @var TPayloadsCollection|null */ - private ?ArrayAccess $payloads = null; + private ?\ArrayAccess $payloads = null; + /** @var array */ private array $values = []; /** * Cannot be constructed directly. */ - private function __construct() + final private function __construct() {} + + public static function empty(): static { + $ev = new static(); + $ev->values = []; + + return $ev; } - public function __clone() + /** + * @param iterable $values + */ + public static function fromValues(iterable $values, ?DataConverterInterface $dataConverter = null): static { - if ($this->payloads !== null) { - $this->payloads = clone $this->payloads; + $ev = new static(); + foreach ($values as $key => $value) { + $ev->values[$key] = $value; } + $ev->converter = $dataConverter; + + return $ev; + } + + /** + * @param array|TPayloadsCollection $payloads + */ + public static function fromPayloadCollection( + array|\ArrayAccess $payloads, + DataConverterInterface $dataConverter, + ): static { + $ev = new static(); + $ev->payloads = \is_array($payloads) + ? new \ArrayIterator($payloads) + : $payloads; + $ev->converter = $dataConverter; + + return $ev; } public function count(): int @@ -65,8 +93,6 @@ public function isEmpty(): bool /** * @param array-key $name * @param Type|string|null $type - * - * @return mixed */ public function getValue(int|string $name, mixed $type = null): mixed { @@ -102,12 +128,11 @@ public function getValues(): array return $result; } - public function getIterator(): Traversable + public function getIterator(): \Traversable { yield from $this->values; - if ($this->payloads !== null) { - $this->converter !== null or $this->payloads->count() === 0 - or throw new \LogicException('DataConverter is not set.'); + if ($this->payloads !== null && $this->payloads->count() > 0) { + $this->converter === null and throw new \LogicException('DataConverter is not set.'); foreach ($this->payloads as $key => $payload) { yield $key => $this->converter->fromPayload($payload, null); @@ -128,9 +153,7 @@ public function toPayloadArray(): array return $data; } - if ($this->converter === null) { - throw new \LogicException('DataConverter is not set.'); - } + $this->converter === null and throw new \LogicException('DataConverter is not set.'); foreach ($this->values as $key => $value) { $data[$key] = $this->converter->toPayload($value); @@ -139,6 +162,10 @@ public function toPayloadArray(): array return $data; } + /** + * @param TKey $name + * @param TValue $value + */ public function withValue(int|string $name, mixed $value): static { $clone = clone $this; @@ -155,58 +182,15 @@ public function withValue(int|string $name, mixed $value): static return $clone; } - /** - * @param DataConverterInterface $converter - */ public function setDataConverter(DataConverterInterface $converter): void { $this->converter = $converter; } - /** - * @return static - */ - public static function empty(): static - { - $ev = new static(); - $ev->values = []; - - return $ev; - } - - /** - * @param iterable $values - * @param DataConverterInterface|null $dataConverter - * - * @return static - */ - public static function fromValues(iterable $values, ?DataConverterInterface $dataConverter = null): static + public function __clone() { - $ev = new static(); - foreach ($values as $key => $value) { - $ev->values[$key] = $value; + if ($this->payloads !== null) { + $this->payloads = clone $this->payloads; } - $ev->converter = $dataConverter; - - return $ev; - } - - /** - * @param iterable $payloads - * @param DataConverterInterface $dataConverter - * - * @return EncodedCollection - */ - public static function fromPayloadCollection( - array|ArrayAccess $payloads, - DataConverterInterface $dataConverter, - ): static { - $ev = new static(); - $ev->payloads = \is_array($payloads) - ? new \ArrayIterator($payloads) - : $payloads; - $ev->converter = $dataConverter; - - return $ev; } } diff --git a/src/DataConverter/EncodedValues.php b/src/DataConverter/EncodedValues.php index 981583560..f11662189 100644 --- a/src/DataConverter/EncodedValues.php +++ b/src/DataConverter/EncodedValues.php @@ -20,33 +20,33 @@ use Traversable; /** + * List of typed values. + * * @psalm-type TPayloadsCollection = Traversable&ArrayAccess&Countable - * @psalm-type TKey = array-key + * @psalm-type TKey = int * @psalm-type TValue = string */ class EncodedValues implements ValuesInterface { - /** - * @var DataConverterInterface|null - */ - private ?DataConverterInterface $converter = null; - /** * @var TPayloadsCollection|null */ - protected ?Traversable $payloads = null; + protected ?\Traversable $payloads = null; /** * @var array|null */ protected ?array $values = null; + /** + * @var DataConverterInterface|null + */ + private ?DataConverterInterface $converter = null; + /** * Can not be constructed directly. */ - private function __construct() - { - } + private function __construct() {} /** * @return static @@ -70,11 +70,6 @@ public static function fromPayloads(Payloads $payloads, DataConverterInterface $ return static::fromPayloadCollection($payloads->getPayloads(), $dataConverter); } - public function toPayloads(): Payloads - { - return new Payloads(['payloads' => $this->toProtoCollection()]); - } - /** * @param DataConverterInterface $converter * @param ValuesInterface $values @@ -91,7 +86,7 @@ public static function sliceValues( ): ValuesInterface { $payloads = $values->toPayloads(); $newPayloads = new Payloads(); - $newPayloads->setPayloads(array_slice(iterator_to_array($payloads->getPayloads()), $offset, $length)); + $newPayloads->setPayloads(\array_slice(\iterator_to_array($payloads->getPayloads()), $offset, $length)); return self::fromPayloads($newPayloads, $converter); } @@ -107,7 +102,7 @@ public static function sliceValues( public static function decodePromise(PromiseInterface $promise, $type = null): PromiseInterface { return $promise->then( - function ($value) use ($type) { + static function (mixed $value) use ($type) { if (!$value instanceof ValuesInterface || $value instanceof \Throwable) { return $value; } @@ -117,28 +112,69 @@ function ($value) use ($type) { ); } + /** + * @param array $values + * @param DataConverterInterface|null $dataConverter + * + * @return static + */ + public static function fromValues(array $values, DataConverterInterface $dataConverter = null): static + { + $ev = new static(); + $ev->values = \array_values($values); + $ev->converter = $dataConverter; + + return $ev; + } + + /** + * @param TPayloadsCollection $payloads + * @param ?DataConverterInterface $dataConverter + * + * @return static + */ + public static function fromPayloadCollection( + \Traversable $payloads, + ?DataConverterInterface $dataConverter = null, + ): static { + $ev = new static(); + $ev->payloads = $payloads; + $ev->converter = $dataConverter; + + return $ev; + } + + public function toPayloads(): Payloads + { + return new Payloads(['payloads' => $this->toProtoCollection()]); + } + public function getValue(int|string $index, $type = null): mixed { if (\is_array($this->values) && \array_key_exists($index, $this->values)) { return $this->values[$index]; } + $count = $this->count(); // External SDKs might return an empty array with metadata, alias to null // Most likely this is a void type - if ($index === 0 && $this->count() === 0 && $this->isVoidType($type)) { + if ($index === 0 && $count === 0 && $this->isVoidType($type)) { return null; } - if ($this->converter === null) { - throw new \LogicException('DataConverter is not set'); - } + $count > $index or throw new \OutOfBoundsException("Index {$index} is out of bounds."); + $this->converter === null and throw new \LogicException('DataConverter is not set.'); - return $this->converter->fromPayload($this->payloads[$index], $type); + \assert($this->payloads !== null); + return $this->converter->fromPayload( + $this->payloads[$index], + $type, + ); } public function getValues(): array { - $result = $this->values; + $result = (array) $this->values; if (empty($this->payloads)) { return $result; @@ -161,38 +197,6 @@ public function setDataConverter(DataConverterInterface $converter): void $this->converter = $converter; } - /** - * @param array $values - * @param DataConverterInterface|null $dataConverter - * - * @return static - */ - public static function fromValues(array $values, DataConverterInterface $dataConverter = null): static - { - $ev = new static(); - $ev->values = \array_values($values); - $ev->converter = $dataConverter; - - return $ev; - } - - /** - * @param TPayloadsCollection $payloads - * @param ?DataConverterInterface $dataConverter - * - * @return static - */ - public static function fromPayloadCollection( - Traversable $payloads, - ?DataConverterInterface $dataConverter = null, - ): static { - $ev = new static(); - $ev->payloads = $payloads; - $ev->converter = $dataConverter; - - return $ev; - } - /** * @return int<0, max> */ diff --git a/src/DataConverter/EncodingKeys.php b/src/DataConverter/EncodingKeys.php index 02ed976e2..a858c1818 100644 --- a/src/DataConverter/EncodingKeys.php +++ b/src/DataConverter/EncodingKeys.php @@ -15,11 +15,9 @@ final class EncodingKeys { public const METADATA_ENCODING_KEY = 'encoding'; public const METADATA_MESSAGE_TYPE = 'messageType'; - public const METADATA_ENCODING_NULL = 'binary/null'; public const METADATA_ENCODING_RAW = 'binary/plain'; public const METADATA_ENCODING_JSON = 'json/plain'; - public const METADATA_ENCODING_PROTOBUF_JSON = 'json/protobuf'; public const METADATA_ENCODING_PROTOBUF = 'binary/protobuf'; } diff --git a/src/DataConverter/JsonConverter.php b/src/DataConverter/JsonConverter.php index 3fb1c44c8..dbff0ca30 100644 --- a/src/DataConverter/JsonConverter.php +++ b/src/DataConverter/JsonConverter.php @@ -63,10 +63,10 @@ public function getEncodingType(): string public function toPayload($value): ?Payload { if (\is_object($value)) { - $value = match(true) { + $value = match (true) { $value instanceof \stdClass => $value, $value instanceof UuidInterface => $value->toString(), - default => $this->marshaller->marshal($value) + default => $this->marshaller->marshal($value), }; } @@ -90,13 +90,13 @@ public function fromPayload(Payload $payload, Type $type) $payload->getData(), $type->getName() === Type::TYPE_ARRAY, 512, - self::JSON_FLAGS + self::JSON_FLAGS, ); } catch (\Throwable $e) { throw new DataConverterException($e->getMessage(), $e->getCode(), $e); } - if ($data === null && $type->allowsNull()){ + if ($data === null && $type->allowsNull()) { return null; } @@ -178,6 +178,26 @@ public function fromPayload(Payload $payload, Type $type) throw $this->errorInvalidTypeName($type); } + /** + * @return MarshallerInterface + */ + private static function createDefaultMarshaller(): MarshallerInterface + { + return new Marshaller(new AttributeMapperFactory(self::createDefaultReader())); + } + + /** + * @return ReaderInterface + */ + private static function createDefaultReader(): ReaderInterface + { + if (\interface_exists(Reader::class)) { + return new SelectiveReader([new AnnotationReader(), new AttributeReader()]); + } + + return new AttributeReader(); + } + /** * @param object|array $context * @return array @@ -185,7 +205,7 @@ public function fromPayload(Payload $payload, Type $type) private function toHashMap($context): array { if (\is_object($context)) { - $context = (array)$context; + $context = (array) $context; } foreach ($context as $key => $value) { @@ -225,26 +245,6 @@ private function errorInvalidType(Type $type, $data): DataConverterException return new DataConverterException($message); } - /** - * @return MarshallerInterface - */ - private static function createDefaultMarshaller(): MarshallerInterface - { - return new Marshaller(new AttributeMapperFactory(self::createDefaultReader())); - } - - /** - * @return ReaderInterface - */ - private static function createDefaultReader(): ReaderInterface - { - if (\interface_exists(Reader::class)) { - return new SelectiveReader([new AnnotationReader(), new AttributeReader()]); - } - - return new AttributeReader(); - } - /** * @template T of object * diff --git a/src/DataConverter/ProtoConverter.php b/src/DataConverter/ProtoConverter.php index 75018e6f0..27720d73e 100644 --- a/src/DataConverter/ProtoConverter.php +++ b/src/DataConverter/ProtoConverter.php @@ -11,7 +11,6 @@ namespace Temporal\DataConverter; -use Google\Protobuf\DescriptorPool; use Google\Protobuf\Internal\Message; use Temporal\Api\Common\V1\Payload; use Temporal\Exception\DataConverterException; diff --git a/src/DataConverter/ProtoJsonConverter.php b/src/DataConverter/ProtoJsonConverter.php index 92bdf13e4..4474a0f8d 100644 --- a/src/DataConverter/ProtoJsonConverter.php +++ b/src/DataConverter/ProtoJsonConverter.php @@ -11,7 +11,6 @@ namespace Temporal\DataConverter; -use Google\Protobuf\DescriptorPool; use Google\Protobuf\Internal\Message; use Temporal\Api\Common\V1\Payload; use Temporal\Exception\DataConverterException; diff --git a/src/DataConverter/Type.php b/src/DataConverter/Type.php index caeb9ee0d..fa20e3e0e 100644 --- a/src/DataConverter/Type.php +++ b/src/DataConverter/Type.php @@ -55,38 +55,6 @@ public static function arrayOf(string $class): self return new self($class, null, true); } - /** - * @return string - */ - public function getName(): string - { - return $this->name; - } - - /** - * @return bool - */ - public function allowsNull(): bool - { - return $this->allowsNull; - } - - /** - * @return bool - */ - public function isUntyped(): bool - { - return $this->name === self::TYPE_ANY; - } - - /** - * @return bool - */ - public function isClass(): bool - { - return \class_exists($this->name); - } - /** * @param \ReflectionClass $class * @param bool $nullable @@ -147,6 +115,38 @@ public static function create($type): Type } } + /** + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * @return bool + */ + public function allowsNull(): bool + { + return $this->allowsNull; + } + + /** + * @return bool + */ + public function isUntyped(): bool + { + return $this->name === self::TYPE_ANY; + } + + /** + * @return bool + */ + public function isClass(): bool + { + return \class_exists($this->name); + } + public function isArrayOf(): bool { return $this->isArrayOf; diff --git a/src/Exception/Client/ActivityCanceledException.php b/src/Exception/Client/ActivityCanceledException.php index 2cdf4f843..97cf19688 100644 --- a/src/Exception/Client/ActivityCanceledException.php +++ b/src/Exception/Client/ActivityCanceledException.php @@ -11,6 +11,4 @@ namespace Temporal\Exception\Client; -class ActivityCanceledException extends ActivityCompletionException -{ -} +class ActivityCanceledException extends ActivityCompletionException {} diff --git a/src/Exception/Client/ActivityCompletionException.php b/src/Exception/Client/ActivityCompletionException.php index 1759a7f5d..83858559e 100644 --- a/src/Exception/Client/ActivityCompletionException.php +++ b/src/Exception/Client/ActivityCompletionException.php @@ -21,38 +21,6 @@ class ActivityCompletionException extends TemporalException private ?string $activityType = null; private ?string $activityId = null; - /** - * @return string|null - */ - public function getWorkflowId(): ?string - { - return $this->workflowId; - } - - /** - * @return string|null - */ - public function getRunId(): ?string - { - return $this->runId; - } - - /** - * @return string|null - */ - public function getActivityType(): ?string - { - return $this->activityType; - } - - /** - * @return string|null - */ - public function getActivityId(): ?string - { - return $this->activityId; - } - /** * @param \Throwable $e * @return static @@ -62,7 +30,7 @@ public static function fromPrevious(\Throwable $e): self return new static( $e->getMessage(), $e->getCode(), - $e + $e, ); } @@ -93,10 +61,10 @@ public static function fromActivityInfo(ActivityInfo $info, \Throwable $e = null 'runId' => $info->workflowExecution->getRunID(), 'activityId' => $info->id, 'activityType' => $info->type->name, - ] + ], ), $e === null ? 0 : $e->getCode(), - $e + $e, ); $e->activityId = $info->id; @@ -106,4 +74,36 @@ public static function fromActivityInfo(ActivityInfo $info, \Throwable $e = null return $e; } + + /** + * @return string|null + */ + public function getWorkflowId(): ?string + { + return $this->workflowId; + } + + /** + * @return string|null + */ + public function getRunId(): ?string + { + return $this->runId; + } + + /** + * @return string|null + */ + public function getActivityType(): ?string + { + return $this->activityType; + } + + /** + * @return string|null + */ + public function getActivityId(): ?string + { + return $this->activityId; + } } diff --git a/src/Exception/Client/ActivityCompletionFailureException.php b/src/Exception/Client/ActivityCompletionFailureException.php index a1f44a66d..534d02320 100644 --- a/src/Exception/Client/ActivityCompletionFailureException.php +++ b/src/Exception/Client/ActivityCompletionFailureException.php @@ -11,6 +11,4 @@ namespace Temporal\Exception\Client; -class ActivityCompletionFailureException extends ActivityCompletionException -{ -} +class ActivityCompletionFailureException extends ActivityCompletionException {} diff --git a/src/Exception/Client/ActivityNotExistsException.php b/src/Exception/Client/ActivityNotExistsException.php index 96379f5be..a5313a8bc 100644 --- a/src/Exception/Client/ActivityNotExistsException.php +++ b/src/Exception/Client/ActivityNotExistsException.php @@ -11,6 +11,4 @@ namespace Temporal\Exception\Client; -class ActivityNotExistsException extends ActivityCompletionException -{ -} +class ActivityNotExistsException extends ActivityCompletionException {} diff --git a/src/Exception/Client/ActivityWorkerShutdownException.php b/src/Exception/Client/ActivityWorkerShutdownException.php index 3d182178f..5eddeeb3b 100644 --- a/src/Exception/Client/ActivityWorkerShutdownException.php +++ b/src/Exception/Client/ActivityWorkerShutdownException.php @@ -11,6 +11,4 @@ namespace Temporal\Exception\Client; -class ActivityWorkerShutdownException extends ActivityCompletionException -{ -} +class ActivityWorkerShutdownException extends ActivityCompletionException {} diff --git a/src/Exception/Client/WorkflowException.php b/src/Exception/Client/WorkflowException.php index 4de40ab80..dfb72d8b9 100644 --- a/src/Exception/Client/WorkflowException.php +++ b/src/Exception/Client/WorkflowException.php @@ -36,7 +36,7 @@ public function __construct( ?string $message, WorkflowExecution $execution, string $workflowType = null, - \Throwable $previous = null + \Throwable $previous = null, ) { parent::__construct( self::buildMessage( @@ -44,15 +44,29 @@ public function __construct( 'message' => $message, 'runId' => $execution->getRunID(), 'workflowType' => $workflowType, - ] + ], ), 0, - $previous + $previous, ); $this->execution = $execution; $this->type = $workflowType; } + /** + * @param WorkflowExecution $execution + * @param string|null $workflowType + * @param \Throwable|null $previous + * @return WorkflowException + */ + public static function withoutMessage( + WorkflowExecution $execution, + string $workflowType = null, + \Throwable $previous = null, + ): WorkflowException { + return new static(null, $execution, $workflowType, $previous); + } + /** * @return WorkflowExecution */ @@ -68,18 +82,4 @@ public function getWorkflowType(): ?string { return $this->type; } - - /** - * @param WorkflowExecution $execution - * @param string|null $workflowType - * @param \Throwable|null $previous - * @return WorkflowException - */ - public static function withoutMessage( - WorkflowExecution $execution, - string $workflowType = null, - \Throwable $previous = null - ): WorkflowException { - return new static(null, $execution, $workflowType, $previous); - } } diff --git a/src/Exception/Client/WorkflowExecutionAlreadyStartedException.php b/src/Exception/Client/WorkflowExecutionAlreadyStartedException.php index 54ca8b4d3..f6d6b976b 100644 --- a/src/Exception/Client/WorkflowExecutionAlreadyStartedException.php +++ b/src/Exception/Client/WorkflowExecutionAlreadyStartedException.php @@ -23,7 +23,7 @@ class WorkflowExecutionAlreadyStartedException extends WorkflowException public function __construct( WorkflowExecution $execution, string $type = null, - \Throwable $previous = null + \Throwable $previous = null, ) { parent::__construct(null, $execution, $type, $previous); } diff --git a/src/Exception/Client/WorkflowFailedException.php b/src/Exception/Client/WorkflowFailedException.php index 11200ecb8..99595b1e5 100644 --- a/src/Exception/Client/WorkflowFailedException.php +++ b/src/Exception/Client/WorkflowFailedException.php @@ -29,7 +29,7 @@ public function __construct( ?string $type, int $lastWorkflowTaskCompletedEventId, int $retryState, - \Throwable $previous = null + \Throwable $previous = null, ) { parent::__construct(null, $execution, $type, $previous); @@ -40,7 +40,7 @@ public function __construct( 'workflowType' => $type, 'workflowTaskCompletedEventId' => $lastWorkflowTaskCompletedEventId, 'retryState' => $retryState, - ] + ], ); $this->lastWorkflowTaskCompletedEventId = $lastWorkflowTaskCompletedEventId; diff --git a/src/Exception/Client/WorkflowNotFoundException.php b/src/Exception/Client/WorkflowNotFoundException.php index dca9f8f9e..58c056c70 100644 --- a/src/Exception/Client/WorkflowNotFoundException.php +++ b/src/Exception/Client/WorkflowNotFoundException.php @@ -11,6 +11,4 @@ namespace Temporal\Exception\Client; -class WorkflowNotFoundException extends WorkflowException -{ -} +class WorkflowNotFoundException extends WorkflowException {} diff --git a/src/Exception/Client/WorkflowQueryException.php b/src/Exception/Client/WorkflowQueryException.php index 178667380..42014911d 100644 --- a/src/Exception/Client/WorkflowQueryException.php +++ b/src/Exception/Client/WorkflowQueryException.php @@ -11,6 +11,4 @@ namespace Temporal\Exception\Client; -class WorkflowQueryException extends WorkflowException -{ -} +class WorkflowQueryException extends WorkflowException {} diff --git a/src/Exception/Client/WorkflowQueryRejectedException.php b/src/Exception/Client/WorkflowQueryRejectedException.php index 6081abe95..0adc6474e 100644 --- a/src/Exception/Client/WorkflowQueryRejectedException.php +++ b/src/Exception/Client/WorkflowQueryRejectedException.php @@ -19,6 +19,7 @@ class WorkflowQueryRejectedException extends WorkflowQueryException * @var int */ private int $queryRejectCondition; + private int $workflowExecutionStatus; /** @@ -33,7 +34,7 @@ public function __construct( string $type, int $queryRejectCondition, int $workflowExecutionStatus, - \Throwable $previous = null + \Throwable $previous = null, ) { parent::__construct(null, $execution, $type, $previous); $this->queryRejectCondition = $queryRejectCondition; diff --git a/src/Exception/Client/WorkflowServiceException.php b/src/Exception/Client/WorkflowServiceException.php index b67d10c6e..20d350609 100644 --- a/src/Exception/Client/WorkflowServiceException.php +++ b/src/Exception/Client/WorkflowServiceException.php @@ -11,6 +11,4 @@ namespace Temporal\Exception\Client; -class WorkflowServiceException extends WorkflowException -{ -} +class WorkflowServiceException extends WorkflowException {} diff --git a/src/Exception/Client/WorkflowUpdateException.php b/src/Exception/Client/WorkflowUpdateException.php index f18d14cdc..d269e6d4d 100644 --- a/src/Exception/Client/WorkflowUpdateException.php +++ b/src/Exception/Client/WorkflowUpdateException.php @@ -21,7 +21,7 @@ public function __construct( ?string $workflowType, private readonly string $updateId, private readonly string $updateName, - ?\Throwable $previous = null + ?\Throwable $previous = null, ) { parent::__construct($message, $execution, $workflowType, $previous); } diff --git a/src/Exception/Client/WorkflowUpdateRPCTimeoutOrCanceledException.php b/src/Exception/Client/WorkflowUpdateRPCTimeoutOrCanceledException.php index ef15a159e..0958c66bb 100644 --- a/src/Exception/Client/WorkflowUpdateRPCTimeoutOrCanceledException.php +++ b/src/Exception/Client/WorkflowUpdateRPCTimeoutOrCanceledException.php @@ -17,7 +17,8 @@ * @note this is not related to any general concept of timing out or cancelling a running update, * this is only related to the client call itself. */ -class WorkflowUpdateRPCTimeoutOrCanceledException extends TimeoutException { +class WorkflowUpdateRPCTimeoutOrCanceledException extends TimeoutException +{ private function __construct(string $message = '', int $code = 0, ?\Throwable $previous = null) { parent::__construct($message, $code, $previous); diff --git a/src/Exception/DataConverterException.php b/src/Exception/DataConverterException.php index 126d54509..593f6fdae 100644 --- a/src/Exception/DataConverterException.php +++ b/src/Exception/DataConverterException.php @@ -11,6 +11,4 @@ namespace Temporal\Exception; -class DataConverterException extends TemporalException -{ -} +class DataConverterException extends TemporalException {} diff --git a/src/Exception/DestructMemorizedInstanceException.php b/src/Exception/DestructMemorizedInstanceException.php index 7ed5e08c3..3dff191d5 100644 --- a/src/Exception/DestructMemorizedInstanceException.php +++ b/src/Exception/DestructMemorizedInstanceException.php @@ -17,6 +17,4 @@ * * @internal */ -class DestructMemorizedInstanceException extends TemporalException -{ -} +class DestructMemorizedInstanceException extends TemporalException {} diff --git a/src/Exception/ExceptionInterceptor.php b/src/Exception/ExceptionInterceptor.php index 4eb08aff8..771730c64 100644 --- a/src/Exception/ExceptionInterceptor.php +++ b/src/Exception/ExceptionInterceptor.php @@ -30,6 +30,14 @@ public function __construct(array $retryableErrors) $this->retryableErrors = $retryableErrors; } + /** + * @return static + */ + public static function createDefault(): self + { + return new self([\Error::class]); + } + /** * @param \Throwable $e * @return bool @@ -44,12 +52,4 @@ public function isRetryable(\Throwable $e): bool return false; } - - /** - * @return static - */ - public static function createDefault(): self - { - return new self([\Error::class]); - } } diff --git a/src/Exception/FailedCancellationException.php b/src/Exception/FailedCancellationException.php index 3992b7b7c..2d41dab3a 100644 --- a/src/Exception/FailedCancellationException.php +++ b/src/Exception/FailedCancellationException.php @@ -11,6 +11,4 @@ namespace Temporal\Exception; -class FailedCancellationException extends TemporalException -{ -} +class FailedCancellationException extends TemporalException {} diff --git a/src/Exception/Failure/ActivityFailure.php b/src/Exception/Failure/ActivityFailure.php index a76906965..9037c8cb4 100644 --- a/src/Exception/Failure/ActivityFailure.php +++ b/src/Exception/Failure/ActivityFailure.php @@ -36,7 +36,7 @@ public function __construct( string $activityId, int $retryState, string $identity, - \Throwable $previous = null + \Throwable $previous = null, ) { parent::__construct( self::buildMessage( @@ -47,10 +47,10 @@ public function __construct( 'activityId' => $activityId, 'identity' => $identity, 'retryState' => $retryState, - ] + ], ), null, - $previous + $previous, ); $this->scheduledEventId = $scheduledEventId; diff --git a/src/Exception/Failure/ApplicationFailure.php b/src/Exception/Failure/ApplicationFailure.php index f781ff887..7e82c9d22 100644 --- a/src/Exception/Failure/ApplicationFailure.php +++ b/src/Exception/Failure/ApplicationFailure.php @@ -50,20 +50,22 @@ class ApplicationFailure extends TemporalFailure * @param string $message * @param string $type * @param bool $nonRetryable - * @param ValuesInterface|null $details + * @param ValuesInterface|null $details Optional details about the failure. * @param \Throwable|null $previous + * @param \DateInterval|null $nextRetryDelay Delay before the next retry attempt. */ public function __construct( string $message, string $type, bool $nonRetryable, ValuesInterface $details = null, - \Throwable $previous = null + \Throwable $previous = null, + private ?\DateInterval $nextRetryDelay = null, ) { parent::__construct( - self::buildMessage(compact('message', 'type', 'nonRetryable')), + self::buildMessage(\compact('message', 'type', 'nonRetryable')), $message, - $previous + $previous, ); $this->type = $type; @@ -87,6 +89,11 @@ public function getDetails(): ValuesInterface return $this->details; } + public function getNextRetryDelay(): ?\DateInterval + { + return $this->nextRetryDelay; + } + /** * @return bool */ @@ -110,4 +117,9 @@ public function setDataConverter(DataConverterInterface $converter): void { $this->details->setDataConverter($converter); } + + public function setNextRetryDelay(?\DateInterval $nextRetryDelay): void + { + $this->nextRetryDelay = $nextRetryDelay; + } } diff --git a/src/Exception/Failure/ChildWorkflowFailure.php b/src/Exception/Failure/ChildWorkflowFailure.php index 9480c0639..70e1630fe 100644 --- a/src/Exception/Failure/ChildWorkflowFailure.php +++ b/src/Exception/Failure/ChildWorkflowFailure.php @@ -38,7 +38,7 @@ public function __construct( WorkflowExecution $execution, string $namespace, int $retryState, - \Throwable $previous = null + \Throwable $previous = null, ) { parent::__construct( self::buildMessage( @@ -50,10 +50,10 @@ public function __construct( 'startedEventId' => $startedEventId, 'namespace' => $namespace, 'retryState' => $retryState, - ] + ], ), null, - $previous + $previous, ); $this->initiatedEventId = $initiatedEventId; diff --git a/src/Exception/Failure/FailureConverter.php b/src/Exception/Failure/FailureConverter.php index 099f23851..faf941b5c 100644 --- a/src/Exception/Failure/FailureConverter.php +++ b/src/Exception/Failure/FailureConverter.php @@ -26,6 +26,7 @@ use Temporal\DataConverter\DataConverterInterface; use Temporal\DataConverter\EncodedValues; use Temporal\Exception\Client\ActivityCanceledException; +use Temporal\Internal\Support\DateInterval; final class FailureConverter { @@ -86,6 +87,10 @@ public static function mapExceptionToFailure(\Throwable $e, DataConverterInterfa $info->setType($e->getType()); $info->setNonRetryable($e->isNonRetryable()); + // Set Next Retry Delay + $nextRetry = DateInterval::toDuration($e->getNextRetryDelay()); + $nextRetry === null or $info->setNextRetryDelay($nextRetry); + if (!$e->getDetails()->isEmpty()) { $info->setDetails($e->getDetails()->toPayloads()); } @@ -126,7 +131,7 @@ public static function mapExceptionToFailure(\Throwable $e, DataConverterInterfa $info ->setActivityId($e->getActivityId()) ->setActivityType(new ActivityType([ - 'name' => $e->getActivityType() + 'name' => $e->getActivityType(), ])) ->setIdentity($e->getIdentity()) ->setRetryState($e->getRetryState()) @@ -144,7 +149,7 @@ public static function mapExceptionToFailure(\Throwable $e, DataConverterInterfa ->setNamespace($e->getNamespace()) ->setRetryState($e->getRetryState()) ->setWorkflowType(new WorkflowType([ - 'name' => $e->getWorkflowType() + 'name' => $e->getWorkflowType(), ])) ->setWorkflowExecution(new WorkflowExecution([ 'workflow_id' => $e->getExecution()->getID(), @@ -192,22 +197,24 @@ private static function createFailureException(Failure $failure, DataConverterIn switch (true) { case $failure->hasApplicationFailureInfo(): $info = $failure->getApplicationFailureInfo(); + \assert($info instanceof ApplicationFailureInfo); $details = $info->hasDetails() ? EncodedValues::fromPayloads($info->getDetails(), $converter) - : EncodedValues::empty() - ; + : EncodedValues::empty(); return new ApplicationFailure( $failure->getMessage(), $info->getType(), $info->getNonRetryable(), $details, - $previous + $previous, + DateInterval::parseOrNull($info->getNextRetryDelay()), ); case $failure->hasTimeoutFailureInfo(): $info = $failure->getTimeoutFailureInfo(); + \assert($info instanceof TimeoutFailureInfo); $details = $info->hasLastHeartbeatDetails() ? EncodedValues::fromPayloads($info->getLastHeartbeatDetails(), $converter) @@ -218,6 +225,7 @@ private static function createFailureException(Failure $failure, DataConverterIn case $failure->hasCanceledFailureInfo(): $info = $failure->getCanceledFailureInfo(); + \assert($info instanceof CanceledFailureInfo); $details = $info->hasDetails() ? EncodedValues::fromPayloads($info->getDetails(), $converter) @@ -231,25 +239,26 @@ private static function createFailureException(Failure $failure, DataConverterIn case $failure->hasServerFailureInfo(): $info = $failure->getServerFailureInfo(); + \assert($info instanceof ServerFailureInfo); return new ServerFailure($failure->getMessage(), $info->getNonRetryable(), $previous); case $failure->hasResetWorkflowFailureInfo(): $info = $failure->getResetWorkflowFailureInfo(); $details = $info->hasLastHeartbeatDetails() ? EncodedValues::fromPayloads($info->getLastHeartbeatDetails(), $converter) - : EncodedValues::empty() - ; + : EncodedValues::empty(); return new ApplicationFailure( $failure->getMessage(), 'ResetWorkflow', false, $details, - $previous + $previous, ); case $failure->hasActivityFailureInfo(): $info = $failure->getActivityFailureInfo(); + \assert($info instanceof ActivityFailureInfo); return new ActivityFailure( $info->getScheduledEventId(), @@ -258,12 +267,13 @@ private static function createFailureException(Failure $failure, DataConverterIn $info->getActivityId(), $info->getRetryState(), $info->getIdentity(), - $previous + $previous, ); case $failure->hasChildWorkflowExecutionFailureInfo(): $info = $failure->getChildWorkflowExecutionFailureInfo(); $execution = $info->getWorkflowExecution(); + \assert($execution instanceof WorkflowExecution); return new ChildWorkflowFailure( $info->getInitiatedEventId(), @@ -275,7 +285,7 @@ private static function createFailureException(Failure $failure, DataConverterIn ), $info->getNamespace(), $info->getRetryState(), - $previous + $previous, ); default: @@ -297,7 +307,7 @@ private static function generateStackTraceString(\Throwable $e, bool $skipIntern */ $frames = $e->getTrace(); - $numPad = \strlen((string)(\count($frames) - 1)) + 2; + $numPad = \strlen((string) (\count($frames) - 1)) + 2; // Skipped frames $internals = []; $isFirst = true; @@ -359,18 +369,19 @@ private static function renderTraceAttributes(array $args): string $result = []; foreach ($args as $arg) { - $result[] = match(true) { + $result[] = match (true) { $arg => 'true', $arg === false => 'false', $arg === null => 'null', - \is_array($arg) => 'array(' . count($arg) . ')', + \is_array($arg) => 'array(' . \count($arg) . ')', \is_object($arg) => \get_class($arg), - \is_string($arg) => (string)\json_encode( + \is_string($arg) => (string) \json_encode( \strlen($arg) > 50 ? \substr($arg, 0, 50) . '...' - : $arg, JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE + : $arg, + JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE, ), - \is_scalar($arg) => (string)$arg, + \is_scalar($arg) => (string) $arg, default => \get_debug_type($arg), }; } diff --git a/src/Exception/Failure/ServerFailure.php b/src/Exception/Failure/ServerFailure.php index 6c751f0f0..c7a297177 100644 --- a/src/Exception/Failure/ServerFailure.php +++ b/src/Exception/Failure/ServerFailure.php @@ -25,7 +25,7 @@ public function __construct(string $message, bool $nonRetryable, \Throwable $pre parent::__construct( $message, $message, - $previous + $previous, ); $this->nonRetryable = $nonRetryable; } diff --git a/src/Exception/Failure/TemporalFailure.php b/src/Exception/Failure/TemporalFailure.php index 7851ec918..97d20bd8f 100644 --- a/src/Exception/Failure/TemporalFailure.php +++ b/src/Exception/Failure/TemporalFailure.php @@ -47,18 +47,6 @@ public function __construct(string $message, string $originalMessage = null, \Th $this->originalMessage = $originalMessage ?? ''; } - /** - * @return string - */ - public function __toString(): string - { - if ($this->hasOriginalStackTrace()) { - return (string)$this->getOriginalStackTrace(); - } - - return parent::__toString(); - } - /** * @return Failure|null */ @@ -93,7 +81,8 @@ public function setOriginalStackTrace(string $stackTrace): void } /** - * @return bool + * @psalm-assert-if-true non-empty-string $this->originalStackTrace + * @psalm-assert-if-false null $this->originalStackTrace */ public function hasOriginalStackTrace(): bool { @@ -116,6 +105,18 @@ public function setDataConverter(DataConverterInterface $converter): void // typically handled by children } + /** + * @return string + */ + public function __toString(): string + { + if ($this->hasOriginalStackTrace()) { + return (string) $this->getOriginalStackTrace(); + } + + return parent::__toString(); + } + /** * Explain known types of key=>value pairs. * @@ -125,9 +126,9 @@ public function setDataConverter(DataConverterInterface $converter): void protected static function buildMessage(array $values): string { $mapped = [ - 'timeoutType' => fn ($value) => TimeoutType::name($value), - 'timeoutWorkflowType' => fn ($value) => TimeoutType::name($value), - 'retryState' => fn ($value) => RetryState::name($value), + 'timeoutType' => static fn($value) => TimeoutType::name($value), + 'timeoutWorkflowType' => static fn($value) => TimeoutType::name($value), + 'retryState' => static fn($value) => RetryState::name($value), ]; $result = []; diff --git a/src/Exception/Failure/TimeoutFailure.php b/src/Exception/Failure/TimeoutFailure.php index 3ed970b89..a0080dde5 100644 --- a/src/Exception/Failure/TimeoutFailure.php +++ b/src/Exception/Failure/TimeoutFailure.php @@ -29,12 +29,12 @@ public function __construct( string $message, ValuesInterface $lastHeartbeatDetails, int $timeoutWorkflowType, - \Throwable $previous = null + \Throwable $previous = null, ) { parent::__construct( self::buildMessage(\compact('message', 'timeoutWorkflowType') + ['type' => 'TimeoutFailure']), $message, - $previous + $previous, ); $this->lastHeartbeatDetails = $lastHeartbeatDetails; diff --git a/src/Exception/IllegalStateException.php b/src/Exception/IllegalStateException.php index f37e9c23a..255052d9e 100644 --- a/src/Exception/IllegalStateException.php +++ b/src/Exception/IllegalStateException.php @@ -11,6 +11,4 @@ namespace Temporal\Exception; -class IllegalStateException extends TemporalException -{ -} +class IllegalStateException extends TemporalException {} diff --git a/src/Exception/InstantiationException.php b/src/Exception/InstantiationException.php index 4581fd8bb..d0e380473 100644 --- a/src/Exception/InstantiationException.php +++ b/src/Exception/InstantiationException.php @@ -11,6 +11,4 @@ namespace Temporal\Exception; -class InstantiationException extends TemporalException -{ -} +class InstantiationException extends TemporalException {} diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php index a8f40f102..be87d0e66 100644 --- a/src/Exception/InvalidArgumentException.php +++ b/src/Exception/InvalidArgumentException.php @@ -11,6 +11,4 @@ namespace Temporal\Exception; -class InvalidArgumentException extends TemporalException -{ -} +class InvalidArgumentException extends TemporalException {} diff --git a/src/Exception/MarshallerException.php b/src/Exception/MarshallerException.php index a54897801..b55e42514 100644 --- a/src/Exception/MarshallerException.php +++ b/src/Exception/MarshallerException.php @@ -11,6 +11,4 @@ namespace Temporal\Exception; -class MarshallerException extends TemporalException -{ -} +class MarshallerException extends TemporalException {} diff --git a/src/Exception/OutOfContextException.php b/src/Exception/OutOfContextException.php index 12f69b037..db3b19785 100644 --- a/src/Exception/OutOfContextException.php +++ b/src/Exception/OutOfContextException.php @@ -11,6 +11,4 @@ namespace Temporal\Exception; -class OutOfContextException extends TemporalException -{ -} +class OutOfContextException extends TemporalException {} diff --git a/src/Exception/ProtocolException.php b/src/Exception/ProtocolException.php index 01182e2bb..382e86602 100644 --- a/src/Exception/ProtocolException.php +++ b/src/Exception/ProtocolException.php @@ -11,6 +11,4 @@ namespace Temporal\Exception; -class ProtocolException extends TransportException -{ -} +class ProtocolException extends TransportException {} diff --git a/src/Exception/TransportException.php b/src/Exception/TransportException.php index f9d6a1cf9..eb514eeba 100644 --- a/src/Exception/TransportException.php +++ b/src/Exception/TransportException.php @@ -11,6 +11,4 @@ namespace Temporal\Exception; -class TransportException extends TemporalException -{ -} +class TransportException extends TemporalException {} diff --git a/src/Interceptor/ActivityInbound/ActivityInput.php b/src/Interceptor/ActivityInbound/ActivityInput.php index 6a7b18de7..0ce1a2b49 100644 --- a/src/Interceptor/ActivityInbound/ActivityInput.php +++ b/src/Interceptor/ActivityInbound/ActivityInput.php @@ -1,5 +1,7 @@ arguments, - $header ?? $this->header + $header ?? $this->header, ); } } diff --git a/src/Interceptor/SimplePipelineProvider.php b/src/Interceptor/SimplePipelineProvider.php index 72c74a8e6..216316941 100644 --- a/src/Interceptor/SimplePipelineProvider.php +++ b/src/Interceptor/SimplePipelineProvider.php @@ -23,17 +23,18 @@ class SimplePipelineProvider implements PipelineProvider */ public function __construct( private iterable $interceptors = [], - ) { - } + ) {} /** * @inheritDoc */ public function getPipeline(string $interceptorClass): Pipeline { - return $this->cache[$interceptorClass] ??= Pipeline::prepare(\array_filter( - $this->interceptors, - static fn(Interceptor $i): bool => $i instanceof $interceptorClass) + return $this->cache[$interceptorClass] ??= Pipeline::prepare( + \array_filter( + $this->interceptors, + static fn(Interceptor $i): bool => $i instanceof $interceptorClass, + ), ); } } diff --git a/src/Interceptor/WorkflowClient/CancelInput.php b/src/Interceptor/WorkflowClient/CancelInput.php index 0427b4434..1146b74bd 100644 --- a/src/Interceptor/WorkflowClient/CancelInput.php +++ b/src/Interceptor/WorkflowClient/CancelInput.php @@ -1,5 +1,7 @@ workflowExecution, - $namespace ?? $this->namespace, + $namespace ?? $this->namespace, ); } } diff --git a/src/Interceptor/WorkflowClient/GetResultInput.php b/src/Interceptor/WorkflowClient/GetResultInput.php index f848d1afb..d89851f2b 100644 --- a/src/Interceptor/WorkflowClient/GetResultInput.php +++ b/src/Interceptor/WorkflowClient/GetResultInput.php @@ -1,5 +1,7 @@ signalName, $info ?? $this->info, $arguments ?? $this->arguments, - $header ?? $this->header + $header ?? $this->header, ); } } diff --git a/src/Interceptor/WorkflowInbound/UpdateInput.php b/src/Interceptor/WorkflowInbound/UpdateInput.php index ae2bbc8af..c033d1923 100644 --- a/src/Interceptor/WorkflowInbound/UpdateInput.php +++ b/src/Interceptor/WorkflowInbound/UpdateInput.php @@ -1,5 +1,7 @@ updateId, $info ?? $this->info, $arguments ?? $this->arguments, - $header ?? $this->header + $header ?? $this->header, ); } } diff --git a/src/Interceptor/WorkflowInbound/WorkflowInput.php b/src/Interceptor/WorkflowInbound/WorkflowInput.php index c0957e0d2..0485c5c1f 100644 --- a/src/Interceptor/WorkflowInbound/WorkflowInput.php +++ b/src/Interceptor/WorkflowInbound/WorkflowInput.php @@ -1,5 +1,7 @@ info, $arguments ?? $this->arguments, - $header ?? $this->header + $header ?? $this->header, ); } } diff --git a/src/Interceptor/WorkflowOutboundCalls/AwaitInput.php b/src/Interceptor/WorkflowOutboundCalls/AwaitInput.php index 6c0c8e5bd..d68974a9e 100644 --- a/src/Interceptor/WorkflowOutboundCalls/AwaitInput.php +++ b/src/Interceptor/WorkflowOutboundCalls/AwaitInput.php @@ -19,8 +19,7 @@ final class AwaitInput */ public function __construct( public readonly array $conditions, - ) { - } + ) {} /** * @param array $conditions diff --git a/src/Interceptor/WorkflowOutboundCalls/AwaitWithTimeoutInput.php b/src/Interceptor/WorkflowOutboundCalls/AwaitWithTimeoutInput.php index e9b0ea96b..d3a33a645 100644 --- a/src/Interceptor/WorkflowOutboundCalls/AwaitWithTimeoutInput.php +++ b/src/Interceptor/WorkflowOutboundCalls/AwaitWithTimeoutInput.php @@ -11,7 +11,6 @@ namespace Temporal\Interceptor\WorkflowOutboundCalls; -use DateInterval; use React\Promise\PromiseInterface; /** @@ -26,16 +25,15 @@ final class AwaitWithTimeoutInput * @param array $conditions */ public function __construct( - public readonly DateInterval $interval, + public readonly \DateInterval $interval, public readonly array $conditions, - ) { - } + ) {} /** * @param array $conditions */ public function with( - ?DateInterval $interval = null, + ?\DateInterval $interval = null, ?array $conditions = null, ): self { return new self( diff --git a/src/Interceptor/WorkflowOutboundCalls/CancelExternalWorkflowInput.php b/src/Interceptor/WorkflowOutboundCalls/CancelExternalWorkflowInput.php index 9bcd5bb89..bce01a9af 100644 --- a/src/Interceptor/WorkflowOutboundCalls/CancelExternalWorkflowInput.php +++ b/src/Interceptor/WorkflowOutboundCalls/CancelExternalWorkflowInput.php @@ -24,8 +24,7 @@ public function __construct( public readonly string $namespace, public readonly string $workflowId, public readonly ?string $runId, - ) { - } + ) {} public function with( ?string $namespace = null, diff --git a/src/Interceptor/WorkflowOutboundCalls/CompleteInput.php b/src/Interceptor/WorkflowOutboundCalls/CompleteInput.php index a6c09ddda..99343342d 100644 --- a/src/Interceptor/WorkflowOutboundCalls/CompleteInput.php +++ b/src/Interceptor/WorkflowOutboundCalls/CompleteInput.php @@ -23,8 +23,7 @@ final class CompleteInput public function __construct( public readonly ?array $result, public readonly ?\Throwable $failure, - ) { - } + ) {} public function with( ?array $result = null, diff --git a/src/Interceptor/WorkflowOutboundCalls/ContinueAsNewInput.php b/src/Interceptor/WorkflowOutboundCalls/ContinueAsNewInput.php index 1edd191a3..f421b46aa 100644 --- a/src/Interceptor/WorkflowOutboundCalls/ContinueAsNewInput.php +++ b/src/Interceptor/WorkflowOutboundCalls/ContinueAsNewInput.php @@ -26,8 +26,7 @@ public function __construct( public readonly string $type, public readonly array $args = [], public readonly ?ContinueAsNewOptions $options = null, - ) { - } + ) {} public function with( ?string $type = null, diff --git a/src/Interceptor/WorkflowOutboundCalls/ExecuteActivityInput.php b/src/Interceptor/WorkflowOutboundCalls/ExecuteActivityInput.php index 15e153b0f..e28b5eb24 100644 --- a/src/Interceptor/WorkflowOutboundCalls/ExecuteActivityInput.php +++ b/src/Interceptor/WorkflowOutboundCalls/ExecuteActivityInput.php @@ -32,8 +32,7 @@ public function __construct( public readonly ?ActivityOptions $options, public readonly null|Type|string|\ReflectionClass|\ReflectionType $returnType, public readonly ?\ReflectionMethod $method = null, - ) { - } + ) {} /** * @param non-empty-string|null $type diff --git a/src/Interceptor/WorkflowOutboundCalls/ExecuteChildWorkflowInput.php b/src/Interceptor/WorkflowOutboundCalls/ExecuteChildWorkflowInput.php index e43a4cae0..7882ffc27 100644 --- a/src/Interceptor/WorkflowOutboundCalls/ExecuteChildWorkflowInput.php +++ b/src/Interceptor/WorkflowOutboundCalls/ExecuteChildWorkflowInput.php @@ -27,8 +27,7 @@ public function __construct( public readonly array $args = [], public readonly ?ChildWorkflowOptions $options = null, public readonly mixed $returnType = null, - ) { - } + ) {} public function with( ?string $type = null, @@ -40,7 +39,7 @@ public function with( $type ?? $this->type, $args ?? $this->args, $options ?? $this->options, - $returnType ?? $this->returnType + $returnType ?? $this->returnType, ); } } diff --git a/src/Interceptor/WorkflowOutboundCalls/ExecuteLocalActivityInput.php b/src/Interceptor/WorkflowOutboundCalls/ExecuteLocalActivityInput.php index 8fbf5bdf1..a6c6a431f 100644 --- a/src/Interceptor/WorkflowOutboundCalls/ExecuteLocalActivityInput.php +++ b/src/Interceptor/WorkflowOutboundCalls/ExecuteLocalActivityInput.php @@ -32,8 +32,7 @@ public function __construct( public readonly ?LocalActivityOptions $options, public readonly null|Type|string|\ReflectionClass|\ReflectionType $returnType, public readonly ?\ReflectionMethod $method = null, - ) { - } + ) {} /** * @param non-empty-string|null $type diff --git a/src/Interceptor/WorkflowOutboundCalls/GetVersionInput.php b/src/Interceptor/WorkflowOutboundCalls/GetVersionInput.php index aa143ebb3..aae679cd2 100644 --- a/src/Interceptor/WorkflowOutboundCalls/GetVersionInput.php +++ b/src/Interceptor/WorkflowOutboundCalls/GetVersionInput.php @@ -24,8 +24,7 @@ public function __construct( public readonly string $changeId, public readonly int $minSupported, public readonly int $maxSupported, - ) { - } + ) {} public function with( ?string $changeId = null, diff --git a/src/Interceptor/WorkflowOutboundCalls/PanicInput.php b/src/Interceptor/WorkflowOutboundCalls/PanicInput.php index 15a4a5959..de120b915 100644 --- a/src/Interceptor/WorkflowOutboundCalls/PanicInput.php +++ b/src/Interceptor/WorkflowOutboundCalls/PanicInput.php @@ -22,8 +22,7 @@ final class PanicInput */ public function __construct( public readonly ?\Throwable $failure, - ) { - } + ) {} public function withoutFailure(): self { diff --git a/src/Interceptor/WorkflowOutboundCalls/SideEffectInput.php b/src/Interceptor/WorkflowOutboundCalls/SideEffectInput.php index 45c53aac0..836218a13 100644 --- a/src/Interceptor/WorkflowOutboundCalls/SideEffectInput.php +++ b/src/Interceptor/WorkflowOutboundCalls/SideEffectInput.php @@ -22,8 +22,7 @@ final class SideEffectInput */ public function __construct( public readonly \Closure $callable, - ) { - } + ) {} public function with( ?\Closure $callable = null, diff --git a/src/Interceptor/WorkflowOutboundCalls/SignalExternalWorkflowInput.php b/src/Interceptor/WorkflowOutboundCalls/SignalExternalWorkflowInput.php index e7e8c41eb..43e115207 100644 --- a/src/Interceptor/WorkflowOutboundCalls/SignalExternalWorkflowInput.php +++ b/src/Interceptor/WorkflowOutboundCalls/SignalExternalWorkflowInput.php @@ -29,8 +29,7 @@ public function __construct( public readonly string $signal, public readonly ValuesInterface $input, public readonly bool $childWorkflowOnly = false, - ) { - } + ) {} public function with( ?string $namespace = null, diff --git a/src/Interceptor/WorkflowOutboundCalls/TimerInput.php b/src/Interceptor/WorkflowOutboundCalls/TimerInput.php index 56b6fe613..c5f9b61e4 100644 --- a/src/Interceptor/WorkflowOutboundCalls/TimerInput.php +++ b/src/Interceptor/WorkflowOutboundCalls/TimerInput.php @@ -22,8 +22,7 @@ final class TimerInput */ public function __construct( public readonly \DateInterval $interval, - ) { - } + ) {} public function with( ?\DateInterval $interval = null, diff --git a/src/Interceptor/WorkflowOutboundCalls/UpsertSearchAttributesInput.php b/src/Interceptor/WorkflowOutboundCalls/UpsertSearchAttributesInput.php index cb4940850..b58057f24 100644 --- a/src/Interceptor/WorkflowOutboundCalls/UpsertSearchAttributesInput.php +++ b/src/Interceptor/WorkflowOutboundCalls/UpsertSearchAttributesInput.php @@ -22,8 +22,7 @@ final class UpsertSearchAttributesInput */ public function __construct( public readonly array $searchAttributes, - ) { - } + ) {} public function with( ?array $searchAttributes = null, diff --git a/src/Internal/Activity/ActivityContext.php b/src/Internal/Activity/ActivityContext.php index e311fad3c..070264ebb 100644 --- a/src/Internal/Activity/ActivityContext.php +++ b/src/Internal/Activity/ActivityContext.php @@ -48,7 +48,7 @@ public function __construct( DataConverterInterface $converter, ValuesInterface $input, HeaderInterface $header, - ValuesInterface $lastHeartbeatDetails = null + ValuesInterface $lastHeartbeatDetails = null, ) { $this->info = new ActivityInfo(); $this->rpc = $rpc; @@ -159,9 +159,9 @@ public function heartbeat($details): void $response = $this->rpc->call( 'temporal.RecordActivityHeartbeat', [ - 'taskToken' => base64_encode($this->info->taskToken), - 'details' => base64_encode($details), - ] + 'taskToken' => \base64_encode($this->info->taskToken), + 'details' => \base64_encode($details), + ], ); if (!empty($response['canceled'])) { diff --git a/src/Internal/Assert.php b/src/Internal/Assert.php index 41c603715..99ef88e7e 100644 --- a/src/Internal/Assert.php +++ b/src/Internal/Assert.php @@ -42,7 +42,7 @@ public static function enum($value, string $enum): bool */ public static function valuesInstanceOf(array $values, string $of): bool { - return self::all($values, fn ($v) => $v instanceof $of); + return self::all($values, static fn($v) => $v instanceof $of); } /** @@ -52,7 +52,7 @@ public static function valuesInstanceOf(array $values, string $of): bool */ public static function valuesSubclassOfOrSameClass(array $values, string $of): bool { - return self::all($values, fn ($v) => is_a($v, $of, true)); + return self::all($values, static fn($v) => \is_a($v, $of, true)); } /** diff --git a/src/Internal/Client/ActivityCompletionClient.php b/src/Internal/Client/ActivityCompletionClient.php index 72c833174..930a9a016 100644 --- a/src/Internal/Client/ActivityCompletionClient.php +++ b/src/Internal/Client/ActivityCompletionClient.php @@ -38,7 +38,7 @@ final class ActivityCompletionClient implements ActivityCompletionClientInterfac public function __construct( ServiceClientInterface $client, ClientOptions $clientOptions, - DataConverterInterface $converter + DataConverterInterface $converter, ) { $this->client = $client; $this->clientOptions = $clientOptions; @@ -58,7 +58,7 @@ public function complete(string $workflowId, ?string $runId, string $activityId, ->setRunId($runId ?? '') ->setActivityId($activityId); - $input = EncodedValues::fromValues(array_slice(func_get_args(), 3), $this->converter); + $input = EncodedValues::fromValues(\array_slice(\func_get_args(), 3), $this->converter); if (!$input->isEmpty()) { $r->setResult($input->toPayloads()); } @@ -86,7 +86,7 @@ public function completeByToken(string $taskToken, $result = null): void ->setNamespace($this->clientOptions->namespace) ->setTaskToken($taskToken); - $input = EncodedValues::fromValues(array_slice(func_get_args(), 1), $this->converter); + $input = EncodedValues::fromValues(\array_slice(\func_get_args(), 1), $this->converter); if (!$input->isEmpty()) { $r->setResult($input->toPayloads()); } @@ -109,7 +109,7 @@ public function completeExceptionally( string $workflowId, ?string $runId, string $activityId, - \Throwable $error + \Throwable $error, ): void { $r = new Proto\RespondActivityTaskFailedByIdRequest(); $r @@ -167,7 +167,7 @@ public function reportCancellation(string $workflowId, ?string $runId, string $a ->setRunId($runId ?? '') ->setActivityId($activityId); - if (func_num_args() == 4) { + if (\func_num_args() == 4) { $r->setDetails(EncodedValues::fromValues([$details], $this->converter)->toPayloads()); } @@ -189,7 +189,7 @@ public function reportCancellationByToken(string $taskToken, $details = null): v ->setNamespace($this->clientOptions->namespace) ->setTaskToken($taskToken); - if (func_num_args() == 2) { + if (\func_num_args() == 2) { $r->setDetails(EncodedValues::fromValues([$details], $this->converter)->toPayloads()); } @@ -213,7 +213,7 @@ public function recordHeartbeat(string $workflowId, ?string $runId, string $acti ->setRunId($runId ?? '') ->setActivityId($activityId); - if (func_num_args() == 4) { + if (\func_num_args() == 4) { $r->setDetails(EncodedValues::fromValues([$details], $this->converter)->toPayloads()); } @@ -242,7 +242,7 @@ public function recordHeartbeatByToken(string $taskToken, $details = null): void ->setNamespace($this->clientOptions->namespace) ->setTaskToken($taskToken); - if (func_num_args() == 2) { + if (\func_num_args() == 2) { $r->setDetails(EncodedValues::fromValues([$details], $this->converter)->toPayloads()); } diff --git a/src/Internal/Client/WorkflowProxy.php b/src/Internal/Client/WorkflowProxy.php index e9d0cb13a..6b8d5821f 100644 --- a/src/Internal/Client/WorkflowProxy.php +++ b/src/Internal/Client/WorkflowProxy.php @@ -37,6 +37,29 @@ public function __construct( private readonly WorkflowPrototype $prototype, ) {} + public function hasHandler(): bool + { + return $this->prototype->getHandler() !== null; + } + + /** + * @return \ReflectionMethod + */ + public function getHandlerReflection(): \ReflectionMethod + { + return $this->prototype->getHandler() ?? throw new \LogicException( + 'The workflow does not contain a handler method.', + ); + } + + /** + * @param non-empty-string $name Signal name + */ + public function findSignalReflection(string $name): ?\ReflectionMethod + { + return ($this->prototype->getSignalHandlers()[$name] ?? null)?->method; + } + /** * @param non-empty-string $method * @return mixed|void @@ -115,27 +138,4 @@ public function __getReturnType(): ?ReturnType { return $this->prototype->getReturnType(); } - - public function hasHandler(): bool - { - return $this->prototype->getHandler() !== null; - } - - /** - * @return \ReflectionMethod - */ - public function getHandlerReflection(): \ReflectionMethod - { - return $this->prototype->getHandler() ?? throw new \LogicException( - 'The workflow does not contain a handler method.', - ); - } - - /** - * @param non-empty-string $name Signal name - */ - public function findSignalReflection(string $name): ?\ReflectionMethod - { - return ($this->prototype->getSignalHandlers()[$name] ?? null)?->method; - } } diff --git a/src/Internal/Client/WorkflowRun.php b/src/Internal/Client/WorkflowRun.php index ff54ebb21..64620b6ef 100644 --- a/src/Internal/Client/WorkflowRun.php +++ b/src/Internal/Client/WorkflowRun.php @@ -26,8 +26,7 @@ final class WorkflowRun implements WorkflowRunInterface public function __construct( private WorkflowStubInterface $stub, private $returnType = null, - ) { - } + ) {} /** * @return WorkflowExecution diff --git a/src/Internal/Client/WorkflowStarter.php b/src/Internal/Client/WorkflowStarter.php index c255b7653..4d9b2cd1b 100644 --- a/src/Internal/Client/WorkflowStarter.php +++ b/src/Internal/Client/WorkflowStarter.php @@ -49,8 +49,7 @@ public function __construct( private DataConverterInterface $converter, private ClientOptions $clientOptions, private Pipeline $interceptors, - ) { - } + ) {} /** * @param string $workflowType @@ -72,8 +71,8 @@ public function start( $arguments = EncodedValues::fromValues($args, $this->converter); return $this->interceptors->with( - fn (StartInput $input): WorkflowExecution => $this->executeRequest( - $this->configureExecutionRequest(new StartWorkflowExecutionRequest(), $input) + fn(StartInput $input): WorkflowExecution => $this->executeRequest( + $this->configureExecutionRequest(new StartWorkflowExecutionRequest(), $input), ), /** @see WorkflowClientCallsInterceptor::start() */ 'start', @@ -137,7 +136,8 @@ function (SignalWithStartInput $input): WorkflowExecution { * @throws ServiceClientException * @throws WorkflowExecutionAlreadyStartedException */ - private function executeRequest(StartWorkflowExecutionRequest|SignalWithStartWorkflowExecutionRequest $request, + private function executeRequest( + StartWorkflowExecutionRequest|SignalWithStartWorkflowExecutionRequest $request, ): WorkflowExecution { try { $response = $request instanceof StartWorkflowExecutionRequest @@ -152,7 +152,7 @@ private function executeRequest(StartWorkflowExecutionRequest|SignalWithStartWor throw new WorkflowExecutionAlreadyStartedException( $execution, $request->getWorkflowType()->getName(), - $e + $e, ); } diff --git a/src/Internal/Client/WorkflowStub.php b/src/Internal/Client/WorkflowStub.php index 98de1ef7e..9d85c55ea 100644 --- a/src/Internal/Client/WorkflowStub.php +++ b/src/Internal/Client/WorkflowStub.php @@ -11,7 +11,6 @@ namespace Temporal\Internal\Client; -use ArrayAccess; use Temporal\Api\Enums\V1\EventType; use Temporal\Api\Enums\V1\HistoryEventFilterType; use Temporal\Api\Enums\V1\RetryState; @@ -252,7 +251,7 @@ static function (QueryInput $input) use ($serviceClient, $converter, $clientOpti $input->workflowType, $clientOptions->queryRejectionCondition, $result->getQueryRejected()->getStatus(), - null + null, ); }, /** @see WorkflowClientCallsInterceptor::query() */ @@ -306,7 +305,7 @@ static function ( ->setRequest($r = new UpdateRequestMessage()) ->setWaitPolicy( (new \Temporal\Api\Update\V1\WaitPolicy()) - ->setLifecycleStage($input->waitPolicy->lifecycleStage->value) + ->setLifecycleStage($input->waitPolicy->lifecycleStage->value), ) ->setFirstExecutionRunId($input->firstExecutionRunId) ; @@ -344,7 +343,7 @@ static function ( \assert($updateRef !== null); $updateRefDto = new UpdateRef( new WorkflowExecution( - (string)$updateRef->getWorkflowExecution()?->getWorkflowId(), + (string) $updateRef->getWorkflowExecution()?->getWorkflowId(), $updateRef->getWorkflowExecution()?->getRunId(), ), $updateRef->getUpdateId(), @@ -383,7 +382,7 @@ static function ( throw new \RuntimeException(\sprintf( 'Received unexpected outcome from update request: %s', - $outcome->getValue() + $outcome->getValue(), )); }, /** @see WorkflowClientCallsInterceptor::update() */ @@ -590,7 +589,7 @@ private function fetchResult(int $timeout = null): ?EncodedValues throw new WorkflowExecutionFailedException( $attr->getFailure(), $closeEvent->getTaskId(), - $attr->getRetryState() + $attr->getRetryState(), ); case EventType::EVENT_TYPE_WORKFLOW_EXECUTION_CANCELED: @@ -608,8 +607,8 @@ private function fetchResult(int $timeout = null): ?EncodedValues new CanceledFailure( 'Workflow canceled', $details, - null - ) + null, + ), ); case EventType::EVENT_TYPE_WORKFLOW_EXECUTION_TERMINATED: $attr = $closeEvent->getWorkflowExecutionTerminatedEventAttributes(); @@ -619,7 +618,7 @@ private function fetchResult(int $timeout = null): ?EncodedValues $this->workflowType, 0, RetryState::RETRY_STATE_NON_RETRYABLE_FAILURE, - new TerminatedFailure($attr->getReason()) + new TerminatedFailure($attr->getReason()), ); case EventType::EVENT_TYPE_WORKFLOW_EXECUTION_TIMED_OUT: @@ -633,13 +632,13 @@ private function fetchResult(int $timeout = null): ?EncodedValues new TimeoutFailure( '', EncodedValues::empty(), - TimeoutType::TIMEOUT_TYPE_START_TO_CLOSE - ) + TimeoutType::TIMEOUT_TYPE_START_TO_CLOSE, + ), ); default: throw new \RuntimeException( - 'Workflow end state is not completed: ' . $closeEvent->serializeToJsonString() + 'Workflow end state is not completed: ' . $closeEvent->serializeToJsonString(), ); } } @@ -659,15 +658,15 @@ private function getCloseEvent(int $timeout = null): HistoryEvent ->setExecution($this->execution->toProtoWorkflowExecution()); do { - $start = time(); + $start = \time(); $response = $this->serviceClient->GetWorkflowExecutionHistory( $historyRequest, - $timeout === null ? null : Context::default()->withTimeout($timeout) + $timeout === null ? null : Context::default()->withTimeout($timeout), ); - $elapsed = time() - $start; + $elapsed = \time() - $start; if ($timeout !== null) { - $timeout = max(0, $timeout - $elapsed); + $timeout = \max(0, $timeout - $elapsed); if ($timeout === 0) { throw new TimeoutException('Unable to wait for workflow completion, deadline reached'); @@ -680,7 +679,7 @@ private function getCloseEvent(int $timeout = null): HistoryEvent } $events = $history->getEvents(); - /** @var ArrayAccess $events */ + /** @var \ArrayAccess $events */ if (!$events->offsetExists(0)) { continue; } @@ -693,7 +692,7 @@ private function getCloseEvent(int $timeout = null): HistoryEvent $this->execution->getID(), $closeEvent ->getWorkflowExecutionContinuedAsNewEventAttributes() - ->getNewExecutionRunId() + ->getNewExecutionRunId(), ); $historyRequest->setExecution($this->execution->toProtoWorkflowExecution()); @@ -717,7 +716,7 @@ private function mapWorkflowFailureToException(\Throwable $failure): \Throwable $this->workflowType, $failure->getWorkflowTaskCompletedEventId(), $failure->getRetryState(), - FailureConverter::mapFailureToException($failure->getFailure(), $this->converter) + FailureConverter::mapFailureToException($failure->getFailure(), $this->converter), ); case $failure instanceof ServiceClientException: @@ -726,7 +725,7 @@ private function mapWorkflowFailureToException(\Throwable $failure): \Throwable null, $this->execution, $this->workflowType, - $failure + $failure, ); } @@ -734,7 +733,7 @@ private function mapWorkflowFailureToException(\Throwable $failure): \Throwable null, $this->execution, $this->workflowType, - $failure + $failure, ); case $failure instanceof CanceledFailure || $failure instanceof WorkflowException: diff --git a/src/Internal/Declaration/ActivityInstanceInterface.php b/src/Internal/Declaration/ActivityInstanceInterface.php index b09baf680..b2255a9fe 100644 --- a/src/Internal/Declaration/ActivityInstanceInterface.php +++ b/src/Internal/Declaration/ActivityInstanceInterface.php @@ -11,6 +11,4 @@ namespace Temporal\Internal\Declaration; -interface ActivityInstanceInterface extends InstanceInterface -{ -} +interface ActivityInstanceInterface extends InstanceInterface {} diff --git a/src/Internal/Declaration/Dispatcher/AutowiredPayloads.php b/src/Internal/Declaration/Dispatcher/AutowiredPayloads.php index e659c9337..adb45a070 100644 --- a/src/Internal/Declaration/Dispatcher/AutowiredPayloads.php +++ b/src/Internal/Declaration/Dispatcher/AutowiredPayloads.php @@ -23,12 +23,12 @@ class AutowiredPayloads extends Dispatcher public function dispatchValues(object $ctx, ValuesInterface $values): mixed { $arguments = []; - for ($i = 0; $i < $values->count(); $i++) { - try { + try { + for ($i = 0, $count = $values->count(); $i < $count; $i++) { $arguments[] = $values->getValue($i, $this->getArgumentTypes()[$i] ?? null); - } catch (\Throwable $e) { - throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); } + } catch (\Throwable $e) { + throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); } try { diff --git a/src/Internal/Declaration/Dispatcher/Dispatcher.php b/src/Internal/Declaration/Dispatcher/Dispatcher.php index 2db1af613..9e3eb2cfd 100644 --- a/src/Internal/Declaration/Dispatcher/Dispatcher.php +++ b/src/Internal/Declaration/Dispatcher/Dispatcher.php @@ -12,7 +12,6 @@ namespace Temporal\Internal\Declaration\Dispatcher; use JetBrains\PhpStorm\Pure; -use ReflectionType; /** * @psalm-type FunctionExecutor = \Closure(object, array): mixed @@ -36,7 +35,7 @@ class Dispatcher implements DispatcherInterface private \Closure $executor; /** - * @var array + * @var array<\ReflectionType> */ private $types; @@ -88,7 +87,7 @@ public function isStaticContextAllowed(): bool } /** - * @return array + * @return array<\ReflectionType> */ public function getArgumentTypes(): array { diff --git a/src/Internal/Declaration/Graph/ClassNode.php b/src/Internal/Declaration/Graph/ClassNode.php index 73e867741..b0dd7888f 100644 --- a/src/Internal/Declaration/Graph/ClassNode.php +++ b/src/Internal/Declaration/Graph/ClassNode.php @@ -13,11 +13,6 @@ final class ClassNode implements NodeInterface { - /** - * @var \ReflectionClass - */ - private \ReflectionClass $class; - /** * @var array|null */ @@ -26,25 +21,36 @@ final class ClassNode implements NodeInterface /** * @param \ReflectionClass $class */ - public function __construct(\ReflectionClass $class) - { - $this->class = $class; - } + public function __construct( + private \ReflectionClass $class, + ) {} - /** - * @return string - */ - public function __toString(): string + public function getReflection(): \ReflectionClass { - return $this->class->getName(); + return $this->class; } /** - * @return \ReflectionClass + * Get all methods from the class and its parents without duplicates. + * + * @return array + * + * @throws \ReflectionException */ - public function getReflection(): \ReflectionClass + public function getAllMethods(): array { - return $this->class; + /** @var array $result */ + $result = []; + + foreach ($this->getInheritance() as $classes) { + foreach ($classes as $class) { + foreach ($class->getReflection()->getMethods() as $method) { + $result[$method->getName()] ??= $method; + } + } + } + + return $result; } /** @@ -53,13 +59,14 @@ public function getReflection(): \ReflectionClass public function count(): int { return \count( - $this->inheritance ??= $this->getClassInheritance() + $this->inheritance ??= $this->getClassInheritance(), ); } /** - * @param string $name - * @param bool $reverse + * Get a method with all the declared classes. + * + * @param non-empty-string $name * @return \Traversable * @throws \ReflectionException */ @@ -85,10 +92,18 @@ public function getMethods(string $name, bool $reverse = true): \Traversable public function getIterator(): \Traversable { return new \ArrayIterator( - $this->inheritance ??= $this->getClassInheritance() + $this->inheritance ??= $this->getClassInheritance(), ); } + /** + * @return string + */ + public function __toString(): string + { + return $this->class->getName(); + } + /** * @return array */ diff --git a/src/Internal/Declaration/Graph/NodeInterface.php b/src/Internal/Declaration/Graph/NodeInterface.php index 05e97bb18..343cbd162 100644 --- a/src/Internal/Declaration/Graph/NodeInterface.php +++ b/src/Internal/Declaration/Graph/NodeInterface.php @@ -14,6 +14,4 @@ /** * @template-implements \IteratorAggregate */ -interface NodeInterface extends \Stringable, \IteratorAggregate, \Countable -{ -} +interface NodeInterface extends \Stringable, \IteratorAggregate, \Countable {} diff --git a/src/Internal/Declaration/Instance.php b/src/Internal/Declaration/Instance.php index 329ef7983..9150b09f8 100644 --- a/src/Internal/Declaration/Instance.php +++ b/src/Internal/Declaration/Instance.php @@ -55,6 +55,11 @@ public function getHandler(): callable return $this->handler; } + public function destroy(): void + { + unset($this->handler, $this->context); + } + /** * @param \ReflectionFunctionAbstract $func * @return \Closure(ValuesInterface): mixed @@ -66,11 +71,6 @@ protected function createHandler(\ReflectionFunctionAbstract $func): \Closure $valueMapper = new AutowiredPayloads($func); $context = $this->context; - return static fn (ValuesInterface $values): mixed => $valueMapper->dispatchValues($context, $values); - } - - public function destroy(): void - { - unset($this->handler, $this->context); + return static fn(ValuesInterface $values): mixed => $valueMapper->dispatchValues($context, $values); } } diff --git a/src/Internal/Declaration/Instantiator/ActivityInstantiator.php b/src/Internal/Declaration/Instantiator/ActivityInstantiator.php index 354a7b2f2..a94fb378c 100644 --- a/src/Internal/Declaration/Instantiator/ActivityInstantiator.php +++ b/src/Internal/Declaration/Instantiator/ActivityInstantiator.php @@ -25,7 +25,7 @@ final class ActivityInstantiator extends Instantiator */ public function instantiate(PrototypeInterface $prototype): ActivityInstance { - assert($prototype instanceof ActivityPrototype, 'Precondition failed'); + \assert($prototype instanceof ActivityPrototype, 'Precondition failed'); return new ActivityInstance($prototype, $this->getInstance($prototype)); } diff --git a/src/Internal/Declaration/Instantiator/WorkflowInstantiator.php b/src/Internal/Declaration/Instantiator/WorkflowInstantiator.php index 028ee1158..c6ac77857 100644 --- a/src/Internal/Declaration/Instantiator/WorkflowInstantiator.php +++ b/src/Internal/Declaration/Instantiator/WorkflowInstantiator.php @@ -25,15 +25,14 @@ final class WorkflowInstantiator extends Instantiator { public function __construct( private PipelineProvider $interceptorProvider, - ) { - } + ) {} /** * {@inheritDoc} */ public function instantiate(PrototypeInterface $prototype): WorkflowInstance { - assert($prototype instanceof WorkflowPrototype, 'Precondition failed'); + \assert($prototype instanceof WorkflowPrototype, 'Precondition failed'); return new WorkflowInstance( $prototype, @@ -49,11 +48,11 @@ public function instantiate(PrototypeInterface $prototype): WorkflowInstance */ protected function getInstance(PrototypeInterface $prototype): object { - $handler = $prototype->getHandler() ?? throw new InstantiationException(\sprintf( + $prototype->getHandler() ?? throw new InstantiationException(\sprintf( 'Unable to instantiate workflow "%s" without handler method', $prototype->getID(), )); - return $handler->getDeclaringClass()->newInstanceWithoutConstructor(); + return $prototype->getClass()->newInstanceWithoutConstructor(); } } diff --git a/src/Internal/Declaration/Prototype/ActivityCollection.php b/src/Internal/Declaration/Prototype/ActivityCollection.php index 3d45a03e1..3e135c61d 100644 --- a/src/Internal/Declaration/Prototype/ActivityCollection.php +++ b/src/Internal/Declaration/Prototype/ActivityCollection.php @@ -11,7 +11,6 @@ namespace Temporal\Internal\Declaration\Prototype; -use Closure; use Temporal\Internal\Repository\ArrayRepository; /** @@ -19,14 +18,14 @@ */ final class ActivityCollection extends ArrayRepository { - private ?Closure $finalizer = null; + private ?\Closure $finalizer = null; - public function addFinalizer(Closure $finalizer): void + public function addFinalizer(\Closure $finalizer): void { $this->finalizer = $finalizer; } - public function getFinalizer(): ?Closure + public function getFinalizer(): ?\Closure { return $this->finalizer; } diff --git a/src/Internal/Declaration/Prototype/ActivityPrototype.php b/src/Internal/Declaration/Prototype/ActivityPrototype.php index 81d9dc1ed..325edb643 100644 --- a/src/Internal/Declaration/Prototype/ActivityPrototype.php +++ b/src/Internal/Declaration/Prototype/ActivityPrototype.php @@ -11,7 +11,6 @@ namespace Temporal\Internal\Declaration\Prototype; -use Closure; use Temporal\Activity\ActivityInterface; use Temporal\Activity\LocalActivityInterface; use Temporal\Common\MethodRetry; @@ -29,8 +28,7 @@ final class ActivityPrototype extends Prototype */ private ?ActivityInstance $instance = null; - private ?Closure $factory = null; - + private ?\Closure $factory = null; private bool $isLocalActivity; /** @@ -53,7 +51,7 @@ public function __construct( public function getHandler(): \ReflectionMethod { $handler = parent::getHandler(); - assert($handler !== null); + \assert($handler !== null); return $handler; } @@ -81,7 +79,7 @@ public function getInstance(): ActivityInstance } if ($this->factory !== null) { - $instance = call_user_func($this->factory, $this->getClass()); + $instance = \call_user_func($this->factory, $this->getClass()); return new ActivityInstance($this, $instance); } @@ -100,7 +98,7 @@ public function withInstance(object $instance): self return $proto; } - public function withFactory(Closure $factory): self + public function withFactory(\Closure $factory): self { $proto = clone $this; $proto->factory = $factory; @@ -113,7 +111,7 @@ public function isLocalActivity(): bool return $this->isLocalActivity; } - public function getFactory(): ?Closure + public function getFactory(): ?\Closure { return $this->factory; } diff --git a/src/Internal/Declaration/Prototype/WorkflowCollection.php b/src/Internal/Declaration/Prototype/WorkflowCollection.php index 678d28711..7c31d603d 100644 --- a/src/Internal/Declaration/Prototype/WorkflowCollection.php +++ b/src/Internal/Declaration/Prototype/WorkflowCollection.php @@ -16,6 +16,4 @@ /** * @template-extends ArrayRepository */ -final class WorkflowCollection extends ArrayRepository -{ -} +final class WorkflowCollection extends ArrayRepository {} diff --git a/src/Internal/Declaration/Reader/ActivityReader.php b/src/Internal/Declaration/Reader/ActivityReader.php index b79137b05..c818764a2 100644 --- a/src/Internal/Declaration/Reader/ActivityReader.php +++ b/src/Internal/Declaration/Reader/ActivityReader.php @@ -135,7 +135,7 @@ private function getMethodGroups(ClassNode $graph, \ReflectionMethod $root): arr $reflection = $method->getDeclaringClass(); throw new \LogicException( - \sprintf(self::ERROR_BAD_DECLARATION, $reflection->getName(), $method->getName()) + \sprintf(self::ERROR_BAD_DECLARATION, $reflection->getName(), $method->getName()), ); } @@ -174,7 +174,7 @@ private function getMethodGroups(ClassNode $graph, \ReflectionMethod $root): arr private function activityName( \ReflectionMethod $ref, ActivityInterface $int, - ?ActivityMethod $method + ?ActivityMethod $method, ): string { return $method === null ? $int->prefix . $ref->getName() @@ -191,7 +191,7 @@ private function assertActivityNotExists( string $name, array $activities, \ReflectionClass $class, - \ReflectionMethod $method + \ReflectionMethod $method, ): void { if (!isset($activities[$name])) { return; diff --git a/src/Internal/Declaration/Reader/RecursiveAttributeReducerInterface.php b/src/Internal/Declaration/Reader/RecursiveAttributeReducerInterface.php index 98e7753f9..48efaf8fb 100644 --- a/src/Internal/Declaration/Reader/RecursiveAttributeReducerInterface.php +++ b/src/Internal/Declaration/Reader/RecursiveAttributeReducerInterface.php @@ -24,7 +24,7 @@ public function root( \ReflectionClass $class, \ReflectionMethod $method, ?object $interface, - array $attributes + array $attributes, ); /** @@ -38,6 +38,6 @@ public function each( \ReflectionClass $class, \ReflectionMethod $method, ?object $interface, - array $attributes + array $attributes, ); } diff --git a/src/Internal/Declaration/Reader/WorkflowReader.php b/src/Internal/Declaration/Reader/WorkflowReader.php index 15524f3ed..5b8183f6f 100644 --- a/src/Internal/Declaration/Reader/WorkflowReader.php +++ b/src/Internal/Declaration/Reader/WorkflowReader.php @@ -97,7 +97,7 @@ public function fromClass(string $class): WorkflowPrototype public function fromObject(object $object): WorkflowPrototype { - return $this->fromClass(get_class($object)); + return $this->fromClass($object::class); } /** @@ -107,9 +107,7 @@ public function fromObject(object $object): WorkflowPrototype */ protected function getWorkflowPrototypes(ClassNode $graph): \Traversable { - $class = $graph->getReflection(); - - foreach ($class->getMethods() as $reflection) { + foreach ($graph->getAllMethods() as $reflection) { if (!$this->isValidMethod($reflection)) { continue; } @@ -173,7 +171,7 @@ private function withMethods(ClassNode $graph, WorkflowPrototype $prototype): Wo 'signal', $contextClass->getName(), $method->getName(), - ]) + ]), ); } @@ -197,7 +195,7 @@ private function withMethods(ClassNode $graph, WorkflowPrototype $prototype): Wo 'query', $contextClass->getName(), $method->getName(), - ]) + ]), ); } @@ -227,7 +225,7 @@ private function withMethods(ClassNode $graph, WorkflowPrototype $prototype): Wo $method->getDeclaringClass()->getName(), $method->getName(), $validate->forUpdate, - ]) + ]), ); } @@ -238,7 +236,7 @@ private function withMethods(ClassNode $graph, WorkflowPrototype $prototype): Wo 'validate update', $contextClass->getName(), $method->getName(), - ]) + ]), ); } @@ -265,7 +263,7 @@ private function assertWorkflowInterface(ClassNode $graph): void } throw new \LogicException( - \sprintf(self::ERROR_WORKFLOW_INTERFACE_NOT_FOUND, $graph, WorkflowInterface::class) + \sprintf(self::ERROR_WORKFLOW_INTERFACE_NOT_FOUND, $graph, WorkflowInterface::class), ); } @@ -305,7 +303,7 @@ private function getPrototype(ClassNode $graph, \ReflectionMethod $handler): ?Wo // $contextualRetry = $previousRetry; - foreach ($group as $ctx => $method) { + foreach ($group as $method) { /** @var MethodRetry $retry */ $retry = $this->reader->firstFunctionMetadata($method, MethodRetry::class); @@ -335,15 +333,29 @@ private function getPrototype(ClassNode $graph, \ReflectionMethod $handler): ?Wo // // - #[WorkflowInterface] // - $interface = $this->reader->firstClassMetadata($ctx->getReflection(), WorkflowInterface::class); + /** @var \ReflectionClass|null $context */ + $interface = $context = null; + foreach ($graph->getIterator() as $edges) { + foreach ($edges as $node) { + $interface = $this->reader->firstClassMetadata( + $context = $node->getReflection(), + WorkflowInterface::class, + ); + + if ($interface !== null) { + break 2; + } + } + } // In case if ($interface === null) { continue; } + \assert($context !== null); if ($prototype === null) { - $prototype = $this->findProto($handler, $method); + $prototype = $this->findProto($handler, $method, $context, $graph->getReflection()); } if ($prototype !== null && $retry !== null) { @@ -371,15 +383,18 @@ private function getDefaultPrototype(ClassNode $graph): WorkflowPrototype } /** - * @param \ReflectionMethod $handler - * @param \ReflectionMethod $ctx + * @param \ReflectionMethod $handler First method in the inheritance chain + * @param \ReflectionMethod $ctx Current method in the inheritance chain + * @param \ReflectionClass $interface Class or Interface with #[WorkflowInterface] attribute + * @param \ReflectionClass $class Target class * @return WorkflowPrototype|null */ - private function findProto(\ReflectionMethod $handler, \ReflectionMethod $ctx): ?WorkflowPrototype - { - $reflection = $ctx->getDeclaringClass(); - - // + private function findProto( + \ReflectionMethod $handler, + \ReflectionMethod $ctx, + \ReflectionClass $interface, + \ReflectionClass $class, + ): ?WorkflowPrototype { // The name of the workflow handler must be generated based // method's name which can be redefined using #[WorkflowMethod] // attribute. @@ -414,12 +429,12 @@ private function findProto(\ReflectionMethod $handler, \ReflectionMethod $ctx): $contextClass = $ctx->getDeclaringClass(); throw new \LogicException( - \sprintf(self::ERROR_HANDLER_VISIBILITY, $contextClass->getName(), $ctx->getName()) + \sprintf(self::ERROR_HANDLER_VISIBILITY, $contextClass->getName(), $ctx->getName()), ); } - $name = $info->name ?? $reflection->getShortName(); + $name = $info->name ?? $interface->getShortName(); - return new WorkflowPrototype($name, $handler, $reflection); + return new WorkflowPrototype($name, $handler, $class); } } diff --git a/src/Internal/Declaration/WorkflowInstance.php b/src/Internal/Declaration/WorkflowInstance.php index 50bd54a7e..3108f1554 100644 --- a/src/Internal/Declaration/WorkflowInstance.php +++ b/src/Internal/Declaration/WorkflowInstance.php @@ -190,7 +190,7 @@ public function findValidateUpdateHandler(string $name): ?\Closure /** * @param string $name - * @param callable(ValuesInterface):mixed $handler + * @param callable $handler * @throws \ReflectionException */ public function addQueryHandler(string $name, callable $handler): void @@ -208,7 +208,7 @@ function (QueryInput $input) use ($fn) { /** * @param non-empty-string $name - * @param callable(ValuesInterface):mixed $handler + * @param callable $handler * @throws \ReflectionException */ public function addUpdateHandler(string $name, callable $handler): void @@ -224,6 +224,17 @@ function (UpdateInput $input, Deferred $deferred) use ($fn) { )(...); } + /** + * @param non-empty-string $name + * @param callable $handler + * @throws \ReflectionException + */ + public function addValidateUpdateHandler(string $name, callable $handler): void + { + $fn = $this->createCallableHandler($handler); + $this->validateUpdateHandlers[$name] = fn(UpdateInput $input): mixed => ($this->updateValidator)($input, $fn); + } + /** * @return string[] */ @@ -242,7 +253,7 @@ public function getUpdateHandlerNames(): array public function getSignalHandler(string $name): \Closure { - return fn (ValuesInterface $values) => $this->signalQueue->push($name, $values); + return fn(ValuesInterface $values) => $this->signalQueue->push($name, $values); } /** diff --git a/src/Internal/Declaration/WorkflowInstanceInterface.php b/src/Internal/Declaration/WorkflowInstanceInterface.php index a0871f780..56c3c5e56 100644 --- a/src/Internal/Declaration/WorkflowInstanceInterface.php +++ b/src/Internal/Declaration/WorkflowInstanceInterface.php @@ -43,6 +43,12 @@ public function addQueryHandler(string $name, callable $handler): void; */ public function addUpdateHandler(string $name, callable $handler): void; + /** + * @param non-empty-string $name + * @param callable $handler + */ + public function addValidateUpdateHandler(string $name, callable $handler): void; + /** * @param non-empty-string $name * @return \Closure(ValuesInterface): void diff --git a/src/Internal/Interceptor/Interceptor.php b/src/Internal/Interceptor/Interceptor.php index acf719e2a..baa3ac690 100644 --- a/src/Internal/Interceptor/Interceptor.php +++ b/src/Internal/Interceptor/Interceptor.php @@ -16,6 +16,4 @@ * * @internal */ -interface Interceptor -{ -} +interface Interceptor {} diff --git a/src/Internal/Interceptor/Pipeline.php b/src/Internal/Interceptor/Pipeline.php index 301453fd3..c6d8fd1f7 100644 --- a/src/Internal/Interceptor/Pipeline.php +++ b/src/Internal/Interceptor/Pipeline.php @@ -30,11 +30,12 @@ final class Pipeline /** @var non-empty-string */ private string $method; - /** @var Closure */ - private Closure $last; + /** @var \Closure */ + private \Closure $last; /** @var list */ private array $interceptors = []; + /** @var int<0, max> Current interceptor key */ private int $current = 0; @@ -65,7 +66,7 @@ public static function prepare(iterable $interceptors): self } /** - * @param Closure $last + * @param \Closure $last * @param non-empty-string $method Method name of the all interceptors. * * @return TCallable diff --git a/src/Internal/Mapper/ScheduleMapper.php b/src/Internal/Mapper/ScheduleMapper.php index 6563cc90f..bddb2317d 100644 --- a/src/Internal/Mapper/ScheduleMapper.php +++ b/src/Internal/Mapper/ScheduleMapper.php @@ -5,7 +5,6 @@ namespace Temporal\Internal\Mapper; use Temporal\Api\Common\V1\Payloads; -use Temporal\Api\Common\V1\RetryPolicy; use Temporal\Api\Common\V1\WorkflowType; use Temporal\Api\Schedule\V1\CalendarSpec; use Temporal\Api\Schedule\V1\IntervalSpec; @@ -26,8 +25,7 @@ final class ScheduleMapper public function __construct( private readonly DataConverterInterface $converter, private readonly MarshallerInterface $marshaller, - ) { - } + ) {} public function toMessage(Schedule $dto): \Temporal\Api\Schedule\V1\Schedule { @@ -50,6 +48,11 @@ public function toMessage(Schedule $dto): \Temporal\Api\Schedule\V1\Schedule return new \Temporal\Api\Schedule\V1\Schedule(self::cleanArray($array)); } + private static function cleanArray(array $array): array + { + return \array_filter($array, static fn($item): bool => $item !== null); + } + /** * @psalm-suppress TypeDoesNotContainNull,RedundantCondition */ @@ -122,9 +125,4 @@ private function prepareStructuredCalendar(array $array): array return $array; } - - private static function cleanArray(array $array): array - { - return \array_filter($array, static fn ($item): bool => $item !== null); - } } diff --git a/src/Internal/Mapper/WorkflowExecutionInfoMapper.php b/src/Internal/Mapper/WorkflowExecutionInfoMapper.php index 00ef71e33..887b2cd62 100644 --- a/src/Internal/Mapper/WorkflowExecutionInfoMapper.php +++ b/src/Internal/Mapper/WorkflowExecutionInfoMapper.php @@ -24,8 +24,7 @@ final class WorkflowExecutionInfoMapper { public function __construct( private readonly DataConverterInterface $converter, - ) { - } + ) {} public function fromMessage(WorkflowExecutionInfo $message): WorkflowExecutionInfoDto { @@ -44,7 +43,7 @@ public function fromMessage(WorkflowExecutionInfo $message): WorkflowExecutionIn startTime: $message->getStartTime()?->toDateTime(), closeTime: $message->getCloseTime()?->toDateTime(), status: WorkflowExecutionStatus::from($message->getStatus()), - historyLength: (int)$message->getHistoryLength(), + historyLength: (int) $message->getHistoryLength(), parentNamespaceId: $message->getParentNamespaceId(), parentExecution: $this->prepareWorkflowExecution($message->getParentExecution()), executionTime: $message->getExecutionTime()?->toDateTime(), @@ -52,8 +51,8 @@ public function fromMessage(WorkflowExecutionInfo $message): WorkflowExecutionIn searchAttributes: $this->prepareSearchAttributes($message->getSearchAttributes()), autoResetPoints: $this->prepareAutoResetPoints($message->getAutoResetPoints()), taskQueue: $message->getTaskQueue(), - stateTransitionCount: (int)$message->getStateTransitionCount(), - historySizeBytes: (int)$message->getHistorySizeBytes(), + stateTransitionCount: (int) $message->getStateTransitionCount(), + historySizeBytes: (int) $message->getHistorySizeBytes(), mostRecentWorkerVersionStamp: $this->prepareWorkerVersionStamp($message->getMostRecentWorkerVersionStamp()), ); } @@ -100,7 +99,7 @@ private function prepareWorkflowExecution(?WorkflowExecution $execution): ?Workf return new WorkflowExecutionDto( id: $execution->getWorkflowId(), - runId: $execution->getRunId() + runId: $execution->getRunId(), ); } @@ -119,7 +118,7 @@ private function prepareAutoResetPoints(?ResetPoints $getAutoResetPoints): array $resetPoints[] = new ResetPointInfoDto( binaryChecksum: $point->getBinaryChecksum(), runId: $point->getRunId(), - firstWorkflowTaskCompletedId: (int)$point->getFirstWorkflowTaskCompletedId(), + firstWorkflowTaskCompletedId: (int) $point->getFirstWorkflowTaskCompletedId(), createTime: $point->getCreateTime()?->toDateTime(), expireTime: $point->getExpireTime()?->toDateTime(), resettable: $point->getResettable(), diff --git a/src/Internal/Marshaller/Marshaller.php b/src/Internal/Marshaller/Marshaller.php index 04ae4793b..535bddc64 100644 --- a/src/Internal/Marshaller/Marshaller.php +++ b/src/Internal/Marshaller/Marshaller.php @@ -63,7 +63,7 @@ public function marshal(object $from): array } catch (\Throwable $e) { throw new MarshallerException( \sprintf('Unable to marshal field `%s` of class %s.', $field, $from::class), - previous: $e + previous: $e, ); } } @@ -101,7 +101,7 @@ public function unmarshal(array $from, object $to): object } catch (\Throwable $e) { throw new MarshallerException( \sprintf('Unable to unmarshal field `%s` of class %s', $field, $to::class), - previous: $e + previous: $e, ); } } diff --git a/src/Internal/Marshaller/MarshallingRule.php b/src/Internal/Marshaller/MarshallingRule.php index efa70b1ed..fb4bae40c 100644 --- a/src/Internal/Marshaller/MarshallingRule.php +++ b/src/Internal/Marshaller/MarshallingRule.php @@ -27,8 +27,7 @@ public function __construct( public ?string $name = null, public ?string $type = null, public self|string|null $of = null, - ) { - } + ) {} public function hasType(): bool { diff --git a/src/Internal/Marshaller/Meta/Marshal.php b/src/Internal/Marshaller/Meta/Marshal.php index a7b019814..d1b44c816 100644 --- a/src/Internal/Marshaller/Meta/Marshal.php +++ b/src/Internal/Marshaller/Meta/Marshal.php @@ -13,7 +13,6 @@ use Spiral\Attributes\NamedArgumentConstructor; use Temporal\Internal\Marshaller\MarshallingRule; -use Temporal\Internal\Marshaller\Type\NullableType; /** * You may use this annotation multiple times to specify multiple marshalling rules for a single property. It may be diff --git a/src/Internal/Marshaller/Meta/MarshalAssocArray.php b/src/Internal/Marshaller/Meta/MarshalAssocArray.php new file mode 100644 index 000000000..c051f6065 --- /dev/null +++ b/src/Internal/Marshaller/Meta/MarshalAssocArray.php @@ -0,0 +1,41 @@ +|null $of Local representation of the date. + * @param class-string<\DateTimeInterface>|null $of Local representation of the date. * May be any of internal or Carbon {@see DatetimeInterface} implementations. * @param non-empty-string $to Datetime format or {@see Timestamp} class name. * @param bool $nullable diff --git a/src/Internal/Marshaller/Meta/Scope.php b/src/Internal/Marshaller/Meta/Scope.php index 4a179ac6d..8473f0123 100644 --- a/src/Internal/Marshaller/Meta/Scope.php +++ b/src/Internal/Marshaller/Meta/Scope.php @@ -42,9 +42,7 @@ class Scope /** * @var int */ - public const VISIBILITY_ALL = self::VISIBILITY_PRIVATE - | self::VISIBILITY_PROTECTED - | self::VISIBILITY_PUBLIC; + public const VISIBILITY_ALL = self::VISIBILITY_PRIVATE | self::VISIBILITY_PROTECTED | self::VISIBILITY_PUBLIC; /** * @var ExportScope @@ -63,7 +61,7 @@ class Scope */ public function __construct( int $properties = self::VISIBILITY_PUBLIC, - bool $copyOnWrite = false + bool $copyOnWrite = false, ) { $this->properties = $properties; $this->copyOnWrite = $copyOnWrite; diff --git a/src/Internal/Marshaller/ProtoToArrayConverter.php b/src/Internal/Marshaller/ProtoToArrayConverter.php index 90b29fe45..325ea271e 100644 --- a/src/Internal/Marshaller/ProtoToArrayConverter.php +++ b/src/Internal/Marshaller/ProtoToArrayConverter.php @@ -4,7 +4,6 @@ namespace Temporal\Internal\Marshaller; -use DateTimeImmutable; use Google\Protobuf\Duration; use Google\Protobuf\Internal\MapField; use Google\Protobuf\Internal\Message; @@ -29,8 +28,7 @@ final class ProtoToArrayConverter { public function __construct( private readonly DataConverterInterface $converter, - ) { - } + ) {} public function convert(mixed $message): mixed { @@ -48,7 +46,7 @@ public function convert(mixed $message): mixed private function getMapper(Message $message): ?\Closure { $mapper = match ($message::class) { - Timestamp::class => static fn(Timestamp $input): DateTimeImmutable => DateTimeImmutable::createFromFormat( + Timestamp::class => static fn(Timestamp $input): \DateTimeImmutable => \DateTimeImmutable::createFromFormat( 'U.u', \sprintf('%d.%d', $input->getSeconds(), $input->getNanos() / 1000), ), @@ -56,8 +54,8 @@ private function getMapper(Message $message): ?\Closure $now = new \DateTimeImmutable('@0'); return $now->diff( $now->modify( - \sprintf('+%d seconds +%d microseconds', $input->getSeconds(), $input->getNanos() / 1000) - ) + \sprintf('+%d seconds +%d microseconds', $input->getSeconds(), $input->getNanos() / 1000), + ), ); }, SearchAttributes::class => fn(SearchAttributes $input): EncodedCollection => @@ -77,7 +75,7 @@ private function getMapper(Message $message): ?\Closure ScheduleAction::class => fn(ScheduleAction $scheduleAction): array => [ 'action' => $this->convert( // Use getter for `oneOf` field - $scheduleAction->{'get' . \str_replace('_', '', \ucwords($scheduleAction->getAction(), '_'))}() + $scheduleAction->{'get' . \str_replace('_', '', \ucwords($scheduleAction->getAction(), '_'))}(), ), 'start_workflow' => $this->convert($scheduleAction->getStartWorkflow()), ], diff --git a/src/Internal/Marshaller/Type/AssocArrayType.php b/src/Internal/Marshaller/Type/AssocArrayType.php new file mode 100644 index 000000000..39154f057 --- /dev/null +++ b/src/Internal/Marshaller/Type/AssocArrayType.php @@ -0,0 +1,97 @@ + + */ +class AssocArrayType extends Type +{ + /** + * @var string + */ + private const ERROR_INVALID_TYPE = 'Passed value must be a type of array, but %s given'; + + /** + * @var TypeInterface|null + */ + private ?TypeInterface $type = null; + + /** + * @param MarshallerInterface $marshaller + * @param MarshallingRule|string|null $typeOrClass + * + * @throws \ReflectionException + */ + public function __construct(MarshallerInterface $marshaller, MarshallingRule|string $typeOrClass = null) + { + if ($typeOrClass !== null) { + $this->type = $this->ofType($marshaller, $typeOrClass); + } + + parent::__construct($marshaller); + } + + /** + * @psalm-assert array $value + * @psalm-assert array $current + * @param mixed $value + * @param mixed $current + */ + public function parse($value, $current): array + { + \is_array($value) or throw new \InvalidArgumentException( + \sprintf(self::ERROR_INVALID_TYPE, \get_debug_type($value)), + ); + + if ($this->type) { + $result = []; + + foreach ($value as $i => $item) { + $result[$i] = $this->type->parse($item, $current[$i] ?? null); + } + + return $result; + } + + return $value; + } + + public function serialize($value): object + { + if ($this->type) { + $result = []; + + foreach ($value as $i => $item) { + $result[$i] = $this->type->serialize($item); + } + + return (object) $result; + } + + if (\is_array($value)) { + return (object) $value; + } + + // Convert iterable to array + $result = []; + foreach ($value as $i => $item) { + $result[$i] = $item; + } + return (object) $result; + } +} diff --git a/src/Internal/Marshaller/Type/CronType.php b/src/Internal/Marshaller/Type/CronType.php index 5ab223cdb..ae5657cb0 100644 --- a/src/Internal/Marshaller/Type/CronType.php +++ b/src/Internal/Marshaller/Type/CronType.php @@ -46,7 +46,7 @@ public function parse($value, $current) public function serialize($value): string { if (\is_string($value) || $value instanceof \Stringable) { - return (string)$value; + return (string) $value; } throw new \InvalidArgumentException(\sprintf(self::ERROR_INVALID_TYPE, \get_debug_type($value))); diff --git a/src/Internal/Marshaller/Type/DateIntervalType.php b/src/Internal/Marshaller/Type/DateIntervalType.php index 261770472..27af8485c 100644 --- a/src/Internal/Marshaller/Type/DateIntervalType.php +++ b/src/Internal/Marshaller/Type/DateIntervalType.php @@ -70,23 +70,23 @@ public static function makeRule(\ReflectionProperty $property): ?MarshallingRule public function serialize($value): int|Duration { if ($this->format === DateInterval::FORMAT_NANOSECONDS) { - return (int)(DateInterval::parse($value, $this->format)->totalMicroseconds * 1000); + return (int) (DateInterval::parse($value, $this->format)->totalMicroseconds * 1000); } if ($this->format === Duration::class) { return match (true) { $value instanceof \DateInterval => DateInterval::toDuration($value), \is_int($value) => (new Duration())->setSeconds($value), - \is_string($value) => (new Duration())->setSeconds((int)$value), + \is_string($value) => (new Duration())->setSeconds((int) $value), \is_float($value) => (new Duration()) - ->setSeconds((int)$value) + ->setSeconds((int) $value) ->setNanos(($value * 1000000000) % 1000000000), default => throw new \InvalidArgumentException('Invalid value type.'), }; } $method = 'total' . \ucfirst($this->format); - return (int)(DateInterval::parse($value, $this->format)->$method); + return (int) (DateInterval::parse($value, $this->format)->$method); } /** diff --git a/src/Internal/Marshaller/Type/DateTimeType.php b/src/Internal/Marshaller/Type/DateTimeType.php index 6b66ce72d..7e15d5e74 100644 --- a/src/Internal/Marshaller/Type/DateTimeType.php +++ b/src/Internal/Marshaller/Type/DateTimeType.php @@ -11,7 +11,6 @@ namespace Temporal\Internal\Marshaller\Type; -use DateTimeInterface; use Google\Protobuf\Timestamp; use JetBrains\PhpStorm\Pure; use Temporal\Internal\Marshaller\MarshallerInterface; @@ -28,14 +27,15 @@ class DateTimeType extends Type implements DetectableTypeInterface, RuleFactoryI * @var string */ private string $format; + /** - * @var class-string + * @var class-string<\DateTimeInterface> */ private string $class; /** * @param MarshallerInterface $marshaller - * @param class-string|null $class + * @param class-string<\DateTimeInterface>|null $class * @param string $format */ #[Pure] @@ -44,7 +44,7 @@ public function __construct( ?string $class = null, string $format = \DateTimeInterface::RFC3339, ) { - $class ??= DateTimeInterface::class; + $class ??= \DateTimeInterface::class; $this->format = $format; parent::__construct($marshaller); @@ -93,7 +93,7 @@ public function serialize($value): Timestamp|string return match ($this->format) { Timestamp::class => (new Timestamp()) ->setSeconds($datetime->getTimestamp()) - ->setNanos((int)$datetime->format('u') * 1000), + ->setNanos((int) $datetime->format('u') * 1000), default => $datetime->format($this->format), }; } diff --git a/src/Internal/Marshaller/Type/DurationJsonType.php b/src/Internal/Marshaller/Type/DurationJsonType.php index 0eb3d7f3e..0605f6bdb 100644 --- a/src/Internal/Marshaller/Type/DurationJsonType.php +++ b/src/Internal/Marshaller/Type/DurationJsonType.php @@ -72,14 +72,14 @@ public function serialize($value): ?array $duration = match (true) { $value instanceof \DateInterval => DateInterval::toDuration($value), \is_int($value) => (new Duration())->setSeconds($value), - \is_string($value) => (new Duration())->setSeconds((int)$value), + \is_string($value) => (new Duration())->setSeconds((int) $value), \is_float($value) => (new Duration()) - ->setSeconds((int)$value) + ->setSeconds((int) $value) ->setNanos(($value * 1000000000) % 1000000000), default => throw new \InvalidArgumentException('Invalid value type.'), }; - return $duration === null ? null : ['seconds' => $duration->getSeconds(), 'nanos' => $duration->getNanos()]; + return ['seconds' => $duration->getSeconds(), 'nanos' => $duration->getNanos()]; } /** @@ -87,7 +87,7 @@ public function serialize($value): ?array */ public function parse($value, $current): CarbonInterval { - if (is_array($value) && isset($value['seconds']) && isset($value['nanos'])) { + if (\is_array($value) && isset($value['seconds']) && isset($value['nanos'])) { // The highest precision is milliseconds either way. $value = $value['seconds'] * 1_000_000_000 + $value['nanos']; return DateInterval::parse($value, DateInterval::FORMAT_NANOSECONDS); diff --git a/src/Internal/Marshaller/Type/EncodedCollectionType.php b/src/Internal/Marshaller/Type/EncodedCollectionType.php index 3a1347160..98e853d74 100644 --- a/src/Internal/Marshaller/Type/EncodedCollectionType.php +++ b/src/Internal/Marshaller/Type/EncodedCollectionType.php @@ -12,7 +12,6 @@ namespace Temporal\Internal\Marshaller\Type; use Google\Protobuf\Internal\Message; -use ReflectionNamedType; use Temporal\Api\Common\V1\Header; use Temporal\Api\Common\V1\Memo; use Temporal\Api\Common\V1\Payloads; @@ -46,7 +45,7 @@ public static function makeRule(\ReflectionProperty $property): ?MarshallingRule { $type = $property->getType(); - if (!$type instanceof ReflectionNamedType || !self::match($type)) { + if (!$type instanceof \ReflectionNamedType || !self::match($type)) { return null; } diff --git a/src/Internal/Marshaller/Type/EnumType.php b/src/Internal/Marshaller/Type/EnumType.php index 8e9342396..202740ff3 100644 --- a/src/Internal/Marshaller/Type/EnumType.php +++ b/src/Internal/Marshaller/Type/EnumType.php @@ -11,7 +11,6 @@ namespace Temporal\Internal\Marshaller\Type; -use BackedEnum; use Temporal\Internal\Marshaller\MarshallerInterface; use Temporal\Internal\Marshaller\MarshallingRule; @@ -70,7 +69,7 @@ public function parse($value, $current) } if (\is_array($value)) { - // Process the `value` key + // Process the `value` key if (\array_key_exists('value', $value)) { return $this->classFQCN::from($value['value']); } @@ -87,10 +86,11 @@ public function parse($value, $current) /** * @psalm-suppress UndefinedDocblockClass + * @param mixed $value */ public function serialize($value): array { - return $value instanceof BackedEnum + return $value instanceof \BackedEnum ? [ 'name' => $value->name, 'value' => $value->value, diff --git a/src/Internal/Marshaller/Type/EnumValueType.php b/src/Internal/Marshaller/Type/EnumValueType.php index 8aa45f0ba..92420bbb7 100644 --- a/src/Internal/Marshaller/Type/EnumValueType.php +++ b/src/Internal/Marshaller/Type/EnumValueType.php @@ -11,7 +11,6 @@ namespace Temporal\Internal\Marshaller\Type; -use BackedEnum; use Temporal\Internal\Marshaller\MarshallerInterface; use Temporal\Internal\Marshaller\MarshallingRule; @@ -31,7 +30,7 @@ class EnumValueType extends Type implements RuleFactoryInterface public function __construct(MarshallerInterface $marshaller, ?string $class = null) { $this->classFQCN = $class ?? throw new \RuntimeException('Enum is required.'); - \is_a($class, BackedEnum::class, true) ?: throw new \RuntimeException( + \is_a($class, \BackedEnum::class, true) ?: throw new \RuntimeException( 'Class for EnumValueType must be an instance of BackedEnum.', ); parent::__construct($marshaller); diff --git a/src/Internal/Marshaller/Type/ObjectType.php b/src/Internal/Marshaller/Type/ObjectType.php index 2c18d66a1..424b53806 100644 --- a/src/Internal/Marshaller/Type/ObjectType.php +++ b/src/Internal/Marshaller/Type/ObjectType.php @@ -11,7 +11,6 @@ namespace Temporal\Internal\Marshaller\Type; -use stdClass; use Temporal\Internal\Marshaller\MarshallerInterface; use Temporal\Internal\Marshaller\MarshallingRule; @@ -33,7 +32,7 @@ class ObjectType extends Type implements DetectableTypeInterface, RuleFactoryInt */ public function __construct(MarshallerInterface $marshaller, string $class = null) { - $this->reflection = new \ReflectionClass($class ?? stdClass::class); + $this->reflection = new \ReflectionClass($class ?? \stdClass::class); parent::__construct($marshaller); } @@ -79,7 +78,7 @@ public function parse($value, $current): object $current = $this->emptyInstance(); } - if ($current::class === stdClass::class && $this->reflection->getName() === stdClass::class) { + if ($current::class === \stdClass::class && $this->reflection->getName() === \stdClass::class) { foreach ($value as $key => $val) { $current->$key = $val; } @@ -95,8 +94,8 @@ public function parse($value, $current): object */ public function serialize($value): array { - return $this->reflection->getName() === stdClass::class - ? (array)$value + return $this->reflection->getName() === \stdClass::class + ? (array) $value : $this->marshaller->marshal($value); } @@ -120,8 +119,8 @@ protected function emptyInstance(): object */ protected function instance(array $data): object { - return $this->reflection->getName() === stdClass::class - ? (object)$data + return $this->reflection->getName() === \stdClass::class + ? (object) $data : $this->marshaller->unmarshal($data, $this->reflection->newInstanceWithoutConstructor()); } } diff --git a/src/Internal/Marshaller/Type/OneOfType.php b/src/Internal/Marshaller/Type/OneOfType.php index b0aade50a..9338d0acb 100644 --- a/src/Internal/Marshaller/Type/OneOfType.php +++ b/src/Internal/Marshaller/Type/OneOfType.php @@ -11,7 +11,6 @@ namespace Temporal\Internal\Marshaller\Type; -use stdClass; use Temporal\Internal\Marshaller\MarshallerInterface; /** @@ -74,7 +73,7 @@ public function parse(mixed $value, mixed $current): ?object ? $current : $this->emptyInstance($dtoClass); - if ($dtoClass === stdClass::class) { + if ($dtoClass === \stdClass::class) { foreach ($value as $key => $val) { $current->$key = $val; } diff --git a/src/Internal/Marshaller/Type/Type.php b/src/Internal/Marshaller/Type/Type.php index cf21baf56..e92981659 100644 --- a/src/Internal/Marshaller/Type/Type.php +++ b/src/Internal/Marshaller/Type/Type.php @@ -25,9 +25,8 @@ abstract class Type implements TypeInterface * @param MarshallerInterface $marshaller */ public function __construct( - protected MarshallerInterface $marshaller - ) { - } + protected MarshallerInterface $marshaller, + ) {} /** * @param MarshallerInterface $marshaller diff --git a/src/Internal/Marshaller/Type/UuidType.php b/src/Internal/Marshaller/Type/UuidType.php index 6f68c321a..afcd091ad 100644 --- a/src/Internal/Marshaller/Type/UuidType.php +++ b/src/Internal/Marshaller/Type/UuidType.php @@ -13,7 +13,6 @@ use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; -use ReflectionNamedType; use Temporal\Internal\Marshaller\MarshallingRule; use Temporal\Internal\Support\Inheritance; @@ -32,7 +31,7 @@ public static function makeRule(\ReflectionProperty $property): ?MarshallingRule { $type = $property->getType(); - if (!$type instanceof ReflectionNamedType || !self::match($type)) { + if (!$type instanceof \ReflectionNamedType || !self::match($type)) { return null; } diff --git a/src/Internal/Marshaller/TypeFactory.php b/src/Internal/Marshaller/TypeFactory.php index bc679ab22..d5b21575d 100644 --- a/src/Internal/Marshaller/TypeFactory.php +++ b/src/Internal/Marshaller/TypeFactory.php @@ -19,7 +19,6 @@ use Temporal\Internal\Marshaller\Type\EnumType; use Temporal\Internal\Marshaller\Type\EnumValueType; use Temporal\Internal\Marshaller\Type\ObjectType; -use Temporal\Internal\Marshaller\Type\OneOfType; use Temporal\Internal\Marshaller\Type\RuleFactoryInterface as TypeRuleFactoryInterface; use Temporal\Internal\Marshaller\Type\TypeInterface; use Temporal\Internal\Marshaller\Type\UuidType; @@ -128,7 +127,7 @@ private function createMatchers(iterable $matchers): void } if (\is_subclass_of($matcher, DetectableTypeInterface::class, true)) { - $this->matchers[] = static fn (\ReflectionNamedType $type): ?string => $matcher::match($type) + $this->matchers[] = static fn(\ReflectionNamedType $type): ?string => $matcher::match($type) ? $matcher : null; } @@ -136,7 +135,7 @@ private function createMatchers(iterable $matchers): void } /** - * @return iterable> + * @return iterable> */ private function getDefaultMatchers(): iterable { @@ -147,7 +146,6 @@ private function getDefaultMatchers(): iterable yield UuidType::class; yield ArrayType::class; yield EncodedCollectionType::class; - yield OneOfType::class; yield ObjectType::class; } } diff --git a/src/Internal/Promise/CancellationQueue.php b/src/Internal/Promise/CancellationQueue.php index f4b5478f7..b7ba16ac8 100644 --- a/src/Internal/Promise/CancellationQueue.php +++ b/src/Internal/Promise/CancellationQueue.php @@ -13,16 +13,6 @@ class CancellationQueue private bool $started = false; private array $queue = []; - public function __invoke() - { - if ($this->started) { - return; - } - - $this->started = true; - $this->drain(); - } - public function enqueue(mixed $cancellable): void { if (!\is_object($cancellable) @@ -34,11 +24,21 @@ public function enqueue(mixed $cancellable): void $length = \array_push($this->queue, $cancellable); - if ($this->started && 1 === $length) { + if ($this->started && $length === 1) { $this->drain(); } } + public function __invoke(): void + { + if ($this->started) { + return; + } + + $this->started = true; + $this->drain(); + } + private function drain(): void { for ($i = \key($this->queue); isset($this->queue[$i]); $i++) { diff --git a/src/Internal/Promise/Reasons.php b/src/Internal/Promise/Reasons.php index 91ed47daf..460be2a85 100644 --- a/src/Internal/Promise/Reasons.php +++ b/src/Internal/Promise/Reasons.php @@ -5,9 +5,7 @@ namespace Temporal\Internal\Promise; use ArrayAccess; -use Countable; use Iterator; -use RuntimeException; use Traversable; /** @@ -18,7 +16,7 @@ * @implements Iterator * @implements ArrayAccess */ -final class Reasons extends RuntimeException implements Iterator, ArrayAccess, Countable +final class Reasons extends \RuntimeException implements \Iterator, \ArrayAccess, \Countable { /** * @param array $collection @@ -46,7 +44,7 @@ public function key(): string|int|null public function valid(): bool { - return null !== \key($this->collection); + return \key($this->collection) !== null; } public function rewind(): void @@ -63,7 +61,7 @@ public function offsetExists(mixed $offset): bool * @param TKey $offset * @return TValue */ - public function offsetGet(mixed $offset): Traversable + public function offsetGet(mixed $offset): \Traversable { return $this->collection[$offset]; } diff --git a/src/Internal/Queue/ArrayQueue.php b/src/Internal/Queue/ArrayQueue.php index 165777e9e..abdcb34ae 100644 --- a/src/Internal/Queue/ArrayQueue.php +++ b/src/Internal/Queue/ArrayQueue.php @@ -23,9 +23,7 @@ class ArrayQueue implements QueueInterface /** * Queue constructor. */ - public function __construct() - { - } + public function __construct() {} /** * @param int $commandId diff --git a/src/Internal/ServiceContainer.php b/src/Internal/ServiceContainer.php index 2385706da..f14191519 100644 --- a/src/Internal/ServiceContainer.php +++ b/src/Internal/ServiceContainer.php @@ -33,6 +33,7 @@ final class ServiceContainer { /** @var RepositoryInterface */ public readonly RepositoryInterface $workflows; + public readonly ProcessCollection $running; public readonly ActivityCollection $activities; public readonly WorkflowReader $workflowsReader; diff --git a/src/Internal/Support/DateInterval.php b/src/Internal/Support/DateInterval.php index 34c677a62..8c84a154c 100644 --- a/src/Internal/Support/DateInterval.php +++ b/src/Internal/Support/DateInterval.php @@ -16,34 +16,22 @@ /** * @psalm-type DateIntervalFormat = DateInterval::FORMAT_* - * @psalm-type DateIntervalValue = string | int | float | \DateInterval + * @psalm-type DateIntervalValue = string | int | float | \DateInterval | Duration */ final class DateInterval { public const FORMAT_YEARS = 'years'; - public const FORMAT_MONTHS = 'months'; - public const FORMAT_WEEKS = 'weeks'; - public const FORMAT_DAYS = 'days'; - public const FORMAT_HOURS = 'hours'; - public const FORMAT_MINUTES = 'minutes'; - public const FORMAT_SECONDS = 'seconds'; - public const FORMAT_MILLISECONDS = 'milliseconds'; - public const FORMAT_MICROSECONDS = 'microseconds'; - public const FORMAT_NANOSECONDS = 'nanoseconds'; - private const ERROR_INVALID_DATETIME = 'Unrecognized date time interval format'; - private const ERROR_INVALID_FORMAT = 'Invalid date interval format "%s", available formats: %s'; - private const AVAILABLE_FORMATS = [ self::FORMAT_YEARS, self::FORMAT_MONTHS, @@ -112,6 +100,12 @@ public static function parse($interval, string $format = self::FORMAT_MILLISECON seconds: $seconds % 60, microseconds: $micros % 1000_000, ); + + case $interval instanceof Duration: + return self::parse( + $interval->getSeconds() * 1e6 + $interval->getNanos() / 1e3, + self::FORMAT_MICROSECONDS, + ); default: throw new \InvalidArgumentException(self::ERROR_INVALID_DATETIME); } @@ -144,7 +138,7 @@ public static function assert($interval): bool /** * @param \DateInterval|null $i - * @return Duration|null + * @return ($i is null ? null : Duration) */ public static function toDuration(\DateInterval $i = null): ?Duration { @@ -154,7 +148,7 @@ public static function toDuration(\DateInterval $i = null): ?Duration $d = new Duration(); $parsed = self::parse($i); - $d->setSeconds((int)$parsed->totalSeconds); + $d->setSeconds((int) $parsed->totalSeconds); $d->setNanos($parsed->microseconds * 1000); return $d; diff --git a/src/Internal/Support/Diff.php b/src/Internal/Support/Diff.php index 49496f1fb..caa1233a7 100644 --- a/src/Internal/Support/Diff.php +++ b/src/Internal/Support/Diff.php @@ -114,7 +114,7 @@ public function getDefaultProperties(): array public function getPresentProperties(object $context): array { $changed = $this->getChangedPropertyNames($context); - $filter = static fn ($_, string $name): bool => !\in_array($name, $changed, true); + $filter = static fn($_, string $name): bool => !\in_array($name, $changed, true); return \array_filter($this->properties, $filter, \ARRAY_FILTER_USE_BOTH); } @@ -139,7 +139,7 @@ public function getChangedProperties(object $context): array $result = []; foreach ($this->properties as $name => $value) { - if ($context->$name !== $value) { + if ($value !== $context->$name) { $result[$name] = $value; } } @@ -171,7 +171,7 @@ private function matchContext(object $context): void private function isChangedAnyProperty(object $context): bool { foreach ($this->properties as $name => $value) { - if ($context->$name !== $value) { + if ($value !== $context->$name) { return true; } } diff --git a/src/Internal/Support/Facade.php b/src/Internal/Support/Facade.php index 724054ddc..6d860bf21 100644 --- a/src/Internal/Support/Facade.php +++ b/src/Internal/Support/Facade.php @@ -39,18 +39,6 @@ private function __construct() // Unable to create new facade instance } - /** - * @param string $name - * @param array $arguments - * @return mixed - */ - public static function __callStatic(string $name, array $arguments) - { - $context = self::getCurrentContext(); - - return $context->$name(...$arguments); - } - /** * @param object|null $ctx * @internal @@ -85,4 +73,16 @@ public static function getContextId(): int return \spl_object_id(self::$ctx); } + + /** + * @param string $name + * @param array $arguments + * @return mixed + */ + public static function __callStatic(string $name, array $arguments) + { + $context = self::getCurrentContext(); + + return $context->$name(...$arguments); + } } diff --git a/src/Internal/Support/Inheritance.php b/src/Internal/Support/Inheritance.php index 716f87e79..132351fb7 100644 --- a/src/Internal/Support/Inheritance.php +++ b/src/Internal/Support/Inheritance.php @@ -24,7 +24,7 @@ public static function uses(string $haystack, string $trait): bool return true; } - foreach ((array)\class_uses($haystack) as $used) { + foreach ((array) \class_uses($haystack) as $used) { if ($used === $trait) { return true; } @@ -76,7 +76,7 @@ public static function implements(string $haystack, string $interface): bool return true; } - foreach ((array)\class_implements($haystack) as $implements) { + foreach ((array) \class_implements($haystack) as $implements) { if ($implements === $interface) { return true; } diff --git a/src/Internal/Support/Options.php b/src/Internal/Support/Options.php index bac6b3b47..dc0e1cd6c 100644 --- a/src/Internal/Support/Options.php +++ b/src/Internal/Support/Options.php @@ -28,6 +28,15 @@ public function __construct() $this->diff = new Diff($this); } + /** + * @return static + */ + #[Pure] + public static function new(): static + { + return new static(); + } + /** * @return array */ @@ -41,13 +50,4 @@ public function __debugInfo(): array return $properties; } - - /** - * @return static - */ - #[Pure] - public static function new(): static - { - return new static(); - } } diff --git a/src/Internal/Support/Process.php b/src/Internal/Support/Process.php index 3492fd1a1..9eb0d7222 100644 --- a/src/Internal/Support/Process.php +++ b/src/Internal/Support/Process.php @@ -33,6 +33,6 @@ public static function run(string ...$cmd): string throw new ProcessFailedException($process); } - return trim($process->getOutput()); + return \trim($process->getOutput()); } } diff --git a/src/Internal/Support/Reflection.php b/src/Internal/Support/Reflection.php index ddaa6309c..734332ab9 100644 --- a/src/Internal/Support/Reflection.php +++ b/src/Internal/Support/Reflection.php @@ -58,7 +58,7 @@ public static function orderArguments(\ReflectionFunctionAbstract $method, array $isPositional => $args[$i], $isNamed => $args[$name], $parameter->isDefaultValueAvailable() => $parameter->getDefaultValue(), - default => throw new InvalidArgumentException("Missing argument `$name`.") + default => throw new InvalidArgumentException("Missing argument `$name`."), }; } diff --git a/src/Internal/Support/StackRenderer.php b/src/Internal/Support/StackRenderer.php index 88bca3e2d..d503f1001 100644 --- a/src/Internal/Support/StackRenderer.php +++ b/src/Internal/Support/StackRenderer.php @@ -46,20 +46,20 @@ public static function renderTrace(array $stackTrace): string continue; } - $path = str_replace('\\', '/', $line['file']); + $path = \str_replace('\\', '/', $line['file']); foreach (self::$ignorePaths as $str) { - if (str_contains($path, $str)) { + if (\str_contains($path, $str)) { continue 2; } } - $result[] = sprintf( + $result[] = \sprintf( '%s:%s', $line['file'] ?? '-', - $line['line'] ?? '-' + $line['line'] ?? '-', ); } - return implode("\n", $result); + return \implode("\n", $result); } } diff --git a/src/Internal/Traits/CloneWith.php b/src/Internal/Traits/CloneWith.php index 9cb4f0f08..1e6919f2c 100644 --- a/src/Internal/Traits/CloneWith.php +++ b/src/Internal/Traits/CloneWith.php @@ -12,9 +12,11 @@ trait CloneWith /** * Return a new immutable instance with the specified property value. */ - private function with(string $key, mixed $value): static { + private function with(string $key, mixed $value): static + { $new = (new \ReflectionClass($this))->newInstanceWithoutConstructor(); $new->{$key} = $value; + /** @psalm-suppress RawObjectIteration */ foreach ($this as $k => $v) { if ($k === $key) { continue; diff --git a/src/Internal/Transport/Client.php b/src/Internal/Transport/Client.php index 11a41fe98..8305de94a 100644 --- a/src/Internal/Transport/Client.php +++ b/src/Internal/Transport/Client.php @@ -32,7 +32,6 @@ final class Client implements ClientInterface private const ERROR_REQUEST_ID_DUPLICATION = 'Unable to create a new request because a ' . 'request with id %d has already been sent'; - private const ERROR_REQUEST_NOT_FOUND = 'Unable to receive a request with id %d because ' . 'a request with that identifier was not sent'; diff --git a/src/Internal/Transport/CompletableResult.php b/src/Internal/Transport/CompletableResult.php index f7e66cad5..93a81805f 100644 --- a/src/Internal/Transport/CompletableResult.php +++ b/src/Internal/Transport/CompletableResult.php @@ -69,7 +69,7 @@ public function __construct( WorkflowContextInterface $context, LoopInterface $loop, PromiseInterface $promise, - string $layer + string $layer, ) { $this->context = $context; $this->loop = $loop; @@ -123,36 +123,6 @@ public function promise(): PromiseInterface return $this->deferred->promise(); } - private function onFulfilled(mixed $result): void - { - $this->resolved = true; - $this->value = $result; - - $this->loop->once( - $this->layer,//LoopInterface::ON_CALLBACK, - function (): void { - Workflow::setCurrentContext($this->context); - $this->deferred->resolve($this->value); - } - ); - } - - /** - * @param \Throwable $e - */ - private function onRejected(\Throwable $e): void - { - $this->resolved = true; - - $this->loop->once( - $this->layer,// LoopInterface::ON_CALLBACK, - function () use ($e): void { - Workflow::setCurrentContext($this->context); - $this->deferred->reject($e); - } - ); - } - public function catch(callable $onRejected): PromiseInterface { return $this->promise() @@ -187,6 +157,36 @@ public function always(callable $onFulfilledOrRejected): PromiseInterface return $this->finally($this->wrapContext($onFulfilledOrRejected)); } + private function onFulfilled(mixed $result): void + { + $this->resolved = true; + $this->value = $result; + + $this->loop->once( + $this->layer,//LoopInterface::ON_CALLBACK, + function (): void { + Workflow::setCurrentContext($this->context); + $this->deferred->resolve($this->value); + }, + ); + } + + /** + * @param \Throwable $e + */ + private function onRejected(\Throwable $e): void + { + $this->resolved = true; + + $this->loop->once( + $this->layer,// LoopInterface::ON_CALLBACK, + function () use ($e): void { + Workflow::setCurrentContext($this->context); + $this->deferred->reject($e); + }, + ); + } + /** * @template TParam of mixed * @template TReturn of mixed diff --git a/src/Internal/Transport/Request/GetChildWorkflowExecution.php b/src/Internal/Transport/Request/GetChildWorkflowExecution.php index 421a4382f..d56dea7f7 100644 --- a/src/Internal/Transport/Request/GetChildWorkflowExecution.php +++ b/src/Internal/Transport/Request/GetChildWorkflowExecution.php @@ -17,6 +17,7 @@ final class GetChildWorkflowExecution extends Request { public const NAME = 'GetChildWorkflowExecution'; + /** @see ParentClosePolicy */ private int $parentClosePolicy; diff --git a/src/Internal/Transport/Request/GetVersion.php b/src/Internal/Transport/Request/GetVersion.php index 127a5335e..8bf4ddaef 100644 --- a/src/Internal/Transport/Request/GetVersion.php +++ b/src/Internal/Transport/Request/GetVersion.php @@ -25,7 +25,7 @@ final class GetVersion extends Request public function __construct( private string $changeId, private int $minSupported, - private int $maxSupported + private int $maxSupported, ) { parent::__construct( self::NAME, @@ -33,7 +33,7 @@ public function __construct( 'changeID' => $changeId, 'minSupported' => $minSupported, 'maxSupported' => $maxSupported, - ] + ], ); } diff --git a/src/Internal/Transport/Request/NewTimer.php b/src/Internal/Transport/Request/NewTimer.php index d9a1bf0f1..1cafcaa02 100644 --- a/src/Internal/Transport/Request/NewTimer.php +++ b/src/Internal/Transport/Request/NewTimer.php @@ -26,7 +26,7 @@ final class NewTimer extends Request */ public function __construct(private \DateInterval $interval) { - parent::__construct(self::NAME, ['ms' => (int)CarbonInterval::make($interval)->totalMilliseconds]); + parent::__construct(self::NAME, ['ms' => (int) CarbonInterval::make($interval)->totalMilliseconds]); } /** diff --git a/src/Internal/Transport/Request/SignalExternalWorkflow.php b/src/Internal/Transport/Request/SignalExternalWorkflow.php index 90821515d..a2f3f6aa3 100644 --- a/src/Internal/Transport/Request/SignalExternalWorkflow.php +++ b/src/Internal/Transport/Request/SignalExternalWorkflow.php @@ -35,7 +35,7 @@ public function __construct( ?string $runId, string $signal, ValuesInterface $input = null, - bool $childWorkflowOnly = false + bool $childWorkflowOnly = false, ) { $options = [ 'namespace' => $namespace, diff --git a/src/Internal/Transport/Router/DestroyWorkflow.php b/src/Internal/Transport/Router/DestroyWorkflow.php index 8bcbbfcad..538b5d5d2 100644 --- a/src/Internal/Transport/Router/DestroyWorkflow.php +++ b/src/Internal/Transport/Router/DestroyWorkflow.php @@ -24,6 +24,7 @@ class DestroyWorkflow extends WorkflowProcessAwareRoute { /** Maximum number of ticks before GC call. */ private const GC_THRESHOLD = 1000; + /** Interval between GC calls in seconds. */ private const GC_TIMEOUT_SECONDS = 30; @@ -31,7 +32,7 @@ class DestroyWorkflow extends WorkflowProcessAwareRoute public function __construct( ProcessCollection $running, - protected LoopInterface $loop + protected LoopInterface $loop, ) { $this->gc = new GarbageCollector(self::GC_THRESHOLD, self::GC_TIMEOUT_SECONDS); parent::__construct($running); @@ -60,7 +61,7 @@ public function kill(string $runId): array $process->cancel(new DestructMemorizedInstanceException()); $this->loop->once( LoopInterface::ON_FINALLY, - function () use ($process) { + function () use ($process): void { $process->destroy(); // Collect garbage if needed diff --git a/src/Internal/Transport/Router/GetWorkerInfo.php b/src/Internal/Transport/Router/GetWorkerInfo.php index 2d7a35693..a3556de8b 100644 --- a/src/Internal/Transport/Router/GetWorkerInfo.php +++ b/src/Internal/Transport/Router/GetWorkerInfo.php @@ -63,16 +63,14 @@ public function handle(ServerRequestInterface $request, array $headers, Deferred */ private function workerToArray(WorkerInterface $worker): array { - $workflowMap = function (WorkflowPrototype $workflow) { - return [ - 'Name' => $workflow->getID(), - 'Queries' => \array_keys($workflow->getQueryHandlers()), - 'Signals' => \array_keys($workflow->getSignalHandlers()), - // 'Updates' => $this->keys($workflow->getUpdateHandlers()), - ]; - }; - - $activityMap = static fn (ActivityPrototype $activity) => [ + $workflowMap = static fn(WorkflowPrototype $workflow): array => [ + 'Name' => $workflow->getID(), + 'Queries' => \array_keys($workflow->getQueryHandlers()), + 'Signals' => \array_keys($workflow->getSignalHandlers()), + // 'Updates' => $this->keys($workflow->getUpdateHandlers()), + ]; + + $activityMap = static fn(ActivityPrototype $activity): array => [ 'Name' => $activity->getID(), ]; @@ -84,7 +82,7 @@ private function workerToArray(WorkerInterface $worker): array // ActivityInfo[] 'Activities' => $this->map($worker->getActivities(), $activityMap), 'PhpSdkVersion' => SdkVersion::getSdkVersion(), - 'Flags' => (object)[], + 'Flags' => (object) [], ]; } diff --git a/src/Internal/Transport/Router/InvokeLocalActivity.php b/src/Internal/Transport/Router/InvokeLocalActivity.php index 2b9719877..0bafd7e6c 100644 --- a/src/Internal/Transport/Router/InvokeLocalActivity.php +++ b/src/Internal/Transport/Router/InvokeLocalActivity.php @@ -14,6 +14,4 @@ /** * For cases if we would like to have different logic for local activity. */ -final class InvokeLocalActivity extends InvokeActivity -{ -} +final class InvokeLocalActivity extends InvokeActivity {} diff --git a/src/Internal/Transport/Router/InvokeQuery.php b/src/Internal/Transport/Router/InvokeQuery.php index a18c6e42f..87a1fc8dc 100644 --- a/src/Internal/Transport/Router/InvokeQuery.php +++ b/src/Internal/Transport/Router/InvokeQuery.php @@ -95,7 +95,7 @@ private function findQueryHandlerOrFail(WorkflowInstanceInterface $instance, str \sprintf( self::ERROR_QUERY_NOT_FOUND, $name, - \implode(' ', $instance->getQueryHandlerNames()) + \implode(' ', $instance->getQueryHandlerNames()), ), ); } diff --git a/src/Internal/Transport/Router/InvokeUpdate.php b/src/Internal/Transport/Router/InvokeUpdate.php index bfc5ba218..b7282cd25 100644 --- a/src/Internal/Transport/Router/InvokeUpdate.php +++ b/src/Internal/Transport/Router/InvokeUpdate.php @@ -59,7 +59,7 @@ public function handle(ServerRequestInterface $request, array $headers, Deferred // Validation - $isReplay = (bool)($request->getOptions()['replay'] ?? false); + $isReplay = (bool) ($request->getOptions()['replay'] ?? false); if ($isReplay) { // On replay, we don't need to execute validation handlers $context->getClient()->send(new UpdateResponse( @@ -90,7 +90,7 @@ public function handle(ServerRequestInterface $request, array $headers, Deferred values: null, failure: $e, updateId: $updateId, - ) + ), ); return; } @@ -127,7 +127,7 @@ static function (\Throwable $err) use ($updateId, $context): void { private function getUpdateHandler(WorkflowInstanceInterface $instance, string $name): \Closure { return $instance->findUpdateHandler($name) ?? throw new \LogicException( - \sprintf(self::ERROR_HANDLER_NOT_FOUND, $name, \implode(' ', $instance->getUpdateHandlerNames())) + \sprintf(self::ERROR_HANDLER_NOT_FOUND, $name, \implode(' ', $instance->getUpdateHandlerNames())), ); } } diff --git a/src/Internal/Transport/Router/StartWorkflow.php b/src/Internal/Transport/Router/StartWorkflow.php index 4e4acc9c1..d24608d59 100644 --- a/src/Internal/Transport/Router/StartWorkflow.php +++ b/src/Internal/Transport/Router/StartWorkflow.php @@ -12,8 +12,9 @@ namespace Temporal\Internal\Transport\Router; use React\Promise\Deferred; +use Temporal\Api\Common\V1\SearchAttributes; +use Temporal\DataConverter\EncodedCollection; use Temporal\DataConverter\EncodedValues; -use Temporal\FeatureFlags; use Temporal\Interceptor\WorkflowInbound\WorkflowInput; use Temporal\Interceptor\WorkflowInboundCallsInterceptor; use Temporal\Internal\Declaration\Instantiator\WorkflowInstantiator; @@ -22,6 +23,7 @@ use Temporal\Internal\Workflow\Input; use Temporal\Internal\Workflow\Process\Process; use Temporal\Internal\Workflow\WorkflowContext; +use Temporal\Worker\FeatureFlags; use Temporal\Worker\Transport\Command\ServerRequestInterface; use Temporal\Workflow; use Temporal\Workflow\WorkflowInfo; @@ -53,8 +55,13 @@ public function handle(ServerRequestInterface $request, array $headers, Deferred $payloads = EncodedValues::sliceValues($this->services->dataConverter, $payloads, 0, $offset); } + // Search Attributes + $searchAttributes = $this->convertSearchAttributes($options['info']['SearchAttributes'] ?? null); + $options['info']['SearchAttributes'] = $searchAttributes?->getValues(); + /** @var Input $input */ $input = $this->services->marshaller->unmarshal($options, new Input()); + /** @psalm-suppress InaccessibleProperty */ $input->input = $payloads; /** @psalm-suppress InaccessibleProperty */ @@ -76,7 +83,7 @@ public function handle(ServerRequestInterface $request, array $headers, Deferred $this->services->client, $instance, $input, - $lastCompletionResult + $lastCompletionResult, ); $runId = $request->getID(); @@ -115,4 +122,30 @@ private function findWorkflowOrFail(WorkflowInfo $info): WorkflowPrototype \sprintf(self::ERROR_NOT_FOUND, $info->type->name), ); } + + private function convertSearchAttributes(?array $param): ?EncodedCollection + { + if (!\is_array($param)) { + return null; + } + + if ($param === []) { + return EncodedCollection::empty(); + } + + try { + $sa = (new SearchAttributes()); + $sa->mergeFromJsonString( + \json_encode($param), + true, + ); + + return EncodedCollection::fromPayloadCollection( + $sa->getIndexedFields(), + $this->services->dataConverter, + ); + } catch (\Throwable) { + return null; + } + } } diff --git a/src/Internal/Transport/Router/WorkflowProcessAwareRoute.php b/src/Internal/Transport/Router/WorkflowProcessAwareRoute.php index 908c35ff1..fff39c2e1 100644 --- a/src/Internal/Transport/Router/WorkflowProcessAwareRoute.php +++ b/src/Internal/Transport/Router/WorkflowProcessAwareRoute.php @@ -20,7 +20,7 @@ abstract class WorkflowProcessAwareRoute extends Route private const ERROR_PROCESS_NOT_FOUND = 'Workflow with the specified run identifier "%s" not found'; public function __construct( - protected RepositoryInterface $running + protected RepositoryInterface $running, ) {} /** diff --git a/src/Internal/Transport/Server.php b/src/Internal/Transport/Server.php index a7a1f63f0..b8a3e401a 100644 --- a/src/Internal/Transport/Server.php +++ b/src/Internal/Transport/Server.php @@ -69,7 +69,8 @@ public function dispatch(ServerRequestInterface $request, array $headers): void $result instanceof PromiseInterface or throw new \BadMethodCallException(\sprintf( self::ERROR_INVALID_RETURN_TYPE, - PromiseInterface::class, \get_debug_type($result), + PromiseInterface::class, + \get_debug_type($result), )); $result->then($this->onFulfilled($request), $this->onRejected($request)); diff --git a/src/Internal/Workflow/ActivityProxy.php b/src/Internal/Workflow/ActivityProxy.php index 3405e3a7e..c529dfca4 100644 --- a/src/Internal/Workflow/ActivityProxy.php +++ b/src/Internal/Workflow/ActivityProxy.php @@ -129,7 +129,7 @@ private function findPrototypeByHandlerNameOrFail(string $name): ActivityPrototy if ($prototype === null) { throw new \BadMethodCallException( - \sprintf(self::ERROR_UNDEFINED_ACTIVITY_METHOD, $this->class, $name) + \sprintf(self::ERROR_UNDEFINED_ACTIVITY_METHOD, $this->class, $name), ); } diff --git a/src/Internal/Workflow/ActivityStub.php b/src/Internal/Workflow/ActivityStub.php index 949b8c8a1..09d3e5a17 100644 --- a/src/Internal/Workflow/ActivityStub.php +++ b/src/Internal/Workflow/ActivityStub.php @@ -28,6 +28,7 @@ final class ActivityStub implements ActivityStubInterface { /** @var MarshallerInterface */ private MarshallerInterface $marshaller; + private ActivityOptionsInterface $options; private HeaderInterface $header; @@ -69,7 +70,7 @@ public function execute( string $name, array $args = [], Type|string|\ReflectionClass|\ReflectionType $returnType = null, - bool $isLocalActivity = false + bool $isLocalActivity = false, ): PromiseInterface { $request = $isLocalActivity ? new ExecuteLocalActivity($name, EncodedValues::fromValues($args), $this->getOptionsArray(), $this->header) : diff --git a/src/Internal/Workflow/ChildWorkflowProxy.php b/src/Internal/Workflow/ChildWorkflowProxy.php index 20e8f6266..4410db93e 100644 --- a/src/Internal/Workflow/ChildWorkflowProxy.php +++ b/src/Internal/Workflow/ChildWorkflowProxy.php @@ -13,7 +13,6 @@ use React\Promise\PromiseInterface; use Temporal\DataConverter\Type; -use Temporal\Internal\Client\WorkflowProxy; use Temporal\Internal\Declaration\Prototype\WorkflowPrototype; use Temporal\Internal\Support\Reflection; use Temporal\Internal\Transport\CompletableResultInterface; @@ -25,10 +24,8 @@ final class ChildWorkflowProxy extends Proxy { private const ERROR_UNDEFINED_WORKFLOW_METHOD = 'The given stub class "%s" does not contain a workflow method named "%s"'; - private const ERROR_UNDEFINED_METHOD = 'The given stub class "%s" does not contain a workflow or signal method named "%s"'; - private const ERROR_UNSUPPORTED_METHOD = 'The method named "%s" (%s) cannot be executed from a child workflow stub. ' . 'Only workflow and signal methods are allowed'; @@ -43,8 +40,7 @@ public function __construct( private readonly WorkflowPrototype $workflow, private readonly ChildWorkflowOptions $options, private readonly WorkflowContextInterface $context, - ) { - } + ) {} /** * @param non-empty-string $method @@ -61,7 +57,7 @@ public function __call(string $method, array $args): PromiseInterface if ($method !== $handler?->getName()) { throw new \BadMethodCallException( - \sprintf(self::ERROR_UNDEFINED_WORKFLOW_METHOD, $this->class, $method) + \sprintf(self::ERROR_UNDEFINED_WORKFLOW_METHOD, $this->class, $method), ); } @@ -70,7 +66,7 @@ public function __call(string $method, array $args): PromiseInterface // - #[CronSchedule] $options = $this->options->mergeWith( $this->workflow->getMethodRetry(), - $this->workflow->getCronSchedule() + $this->workflow->getCronSchedule(), ); $this->stub = $this->context->newUntypedChildWorkflowStub( @@ -97,12 +93,12 @@ public function __call(string $method, array $args): PromiseInterface // Otherwise, we try to find a suitable workflow "query" method. foreach ($this->workflow->getQueryHandlers() as $name => $definition) { $definition->method->getName() === $method and throw new \BadMethodCallException( - \sprintf(self::ERROR_UNSUPPORTED_METHOD, $method, $name) + \sprintf(self::ERROR_UNSUPPORTED_METHOD, $method, $name), ); } throw new \BadMethodCallException( - \sprintf(self::ERROR_UNDEFINED_METHOD, $this->class, $method) + \sprintf(self::ERROR_UNDEFINED_METHOD, $this->class, $method), ); } diff --git a/src/Internal/Workflow/ChildWorkflowStub.php b/src/Internal/Workflow/ChildWorkflowStub.php index 06404bfaa..c8733bf4f 100644 --- a/src/Internal/Workflow/ChildWorkflowStub.php +++ b/src/Internal/Workflow/ChildWorkflowStub.php @@ -88,7 +88,7 @@ function (ValuesInterface $values): mixed { $this->execution->resolve($execution); return $execution; - } + }, ); return EncodedValues::decodePromise($started); @@ -120,9 +120,7 @@ public function getOptions(): ChildWorkflowOptions */ public function signal(string $name, array $args = []): PromiseInterface { - $execution = $this->execution->promise(); - - return $execution->then( + return $this->execution->promise()->then( function (WorkflowExecution $execution) use ($name, $args) { $request = new SignalExternalWorkflow( $this->getOptions()->namespace, @@ -134,7 +132,7 @@ function (WorkflowExecution $execution) use ($name, $args) { ); return $this->request($request); - } + }, ); } diff --git a/src/Internal/Workflow/ContinueAsNewProxy.php b/src/Internal/Workflow/ContinueAsNewProxy.php index 7903d494c..3c3949936 100644 --- a/src/Internal/Workflow/ContinueAsNewProxy.php +++ b/src/Internal/Workflow/ContinueAsNewProxy.php @@ -66,7 +66,7 @@ public function __construct( string $class, WorkflowPrototype $workflow, ContinueAsNewOptions $options, - WorkflowContextInterface $context + WorkflowContextInterface $context, ) { $this->class = $class; $this->workflow = $workflow; @@ -83,7 +83,7 @@ public function __call(string $method, array $args) { if ($this->isContinued()) { throw new \BadMethodCallException( - \sprintf(self::ERROR_ALREADY_CONTINUED, $this->workflow->getID()) + \sprintf(self::ERROR_ALREADY_CONTINUED, $this->workflow->getID()), ); } @@ -91,7 +91,7 @@ public function __call(string $method, array $args) if ($method !== $handler?->getName()) { throw new \BadMethodCallException( - \sprintf(self::ERROR_UNDEFINED_WORKFLOW_METHOD, $this->class, $method) + \sprintf(self::ERROR_UNDEFINED_WORKFLOW_METHOD, $this->class, $method), ); } diff --git a/src/Internal/Workflow/ExternalWorkflowProxy.php b/src/Internal/Workflow/ExternalWorkflowProxy.php index ed908ab57..91f511c8e 100644 --- a/src/Internal/Workflow/ExternalWorkflowProxy.php +++ b/src/Internal/Workflow/ExternalWorkflowProxy.php @@ -69,7 +69,7 @@ public function __call(string $method, array $args): PromiseInterface } throw new \BadMethodCallException( - \sprintf(self::ERROR_INVALID_SIGNAL_METHOD, $method, $this->class) + \sprintf(self::ERROR_INVALID_SIGNAL_METHOD, $method, $this->class), ); } } diff --git a/src/Internal/Workflow/ExternalWorkflowStub.php b/src/Internal/Workflow/ExternalWorkflowStub.php index 67ae2cecc..a8d3af5a3 100644 --- a/src/Internal/Workflow/ExternalWorkflowStub.php +++ b/src/Internal/Workflow/ExternalWorkflowStub.php @@ -33,8 +33,7 @@ final class ExternalWorkflowStub implements ExternalWorkflowStubInterface public function __construct( private WorkflowExecution $execution, private Pipeline $callsInterceptor, - ) { - } + ) {} /** * {@inheritDoc} diff --git a/src/Internal/Workflow/Process/DeferredGenerator.php b/src/Internal/Workflow/Process/DeferredGenerator.php index 7edc74875..880732341 100644 --- a/src/Internal/Workflow/Process/DeferredGenerator.php +++ b/src/Internal/Workflow/Process/DeferredGenerator.php @@ -63,7 +63,7 @@ public function throw(\Throwable $exception): void { $this->started or throw new \LogicException('Cannot throw exception into a generator that was not started.'); $this->finished and throw new \LogicException( - 'Cannot throw exception into a generator that was already finished.' + 'Cannot throw exception into a generator that was already finished.', ); try { $this->generator->throw($exception); diff --git a/src/Internal/Workflow/Process/Process.php b/src/Internal/Workflow/Process/Process.php index 58f9bff4d..bbf30e9aa 100644 --- a/src/Internal/Workflow/Process/Process.php +++ b/src/Internal/Workflow/Process/Process.php @@ -17,7 +17,6 @@ use Temporal\DataConverter\ValuesInterface; use Temporal\Exception\DestructMemorizedInstanceException; use Temporal\Exception\Failure\CanceledFailure; -use Temporal\FeatureFlags; use Temporal\Interceptor\WorkflowInbound\QueryInput; use Temporal\Interceptor\WorkflowInbound\SignalInput; use Temporal\Interceptor\WorkflowInbound\UpdateInput; @@ -27,6 +26,7 @@ use Temporal\Internal\ServiceContainer; use Temporal\Internal\Workflow\Input; use Temporal\Internal\Workflow\WorkflowContext; +use Temporal\Worker\FeatureFlags; use Temporal\Worker\LoopInterface; use Temporal\Workflow; use Temporal\Workflow\HandlerUnfinishedPolicy as HandlerPolicy; @@ -78,7 +78,7 @@ function (UpdateInput $input) use ($handler): void { $this->scopeContext->getInfo(), $input->arguments, $input->header, - ) + ), )); $handler($input->arguments); }, @@ -104,7 +104,7 @@ function (UpdateInput $input) use ($handler): void { ); $scope->startUpdate( - function () use ($handler, $inboundPipeline, $input): mixed { + static function () use ($handler, $inboundPipeline, $input): mixed { return $inboundPipeline->with( static fn(UpdateInput $input): mixed => $handler($input->arguments), /** @see WorkflowInboundCallsInterceptor::handleUpdate() */ @@ -128,7 +128,7 @@ function (string $name, callable $handler, ValuesInterface $arguments) use ($inb Workflow::setCurrentContext($this->scopeContext); $inboundPipeline->with( - function (SignalInput $input) use ($handler) { + function (SignalInput $input) use ($handler): void { $this->createScope( true, LoopInterface::ON_SIGNAL, @@ -156,7 +156,7 @@ function (?\Throwable $error): void { $arguments, $this->scopeContext->getHeader(), )); - } + }, ); // unlike other scopes Process will notify the server when complete instead of pushing the result @@ -174,7 +174,7 @@ function (\Throwable $e): void { /** * @param \Closure(ValuesInterface): mixed $handler */ - public function start(\Closure $handler, ValuesInterface $values = null, bool $deferred): void + public function start(\Closure $handler, ?ValuesInterface $values, bool $deferred): void { try { $this->makeCurrent(); diff --git a/src/Internal/Workflow/Process/Scope.php b/src/Internal/Workflow/Process/Scope.php index e0a050c22..f36f2aeff 100644 --- a/src/Internal/Workflow/Process/Scope.php +++ b/src/Internal/Workflow/Process/Scope.php @@ -91,7 +91,6 @@ class Scope implements CancellationScopeInterface, Destroyable private array $onClose = []; private bool $detached = false; - private bool $cancelled = false; public function __construct( @@ -137,7 +136,7 @@ public function getContext(): WorkflowContext /** * @param \Closure(ValuesInterface): mixed $handler */ - public function start(\Closure $handler, ValuesInterface $values = null, bool $deferred): void + public function start(\Closure $handler, ?ValuesInterface $values, bool $deferred): void { // Create a coroutine generator $this->coroutine = $this->call($handler, $values ?? EncodedValues::empty()); @@ -166,7 +165,7 @@ function (\Throwable $error) use ($resolver): void { $this->services->exceptionInterceptor->isRetryable($error) ? $this->scopeContext->panic($error) : $resolver->reject($error); - } + }, ); // Create a coroutine generator @@ -309,6 +308,13 @@ public function onAwait(Deferred $deferred): void $deferred->promise()->then($cleanup, $cleanup); } + public function destroy(): void + { + $this->scopeContext->destroy(); + $this->context->destroy(); + unset($this->coroutine); + } + /** * @param non-empty-string|null $layer */ @@ -331,7 +337,7 @@ protected function createScope( $scope->onClose( function () use ($cancelID): void { unset($this->onCancel[$cancelID]); - } + }, ); return $scope; @@ -468,7 +474,7 @@ function () use ($result): void { $this->onException($e); return; } - } + }, ); return $result; @@ -482,7 +488,7 @@ function () use ($e): void { } $this->handleError($e); - } + }, ); throw $e; @@ -491,7 +497,7 @@ function () use ($e): void { $promise ->then($onFulfilled, $onRejected) // Handle last error - ->then(null, fn (\Throwable $e) => null); + ->then(null, static fn(\Throwable $e) => null); } /** @@ -548,11 +554,4 @@ private function defer(\Closure $tick): void $this->services->loop->once($this->layer, $tick); $this->services->queue->count() === 0 and $this->services->loop->tick(); } - - public function destroy(): void - { - $this->scopeContext->destroy(); - $this->context->destroy(); - unset($this->coroutine); - } } diff --git a/src/Internal/Workflow/ScopeContext.php b/src/Internal/Workflow/ScopeContext.php index 7fdb45b73..a611aa24d 100644 --- a/src/Internal/Workflow/ScopeContext.php +++ b/src/Internal/Workflow/ScopeContext.php @@ -78,7 +78,7 @@ public function request(RequestInterface $request, bool $cancellable = true): Pr $this, $this->services->loop, $promise, - $this->scope->getLayer() + $this->scope->getLayer(), ); } @@ -87,20 +87,6 @@ public function getUpdateContext(): ?UpdateContext return $this->updateContext; } - protected function addCondition(string $conditionGroupId, callable $condition): PromiseInterface - { - $deferred = new Deferred(); - $this->parent->awaits[$conditionGroupId][] = [$condition, $deferred]; - $this->scope->onAwait($deferred); - - return new CompletableResult( - $this, - $this->services->loop, - $deferred->promise(), - $this->scope->getLayer() - ); - } - public function resolveConditions(): void { $this->parent->resolveConditions(); @@ -119,7 +105,7 @@ public function rejectConditionGroup(string $conditionGroupId): void public function upsertSearchAttributes(array $searchAttributes): void { $this->request( - new UpsertSearchAttributes($searchAttributes) + new UpsertSearchAttributes($searchAttributes), ); } @@ -129,4 +115,18 @@ public function destroy(): void parent::destroy(); unset($this->scope, $this->parent, $this->onRequest); } + + protected function addCondition(string $conditionGroupId, callable $condition): PromiseInterface + { + $deferred = new Deferred(); + $this->parent->awaits[$conditionGroupId][] = [$condition, $deferred]; + $this->scope->onAwait($deferred); + + return new CompletableResult( + $this, + $this->services->loop, + $deferred->promise(), + $this->scope->getLayer(), + ); + } } diff --git a/src/Internal/Workflow/WorkflowContext.php b/src/Internal/Workflow/WorkflowContext.php index f815552e7..680e7d4b8 100644 --- a/src/Internal/Workflow/WorkflowContext.php +++ b/src/Internal/Workflow/WorkflowContext.php @@ -11,11 +11,9 @@ namespace Temporal\Internal\Workflow; -use DateTimeInterface; use Ramsey\Uuid\UuidInterface; use React\Promise\Deferred; use React\Promise\PromiseInterface; -use RuntimeException; use Temporal\Activity\ActivityOptions; use Temporal\Activity\ActivityOptionsInterface; use Temporal\Activity\LocalActivityOptions; @@ -213,6 +211,17 @@ public function registerSignal(string $queryType, callable $handler): WorkflowCo return $this; } + /** + * {@inheritDoc} + */ + public function registerUpdate(string $name, callable $handler, ?callable $validator): static + { + $this->getWorkflowInstance()->addUpdateHandler($name, $handler); + $this->getWorkflowInstance()->addValidateUpdateHandler($name, $validator ?? static fn() => null); + + return $this; + } + /** * {@inheritDoc} */ @@ -469,7 +478,7 @@ public function newActivityStub( $activities = $this->services->activitiesReader->fromClass($class); if (isset($activities[0]) && $activities[0]->isLocalActivity() && !$options instanceof LocalActivityOptions) { - throw new RuntimeException("Local activity can be used only with LocalActivityOptions"); + throw new \RuntimeException("Local activity can be used only with LocalActivityOptions"); } return new ActivityProxy( @@ -628,7 +637,7 @@ public function uuid4(): PromiseInterface /** * {@inheritDoc} */ - public function uuid7(?DateTimeInterface $dateTime = null): PromiseInterface + public function uuid7(?\DateTimeInterface $dateTime = null): PromiseInterface { return $this->sideEffect(static fn(): UuidInterface => \Ramsey\Uuid\Uuid::uuid7($dateTime)); } @@ -697,7 +706,7 @@ function ($result) use ($conditionGroupId) { $this->resolveConditionGroup($conditionGroupId); return $result; }, - function ($reason) use ($conditionGroupId) { + function ($reason) use ($conditionGroupId): void { $this->rejectConditionGroup($conditionGroupId); // Throw the first reason // It need to avoid memory leak when the related workflow is destroyed diff --git a/src/Promise.php b/src/Promise.php index 2b2a736b0..4ff55f80f 100644 --- a/src/Promise.php +++ b/src/Promise.php @@ -14,7 +14,6 @@ use React\Promise\Exception\LengthException; use React\Promise\PromiseInterface; use Temporal\Internal\Promise\CancellationQueue; - use Temporal\Internal\Promise\Reasons; use function React\Promise\race; @@ -99,10 +98,10 @@ static function (iterable $array) use ($count, $cancellationQueue, $resolve, $re \sprintf( 'Input array must contain at least %d item%s but contains only %s item%s.', $count, - 1 === $count ? '' : 's', + $count === 1 ? '' : 's', $len, - 1 === $len ? '' : 's' - ) + $len === 1 ? '' : 's', + ), )); return; } @@ -120,7 +119,7 @@ static function (iterable $array) use ($count, $cancellationQueue, $resolve, $re $values[$i] = $val; - if (0 === --$toResolve) { + if (--$toResolve === 0) { $resolve($values); } }; @@ -132,7 +131,7 @@ static function (iterable $array) use ($count, $cancellationQueue, $resolve, $re $reasons[$i] = $reason; - if (0 === --$toReject) { + if (--$toReject === 0) { $reject(new Reasons($reasons)); } }; @@ -141,8 +140,11 @@ static function (iterable $array) use ($count, $cancellationQueue, $resolve, $re resolve($promiseOrValue)->then($fulfiller, $rejecter); } - }, $reject); - }, $cancellationQueue + }, + $reject, + ); + }, + $cancellationQueue, ); } @@ -166,7 +168,7 @@ public static function map(iterable $promises, callable $map): PromiseInterface $cancellationQueue->enqueue($promises); return new \React\Promise\Promise( - function (callable $resolve, callable $reject) use ($promises, $map, $cancellationQueue): void { + static function (callable $resolve, callable $reject) use ($promises, $map, $cancellationQueue): void { resolve($promises) ->then(static function (iterable $array) use ($map, $cancellationQueue, $resolve, $reject): void { if (!\is_array($array) || !$array) { @@ -187,7 +189,7 @@ function (callable $resolve, callable $reject) use ($promises, $map, $cancellati static function (mixed $mapped) use ($i, &$values, &$toResolve, $resolve): void { $values[$i] = $mapped; - if (0 === --$toResolve) { + if (--$toResolve === 0) { $resolve($values); } }, @@ -195,7 +197,8 @@ static function (mixed $mapped) use ($i, &$values, &$toResolve, $resolve): void ); } }, $reject); - }, $cancellationQueue + }, + $cancellationQueue, ); } @@ -218,7 +221,7 @@ public static function reduce(iterable $promises, callable $reduce, $initial = n $cancellationQueue->enqueue($promises); return new \React\Promise\Promise( - function (callable $resolve, callable $reject) use ($promises, $reduce, $initial, $cancellationQueue): void { + static function (callable $resolve, callable $reject) use ($promises, $reduce, $initial, $cancellationQueue): void { resolve($promises) ->then( static function (iterable $array) use ( @@ -261,7 +264,8 @@ static function (iterable $array) use ( }, $reject, ); - }, $cancellationQueue + }, + $cancellationQueue, ); } diff --git a/src/Worker/ActivityInvocationCache/ActivityInvocationCacheInterface.php b/src/Worker/ActivityInvocationCache/ActivityInvocationCacheInterface.php index dbf53508b..0ef3dd86e 100644 --- a/src/Worker/ActivityInvocationCache/ActivityInvocationCacheInterface.php +++ b/src/Worker/ActivityInvocationCache/ActivityInvocationCacheInterface.php @@ -6,15 +6,20 @@ use React\Promise\PromiseInterface; use Temporal\Worker\Transport\Command\ServerRequestInterface; -use Throwable; interface ActivityInvocationCacheInterface { public function clear(): void; - public function saveCompletion(string $activityMethodName, $value): void; + /** + * @param non-empty-string $activityMethodName + */ + public function saveCompletion(string $activityMethodName, mixed $value): void; - public function saveFailure(string $activityMethodName, Throwable $error): void; + /** + * @param non-empty-string $activityMethodName + */ + public function saveFailure(string $activityMethodName, \Throwable $error): void; public function canHandle(ServerRequestInterface $request): bool; diff --git a/src/Worker/ActivityInvocationCache/ActivityInvocationFailure.php b/src/Worker/ActivityInvocationCache/ActivityInvocationFailure.php index 1f8a9dcac..5abb249f9 100644 --- a/src/Worker/ActivityInvocationCache/ActivityInvocationFailure.php +++ b/src/Worker/ActivityInvocationCache/ActivityInvocationFailure.php @@ -4,28 +4,31 @@ namespace Temporal\Worker\ActivityInvocationCache; -use Throwable; - final class ActivityInvocationFailure { + /** @var class-string<\Throwable> */ public string $errorClass; + public string $errorMessage; - public function __construct(string $exceptionClass, string $exceptionMessage) - { + /** + * @param class-string<\Throwable> $exceptionClass + */ + public function __construct( + string $exceptionClass, + string $exceptionMessage, + ) { $this->errorClass = $exceptionClass; $this->errorMessage = $exceptionMessage; } - public static function fromThrowable(Throwable $error): self + public static function fromThrowable(\Throwable $error): self { - return new self(get_class($error), $error->getMessage()); + return new self($error::class, $error->getMessage()); } - public function toThrowable(): Throwable + public function toThrowable(): \Throwable { - $errorClass = $this->errorClass; - - return new $errorClass($this->errorMessage); + return new ($this->errorClass)($this->errorMessage); } } diff --git a/src/Worker/ActivityInvocationCache/ActivityInvocationResult.php b/src/Worker/ActivityInvocationCache/ActivityInvocationResult.php index e7b005bb6..76b9f0244 100644 --- a/src/Worker/ActivityInvocationCache/ActivityInvocationResult.php +++ b/src/Worker/ActivityInvocationCache/ActivityInvocationResult.php @@ -11,11 +11,10 @@ final class ActivityInvocationResult { - public function __construct(protected Payloads $payloads) - { - } + public function __construct(protected Payloads $payloads) {} - public static function fromValue(mixed $value, ?DataConverterInterface $dataConverter = null): ActivityInvocationResult { + public static function fromValue(mixed $value, ?DataConverterInterface $dataConverter = null): ActivityInvocationResult + { $value = $value instanceof EncodedValues ? $value : EncodedValues::fromValues([$value], $dataConverter); return new self($value->toPayloads()); diff --git a/src/Worker/ActivityInvocationCache/InMemoryActivityInvocationCache.php b/src/Worker/ActivityInvocationCache/InMemoryActivityInvocationCache.php index 08643bb5e..f5e8442d2 100644 --- a/src/Worker/ActivityInvocationCache/InMemoryActivityInvocationCache.php +++ b/src/Worker/ActivityInvocationCache/InMemoryActivityInvocationCache.php @@ -5,23 +5,20 @@ namespace Temporal\Worker\ActivityInvocationCache; use React\Promise\PromiseInterface; -use Spiral\Goridge\RPC\RPC; -use Spiral\RoadRunner\KeyValue\Factory; -use Spiral\RoadRunner\KeyValue\StorageInterface; use Temporal\DataConverter\DataConverter; use Temporal\DataConverter\DataConverterInterface; -use Temporal\DataConverter\EncodedValues; -use Temporal\Exception\InvalidArgumentException; -use Temporal\Worker\Transport\Command\RequestInterface; use Temporal\Worker\Transport\Command\ServerRequestInterface; -use Throwable; use function React\Promise\reject; use function React\Promise\resolve; final class InMemoryActivityInvocationCache implements ActivityInvocationCacheInterface { + /** + * @var array + */ private array $cache = []; + private DataConverterInterface $dataConverter; public function __construct(DataConverterInterface $dataConverter = null) @@ -34,12 +31,12 @@ public function clear(): void $this->cache = []; } - public function saveCompletion(string $activityMethodName, $value): void + public function saveCompletion(string $activityMethodName, mixed $value): void { $this->cache[$activityMethodName] = ActivityInvocationResult::fromValue($value, $this->dataConverter); } - public function saveFailure(string $activityMethodName, Throwable $error): void + public function saveFailure(string $activityMethodName, \Throwable $error): void { $this->cache[$activityMethodName] = ActivityInvocationFailure::fromThrowable($error); } @@ -60,14 +57,8 @@ public function execute(ServerRequestInterface $request): PromiseInterface $activityMethodName = $request->getOptions()['name']; $value = $this->cache[$activityMethodName]; - if ($value instanceof ActivityInvocationFailure) { - return reject($value->toThrowable()); - } - - if ($value instanceof ActivityInvocationResult) { - return resolve($value->toEncodedValues($this->dataConverter)); - } - - return reject(new InvalidArgumentException('Invalid cache value')); + return $value instanceof ActivityInvocationFailure + ? reject($value->toThrowable()) + : resolve($value->toEncodedValues($this->dataConverter)); } } diff --git a/src/Worker/ActivityInvocationCache/RoadRunnerActivityInvocationCache.php b/src/Worker/ActivityInvocationCache/RoadRunnerActivityInvocationCache.php index 9401aef21..05ed81952 100644 --- a/src/Worker/ActivityInvocationCache/RoadRunnerActivityInvocationCache.php +++ b/src/Worker/ActivityInvocationCache/RoadRunnerActivityInvocationCache.php @@ -12,13 +12,14 @@ use Temporal\DataConverter\DataConverterInterface; use Temporal\Exception\InvalidArgumentException; use Temporal\Worker\Transport\Command\ServerRequestInterface; -use Throwable; + use function React\Promise\reject; use function React\Promise\resolve; final class RoadRunnerActivityInvocationCache implements ActivityInvocationCacheInterface { private const CACHE_NAME = 'test'; + private StorageInterface $cache; private DataConverterInterface $dataConverter; @@ -28,13 +29,14 @@ public function __construct(string $host, string $cacheName, DataConverterInterf $this->dataConverter = $dataConverter ?? DataConverter::createDefault(); } - public function clear(): void + public static function create(DataConverterInterface $dataConverter = null): self { - $this->cache->clear(); + return new self('tcp://127.0.0.1:6001', self::CACHE_NAME, $dataConverter); } - public static function create(DataConverterInterface $dataConverter = null): self { - return new self('tcp://127.0.0.1:6001', self::CACHE_NAME, $dataConverter); + public function clear(): void + { + $this->cache->clear(); } public function saveCompletion(string $activityMethodName, $value): void @@ -42,7 +44,7 @@ public function saveCompletion(string $activityMethodName, $value): void $this->cache->set($activityMethodName, ActivityInvocationResult::fromValue($value, $this->dataConverter)); } - public function saveFailure(string $activityMethodName, Throwable $error): void + public function saveFailure(string $activityMethodName, \Throwable $error): void { $this->cache->set($activityMethodName, ActivityInvocationFailure::fromThrowable($error)); } diff --git a/src/Worker/Environment/Environment.php b/src/Worker/Environment/Environment.php index 1d83ecbda..71809387c 100644 --- a/src/Worker/Environment/Environment.php +++ b/src/Worker/Environment/Environment.php @@ -16,7 +16,6 @@ class Environment implements EnvironmentInterface { protected \DateTimeInterface $tickTime; - protected bool $isReplaying = false; public function __construct() diff --git a/src/FeatureFlags.php b/src/Worker/FeatureFlags.php similarity index 93% rename from src/FeatureFlags.php rename to src/Worker/FeatureFlags.php index d4b606b49..adb248ff4 100644 --- a/src/FeatureFlags.php +++ b/src/Worker/FeatureFlags.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Temporal; +namespace Temporal\Worker; /** * Feature flags help to smoothly introduce behavior changes that may affect existing workflows. @@ -25,6 +25,8 @@ final class FeatureFlags /** * Warn about running Signal and Update handlers on Workflow finish. * It uses `error_log()` function to output a warning message. + * + * @since SDK 2.11.0 */ public static bool $warnOnWorkflowUnfinishedHandlers = true; } diff --git a/src/Worker/LoopInterface.php b/src/Worker/LoopInterface.php index 5a1185b29..27f1999b2 100644 --- a/src/Worker/LoopInterface.php +++ b/src/Worker/LoopInterface.php @@ -37,9 +37,7 @@ interface LoopInterface extends EventListenerInterface public const ON_TICK = 'tick'; public const ON_SIGNAL = 'signal'; - public const ON_QUERY = 'query'; - public const ON_CALLBACK = 'callback'; /** diff --git a/src/Worker/Transport/Codec/JsonCodec.php b/src/Worker/Transport/Codec/JsonCodec.php index f4c317d88..fc48ef298 100644 --- a/src/Worker/Transport/Codec/JsonCodec.php +++ b/src/Worker/Transport/Codec/JsonCodec.php @@ -11,7 +11,6 @@ namespace Temporal\Worker\Transport\Codec; -use DateTimeImmutable; use Temporal\DataConverter\DataConverterInterface; use Temporal\Exception\ProtocolException; use Temporal\Worker\Transport\Codec\JsonCodec\Decoder; @@ -46,7 +45,7 @@ public function encode(iterable $commands): string $result = []; foreach ($commands as $command) { - assert($command instanceof CommandInterface); + \assert($command instanceof CommandInterface); $result[] = $this->serializer->encode($command); } @@ -67,15 +66,16 @@ public function decode(string $batch, array $headers = []): iterable $commands = \json_decode($batch, true, $this->maxDepth, \JSON_THROW_ON_ERROR); foreach ($commands as $command) { + /** @psalm-suppress ArgumentTypeCoercion */ $info = new TickInfo( - time: new DateTimeImmutable($headers['tickTime'] ?? 'now', $tz), - historyLength: (int)($headers['history_length'] ?? 0), - historySize: (int)($headers['history_size'] ?? 0), - continueAsNewSuggested: (bool)($headers['continue_as_new_suggested'] ?? false), - isReplaying: (bool)($headers['replay'] ?? false), + time: new \DateTimeImmutable($headers['tickTime'] ?? 'now', $tz), + historyLength: (int) ($headers['history_length'] ?? 0), + historySize: (int) ($headers['history_size'] ?? 0), + continueAsNewSuggested: (bool) ($headers['continue_as_new_suggested'] ?? false), + isReplaying: (bool) ($headers['replay'] ?? false), ); - assert(\is_array($command)); + \assert(\is_array($command)); yield $this->parser->decode($command, $info); } } catch (\Throwable $e) { diff --git a/src/Worker/Transport/Codec/JsonCodec/Decoder.php b/src/Worker/Transport/Codec/JsonCodec/Decoder.php index 8af5a0a31..7a8f5eaa9 100644 --- a/src/Worker/Transport/Codec/JsonCodec/Decoder.php +++ b/src/Worker/Transport/Codec/JsonCodec/Decoder.php @@ -87,7 +87,7 @@ private function parseResponse(array $data, TickInfo $info): SuccessResponseInte $payloads = new Payloads(); if (isset($data['payloads'])) { - $payloads->mergeFromString(base64_decode($data['payloads'])); + $payloads->mergeFromString(\base64_decode($data['payloads'])); } return new SuccessResponse(EncodedValues::fromPayloads($payloads, $this->dataConverter), $data['id'], $info); @@ -106,7 +106,7 @@ private function assertCommandID(array $data): void #[Pure] private function isUInt32( - mixed $value + mixed $value, ): bool { return \is_int($value) && $value >= 0 && $value <= 2_147_483_647; } diff --git a/src/Worker/Transport/Codec/JsonCodec/Encoder.php b/src/Worker/Transport/Codec/JsonCodec/Encoder.php index 37d5c5926..fd59fe386 100644 --- a/src/Worker/Transport/Codec/JsonCodec/Encoder.php +++ b/src/Worker/Transport/Codec/JsonCodec/Encoder.php @@ -52,13 +52,13 @@ public function encode(CommandInterface $cmd): array 'id' => $cmd->getID(), 'command' => $cmd->getName(), 'options' => $options, - 'payloads' => base64_encode($cmd->getPayloads()->toPayloads()->serializeToString()), - 'header' => base64_encode($header->toHeader()->serializeToString()), + 'payloads' => \base64_encode($cmd->getPayloads()->toPayloads()->serializeToString()), + 'header' => \base64_encode($header->toHeader()->serializeToString()), ]; if ($cmd->getFailure() !== null) { $failure = FailureConverter::mapExceptionToFailure($cmd->getFailure(), $this->converter); - $data['failure'] = base64_encode($failure->serializeToString()); + $data['failure'] = \base64_encode($failure->serializeToString()); } return $data; diff --git a/src/Worker/Transport/Codec/ProtoCodec.php b/src/Worker/Transport/Codec/ProtoCodec.php index 014295b4f..5a7ebffd9 100644 --- a/src/Worker/Transport/Codec/ProtoCodec.php +++ b/src/Worker/Transport/Codec/ProtoCodec.php @@ -11,7 +11,6 @@ namespace Temporal\Worker\Transport\Codec; -use DateTimeImmutable; use Temporal\DataConverter\DataConverterInterface; use Temporal\Exception\ProtocolException; use RoadRunner\Temporal\DTO\V1\Frame; @@ -55,7 +54,7 @@ public function encode(iterable $commands): string $messages = []; foreach ($commands as $command) { - assert($command instanceof CommandInterface); + \assert($command instanceof CommandInterface); $messages[] = $this->encoder->encode($command); } @@ -80,12 +79,13 @@ public function decode(string $batch, array $headers = []): iterable /** @var Message $msg */ foreach ($frame->getMessages() as $msg) { + /** @psalm-suppress ArgumentTypeCoercion */ $info = new TickInfo( - time: new DateTimeImmutable($headers['tickTime'] ?? $msg->getTickTime(), $tz), - historyLength: (int)($headers['history_length'] ?? $msg->getHistoryLength()), - historySize: (int)($headers['history_size'] ?? $msg->getHistorySize()), - continueAsNewSuggested: (bool)($headers['continue_as_new_suggested'] ?? $msg->getContinueAsNewSuggested()), - isReplaying: (bool)($headers['replay'] ?? $msg->getReplay()), + time: new \DateTimeImmutable($headers['tickTime'] ?? $msg->getTickTime(), $tz), + historyLength: (int) ($headers['history_length'] ?? $msg->getHistoryLength()), + historySize: (int) ($headers['history_size'] ?? $msg->getHistorySize()), + continueAsNewSuggested: (bool) ($headers['continue_as_new_suggested'] ?? $msg->getContinueAsNewSuggested()), + isReplaying: (bool) ($headers['replay'] ?? $msg->getReplay()), ); yield $this->parser->decode($msg, $info, $headers); diff --git a/src/Worker/Transport/Codec/ProtoCodec/Decoder.php b/src/Worker/Transport/Codec/ProtoCodec/Decoder.php index 21bdaa643..23f33707a 100644 --- a/src/Worker/Transport/Codec/ProtoCodec/Decoder.php +++ b/src/Worker/Transport/Codec/ProtoCodec/Decoder.php @@ -28,7 +28,7 @@ class Decoder { public function __construct( - private readonly DataConverterInterface $dataConverter + private readonly DataConverterInterface $dataConverter, ) {} public function decode(Message $msg, TickInfo $info): ServerRequestInterface|ServerResponse diff --git a/src/Worker/Transport/Codec/ProtoCodec/Encoder.php b/src/Worker/Transport/Codec/ProtoCodec/Encoder.php index 838d2cf7c..f059074a8 100644 --- a/src/Worker/Transport/Codec/ProtoCodec/Encoder.php +++ b/src/Worker/Transport/Codec/ProtoCodec/Encoder.php @@ -29,7 +29,7 @@ class Encoder private const ERROR_INVALID_COMMAND = 'Unserializable command type %s'; public function __construct( - private readonly DataConverterInterface $converter + private readonly DataConverterInterface $converter, ) {} public function encode(CommandInterface $cmd): Message diff --git a/src/Worker/Transport/Command/Client/UpdateResponse.php b/src/Worker/Transport/Command/Client/UpdateResponse.php index ac4afced6..1b10adcaa 100644 --- a/src/Worker/Transport/Command/Client/UpdateResponse.php +++ b/src/Worker/Transport/Command/Client/UpdateResponse.php @@ -24,8 +24,7 @@ public function __construct( private ?ValuesInterface $values, private readonly ?\Throwable $failure, private string|int $updateId, - ) { - } + ) {} public function getID(): int { diff --git a/src/Worker/Transport/Command/RequestInterface.php b/src/Worker/Transport/Command/RequestInterface.php index 5feb45e6f..d7ef99601 100644 --- a/src/Worker/Transport/Command/RequestInterface.php +++ b/src/Worker/Transport/Command/RequestInterface.php @@ -13,7 +13,6 @@ use Temporal\DataConverter\ValuesInterface; use Temporal\Interceptor\HeaderInterface; -use Temporal\Internal\Interceptor\HeaderCarrier; /** * @psalm-type RequestOptions = array diff --git a/src/Worker/Transport/Command/ResponseInterface.php b/src/Worker/Transport/Command/ResponseInterface.php index ff2b22bc8..34266e928 100644 --- a/src/Worker/Transport/Command/ResponseInterface.php +++ b/src/Worker/Transport/Command/ResponseInterface.php @@ -11,6 +11,4 @@ namespace Temporal\Worker\Transport\Command; -interface ResponseInterface extends CommandInterface -{ -} +interface ResponseInterface extends CommandInterface {} diff --git a/src/Worker/Transport/Command/Server/ServerRequest.php b/src/Worker/Transport/Command/Server/ServerRequest.php index 2535752ea..5efeb6f9c 100644 --- a/src/Worker/Transport/Command/Server/ServerRequest.php +++ b/src/Worker/Transport/Command/Server/ServerRequest.php @@ -29,9 +29,9 @@ class ServerRequest implements ServerRequestInterface { use RequestTrait; - private string $id; protected ValuesInterface $payloads; protected HeaderInterface $header; + private string $id; /** * @param non-empty-string $name diff --git a/src/Worker/Transport/Command/Server/ServerResponse.php b/src/Worker/Transport/Command/Server/ServerResponse.php index cc4bc94c4..3bebb396e 100644 --- a/src/Worker/Transport/Command/Server/ServerResponse.php +++ b/src/Worker/Transport/Command/Server/ServerResponse.php @@ -18,8 +18,7 @@ abstract class ServerResponse implements ServerResponseInterface public function __construct( private readonly string|int $id, private readonly TickInfo $info, - ) { - } + ) {} public function getID(): string|int { diff --git a/src/Worker/Transport/RoadRunner.php b/src/Worker/Transport/RoadRunner.php index 0e6eae36f..8d57d8e28 100644 --- a/src/Worker/Transport/RoadRunner.php +++ b/src/Worker/Transport/RoadRunner.php @@ -54,7 +54,7 @@ public function __construct(RoadRunnerWorker $worker) */ public static function create( EnvironmentInterface $env = null, - RoadRunnerVersionChecker $versionChecker = null + RoadRunnerVersionChecker $versionChecker = null, ): HostConnectionInterface { $versionChecker ??= new RoadRunnerVersionChecker(); $versionChecker->check(); @@ -78,7 +78,7 @@ public function waitBatch(): ?CommandBatch return new CommandBatch( $payload->body, - $this->decodeHeaders($payload->header) + $this->decodeHeaders($payload->header), ); } @@ -102,7 +102,7 @@ public function send(string $frame, array $headers = []): void public function error(\Throwable $error): void { try { - $this->worker->error((string)$error); + $this->worker->error((string) $error); } catch (\Throwable $e) { throw new TransportException($e->getMessage(), $e->getCode(), $e); } diff --git a/src/Worker/Transport/RoadRunnerVersionChecker.php b/src/Worker/Transport/RoadRunnerVersionChecker.php index 130d08222..bf70cba40 100644 --- a/src/Worker/Transport/RoadRunnerVersionChecker.php +++ b/src/Worker/Transport/RoadRunnerVersionChecker.php @@ -22,27 +22,26 @@ final class RoadRunnerVersionChecker { public function __construct( private readonly VersionChecker $checker = new VersionChecker(), - private readonly LoggerInterface $logger = new Logger() - ) { - } + private readonly LoggerInterface $logger = new Logger(), + ) {} - public function check(): void + public static function postUpdate(Event $event): void { + $checker = new VersionChecker(); + try { - $this->checker->greaterThan(); + $checker->greaterThan(); } catch (UnsupportedVersionException|RoadrunnerNotInstalledException $e) { - $this->logger->warning($e->getMessage()); + $event->getIO()->warning($e->getMessage()); } } - public static function postUpdate(Event $event): void + public function check(): void { - $checker = new VersionChecker(); - try { - $checker->greaterThan(); + $this->checker->greaterThan(); } catch (UnsupportedVersionException|RoadrunnerNotInstalledException $e) { - $event->getIO()->warning($e->getMessage()); + $this->logger->warning($e->getMessage()); } } } diff --git a/src/Worker/Worker.php b/src/Worker/Worker.php index f37a98d38..ea3697e03 100644 --- a/src/Worker/Worker.php +++ b/src/Worker/Worker.php @@ -11,7 +11,6 @@ namespace Temporal\Worker; -use Closure; use React\Promise\PromiseInterface; use Temporal\Internal\Events\EventEmitterTrait; use Temporal\Internal\Events\EventListenerInterface; @@ -126,7 +125,7 @@ public function getWorkflows(): RepositoryInterface public function registerActivityImplementations(object ...$activity): WorkerInterface { foreach ($activity as $act) { - $this->registerActivity(\get_class($act), fn() => $act); + $this->registerActivity(\get_class($act), static fn() => $act); } return $this; @@ -136,7 +135,7 @@ public function registerActivity(string $type, callable $factory = null): Worker { foreach ($this->services->activitiesReader->fromClass($type) as $proto) { if ($factory !== null) { - $proto = $proto->withFactory($factory instanceof Closure ? $factory : Closure::fromCallable($factory)); + $proto = $proto->withFactory($factory instanceof \Closure ? $factory : \Closure::fromCallable($factory)); } $this->services->activities->add($proto, false); } @@ -144,7 +143,7 @@ public function registerActivity(string $type, callable $factory = null): Worker return $this; } - public function registerActivityFinalizer(Closure $finalizer): WorkerInterface + public function registerActivityFinalizer(\Closure $finalizer): WorkerInterface { $this->services->activities->addFinalizer($finalizer); diff --git a/src/Worker/WorkerInterface.php b/src/Worker/WorkerInterface.php index dcb28463e..626be561c 100644 --- a/src/Worker/WorkerInterface.php +++ b/src/Worker/WorkerInterface.php @@ -11,7 +11,6 @@ namespace Temporal\Worker; -use Closure; use Temporal\Internal\Declaration\Prototype\ActivityPrototype; use Temporal\Internal\Declaration\Prototype\WorkflowPrototype; use Temporal\Internal\Repository\Identifiable; @@ -42,7 +41,7 @@ public function registerWorkflowTypes(string ...$class): self; * Register activity finalizer which is a callback being called after each activity. This * can be used to clean up resources in your application. */ - public function registerActivityFinalizer(Closure $finalizer): self; + public function registerActivityFinalizer(\Closure $finalizer): self; /** * Returns list of registered workflow prototypes. diff --git a/src/WorkerFactory.php b/src/WorkerFactory.php index 80fffcd8b..e6550f88f 100644 --- a/src/WorkerFactory.php +++ b/src/WorkerFactory.php @@ -287,21 +287,6 @@ public function tick(): void $this->emit(LoopInterface::ON_FINALLY); } - /** - * @return void - */ - private function boot(): void - { - $this->reader = $this->createReader(); - $this->marshaller = $this->createMarshaller($this->reader); - $this->queues = $this->createTaskQueue(); - $this->router = $this->createRouter(); - $this->responses = $this->createQueue(); - $this->client = $this->createClient(); - $this->server = $this->createServer(); - $this->env = new Environment(); - } - /** * @return ReaderInterface */ @@ -367,6 +352,21 @@ protected function createMarshaller(ReaderInterface $reader): MarshallerInterfac return new Marshaller(new AttributeMapperFactory($reader)); } + /** + * @return void + */ + private function boot(): void + { + $this->reader = $this->createReader(); + $this->marshaller = $this->createMarshaller($this->reader); + $this->queues = $this->createTaskQueue(); + $this->router = $this->createRouter(); + $this->responses = $this->createQueue(); + $this->client = $this->createClient(); + $this->server = $this->createServer(); + $this->env = new Environment(); + } + /** * @return CodecInterface */ @@ -419,7 +419,7 @@ private function onRequest(ServerRequestInterface $request, array $headers): Pro } $queue = $this->findTaskQueueOrFail( - $this->findTaskQueueNameOrFail($headers) + $this->findTaskQueueNameOrFail($headers), ); return $queue->dispatch($request, $headers); @@ -454,7 +454,7 @@ private function findTaskQueueNameOrFail(array $headers): string [ self::HEADER_TASK_QUEUE, \get_debug_type($taskQueue), - ] + ], ); throw new \InvalidArgumentException($error); diff --git a/src/Workflow.php b/src/Workflow.php index ade7e9baf..6e296d466 100644 --- a/src/Workflow.php +++ b/src/Workflow.php @@ -11,7 +11,6 @@ namespace Temporal; -use DateTimeInterface; use Ramsey\Uuid\UuidInterface; use React\Promise\PromiseInterface; use Temporal\Activity\ActivityOptions; @@ -334,17 +333,12 @@ public static function getLastCompletionResult($type = null) } /** - * A method that allows you to dynamically register additional query - * handler in a workflow during the execution of a workflow. + * Register a Query handler in the Workflow. * * ```php - * #[WorkflowMethod] - * public function handler() - * { - * Workflow::registerQuery('query', function(string $argument) { - * echo sprintf('Executed query "query" with argument "%s"', $argument); - * }); - * } + * Workflow::registerQuery('query', function(string $argument) { + * echo sprintf('Executed query "query" with argument "%s"', $argument); + * }); * ``` * * The same method ({@see WorkflowStubInterface::query()}) should be used @@ -361,19 +355,12 @@ public static function registerQuery(string $queryType, callable $handler): Scop } /** - * Registers a query with an additional signal handler. - * - * The method is similar to the {@see Workflow::registerQuery()}, but it - * registers an additional signal handler. + * Registers a Signal handler in the Workflow. * * ```php - * #[WorkflowMethod] - * public function handler() - * { - * Workflow::registerSignal('signal', function(string $argument) { - * echo sprintf('Executed signal "signal" with argument "%s"', $argument); - * }); - * } + * Workflow::registerSignal('signal', function(string $argument) { + * echo sprintf('Executed signal "signal" with argument "%s"', $argument); + * }); * ``` * * The same method ({@see WorkflowStubInterface::signal()}) should be used @@ -389,6 +376,42 @@ public static function registerSignal(string $queryType, callable $handler): Sco return self::getCurrentContext()->registerSignal($queryType, $handler); } + /** + * Registers an Update method in the Workflow. + * + * ```php + * Workflow::registerUpdate( + * 'pushTask', + * fn(Task $task) => $this->queue->push($task), + * ); + * ``` + * + * Register an Update method with a validator: + * + * ```php + * Workflow::registerUpdate( + * 'pushTask', + * fn(Task $task) => $this->queue->push($task), + * fn(Task $task) => $this->isValidTask($task) or throw new \InvalidArgumentException('Invalid task'), + * ); + * ``` + * + * @param non-empty-string $name + * @param callable $handler Handler function to execute the update. + * @param callable|null $validator Validator function to check the input. It should throw an exception + * if the input is invalid. + * Note that the validator must have the same parameters as the handler. + * @throws OutOfContextException in the absence of the workflow execution context. + * @since 2.11.0 + */ + public static function registerUpdate( + string $name, + callable $handler, + ?callable $validator = null, + ): ScopedContextInterface { + return self::getCurrentContext()->registerUpdate($name, $handler, $validator); + } + /** * Updates the behavior of an existing workflow to resolve inconsistency errors during replay. * @@ -506,7 +529,7 @@ public static function timer($interval): PromiseInterface public static function continueAsNew( string $type, array $args = [], - ContinueAsNewOptions $options = null + ContinueAsNewOptions $options = null, ): PromiseInterface { return self::getCurrentContext()->continueAsNew($type, $args, $options); } @@ -959,13 +982,13 @@ public static function uuid4(): PromiseInterface /** * Generate a UUID version 7 (Unix Epoch time). * - * @param DateTimeInterface|null $dateTime An optional date/time from which + * @param \DateTimeInterface|null $dateTime An optional date/time from which * to create the version 7 UUID. If not provided, the UUID is generated * using the current date/time. * * @return PromiseInterface */ - public static function uuid7(?DateTimeInterface $dateTime = null): PromiseInterface + public static function uuid7(?\DateTimeInterface $dateTime = null): PromiseInterface { /** @var ScopedContextInterface $context */ $context = self::getCurrentContext(); diff --git a/src/Workflow/ActivityStubInterface.php b/src/Workflow/ActivityStubInterface.php index 4f8dbd9d1..26ea7c2ae 100644 --- a/src/Workflow/ActivityStubInterface.php +++ b/src/Workflow/ActivityStubInterface.php @@ -36,6 +36,6 @@ public function execute( string $name, array $args = [], Type|string|\ReflectionClass|\ReflectionType $returnType = null, - bool $isLocalActivity = false + bool $isLocalActivity = false, ): PromiseInterface; } diff --git a/src/Workflow/ChildWorkflowOptions.php b/src/Workflow/ChildWorkflowOptions.php index 75ba5f24d..50d27494e 100644 --- a/src/Workflow/ChildWorkflowOptions.php +++ b/src/Workflow/ChildWorkflowOptions.php @@ -20,6 +20,7 @@ use Temporal\Common\RetryOptions; use Temporal\Exception\FailedCancellationException; use Temporal\Internal\Marshaller\Meta\Marshal; +use Temporal\Internal\Marshaller\Meta\MarshalAssocArray; use Temporal\Internal\Marshaller\Type\ArrayType; use Temporal\Internal\Marshaller\Type\ChildWorkflowCancellationType as ChildWorkflowCancellationMarshalType; use Temporal\Internal\Marshaller\Type\CronType; @@ -153,7 +154,7 @@ final class ChildWorkflowOptions extends Options * * @psalm-var array|null */ - #[Marshal(name: 'SearchAttributes', type: NullableType::class, of: ArrayType::class)] + #[MarshalAssocArray(name: 'SearchAttributes', nullable: true)] public ?array $searchAttributes = null; /** @@ -262,9 +263,9 @@ public function withTaskQueue(string $taskQueue): self #[Pure] public function withWorkflowExecutionTimeout($timeout): self { - assert(DateInterval::assert($timeout)); + \assert(DateInterval::assert($timeout)); $timeout = DateInterval::parse($timeout, DateInterval::FORMAT_SECONDS); - assert($timeout->totalMicroseconds >= 0); + \assert($timeout->totalMicroseconds >= 0); $self = clone $this; $self->workflowExecutionTimeout = $timeout; @@ -284,9 +285,9 @@ public function withWorkflowExecutionTimeout($timeout): self #[Pure] public function withWorkflowRunTimeout($timeout): self { - assert(DateInterval::assert($timeout)); + \assert(DateInterval::assert($timeout)); $timeout = DateInterval::parse($timeout, DateInterval::FORMAT_SECONDS); - assert($timeout->totalMicroseconds >= 0); + \assert($timeout->totalMicroseconds >= 0); $self = clone $this; $self->workflowRunTimeout = $timeout; @@ -305,9 +306,9 @@ public function withWorkflowRunTimeout($timeout): self #[Pure] public function withWorkflowTaskTimeout($timeout): self { - assert(DateInterval::assert($timeout)); + \assert(DateInterval::assert($timeout)); $timeout = DateInterval::parse($timeout, DateInterval::FORMAT_SECONDS); - assert($timeout->totalMicroseconds >= 0 && $timeout->totalSeconds <= 60); + \assert($timeout->totalMicroseconds >= 0 && $timeout->totalSeconds <= 60); $self = clone $this; $self->workflowTaskTimeout = $timeout; diff --git a/src/Workflow/ChildWorkflowStubInterface.php b/src/Workflow/ChildWorkflowStubInterface.php index ff0e4b8f6..14186d9d4 100644 --- a/src/Workflow/ChildWorkflowStubInterface.php +++ b/src/Workflow/ChildWorkflowStubInterface.php @@ -51,7 +51,7 @@ public function start(...$args): PromiseInterface; public function getResult($returnType = null): PromiseInterface; /** - * @param string $name + * @param non-empty-string $name * @param array $args * * @return CompletableResultInterface diff --git a/src/Workflow/ContinueAsNewOptions.php b/src/Workflow/ContinueAsNewOptions.php index 08af4bfb4..159156723 100644 --- a/src/Workflow/ContinueAsNewOptions.php +++ b/src/Workflow/ContinueAsNewOptions.php @@ -105,9 +105,9 @@ public function withTaskQueue(string $taskQueue): self #[Pure] public function withWorkflowRunTimeout($timeout): self { - assert(DateInterval::assert($timeout)); + \assert(DateInterval::assert($timeout)); $timeout = DateInterval::parse($timeout, DateInterval::FORMAT_SECONDS); - assert($timeout->totalMicroseconds >= 0); + \assert($timeout->totalMicroseconds >= 0); $self = clone $this; $self->workflowRunTimeout = $timeout; @@ -126,9 +126,9 @@ public function withWorkflowRunTimeout($timeout): self #[Pure] public function withWorkflowTaskTimeout($timeout): self { - assert(DateInterval::assert($timeout)); + \assert(DateInterval::assert($timeout)); $timeout = DateInterval::parse($timeout, DateInterval::FORMAT_SECONDS); - assert($timeout->totalMicroseconds >= 0 && $timeout->totalSeconds <= 60); + \assert($timeout->totalMicroseconds >= 0 && $timeout->totalSeconds <= 60); $self = clone $this; $self->workflowTaskTimeout = $timeout; diff --git a/src/Workflow/ExternalWorkflowStubInterface.php b/src/Workflow/ExternalWorkflowStubInterface.php index a0d3b34b4..ab22cd7c3 100644 --- a/src/Workflow/ExternalWorkflowStubInterface.php +++ b/src/Workflow/ExternalWorkflowStubInterface.php @@ -12,7 +12,6 @@ namespace Temporal\Workflow; use React\Promise\PromiseInterface; -use Temporal\Internal\Transport\CompletableResultInterface; interface ExternalWorkflowStubInterface { diff --git a/src/Workflow/ResetPointInfo.php b/src/Workflow/ResetPointInfo.php index 05537a8bf..a120d1655 100644 --- a/src/Workflow/ResetPointInfo.php +++ b/src/Workflow/ResetPointInfo.php @@ -11,7 +11,6 @@ namespace Temporal\Workflow; -use DateTimeInterface; use JetBrains\PhpStorm\Immutable; /** @@ -25,9 +24,8 @@ public function __construct( public string $binaryChecksum, public string $runId, public int $firstWorkflowTaskCompletedId, - public ?DateTimeInterface $createTime, - public ?DateTimeInterface $expireTime, + public ?\DateTimeInterface $createTime, + public ?\DateTimeInterface $expireTime, public bool $resettable, - ) { - } + ) {} } diff --git a/src/Workflow/Saga.php b/src/Workflow/Saga.php index abf2142b8..736b4d1ac 100644 --- a/src/Workflow/Saga.php +++ b/src/Workflow/Saga.php @@ -78,7 +78,7 @@ function () { $sagaException = null; - for ($i = count($this->compensate) - 1; $i >= 0; $i--) { + for ($i = \count($this->compensate) - 1; $i >= 0; $i--) { $handler = $this->compensate[$i]; try { yield Workflow::asyncDetached($handler); @@ -98,7 +98,7 @@ function () { if ($sagaException !== null) { throw $sagaException; } - } + }, ); } } diff --git a/src/Workflow/SignalMethod.php b/src/Workflow/SignalMethod.php index 1169a82ed..8d11532bd 100644 --- a/src/Workflow/SignalMethod.php +++ b/src/Workflow/SignalMethod.php @@ -26,7 +26,6 @@ #[\Attribute(\Attribute::TARGET_METHOD), NamedArgumentConstructor] final class SignalMethod { - /** * @param non-empty-string|null $name Signal name. * @param HandlerUnfinishedPolicy $unfinishedPolicy Actions taken if a workflow exits with diff --git a/src/Workflow/WorkflowContextInterface.php b/src/Workflow/WorkflowContextInterface.php index acdd5bb75..05d778869 100644 --- a/src/Workflow/WorkflowContextInterface.php +++ b/src/Workflow/WorkflowContextInterface.php @@ -11,7 +11,6 @@ namespace Temporal\Workflow; -use DateTimeInterface; use Ramsey\Uuid\UuidInterface; use React\Promise\PromiseInterface; use Temporal\Activity\ActivityOptions; @@ -79,6 +78,15 @@ public function registerQuery(string $queryType, callable $handler): self; */ public function registerSignal(string $queryType, callable $handler): self; + /** + * Registers an update method with an optional validator. + * + * @see Workflow::registerUpdate() + * + * @param non-empty-string $name + */ + public function registerUpdate(string $name, callable $handler, ?callable $validator): static; + /** * Exchanges data between worker and host process. * @@ -154,7 +162,7 @@ public function timer($interval): PromiseInterface; public function continueAsNew( string $type, array $args = [], - ContinueAsNewOptions $options = null + ContinueAsNewOptions $options = null, ): PromiseInterface; /** @@ -275,7 +283,8 @@ public function executeActivity( * * @return T */ - public function newActivityStub(string $class, + public function newActivityStub( + string $class, ActivityOptionsInterface $options = null, ): object; @@ -368,13 +377,13 @@ public function uuid4(): PromiseInterface; * * @see Workflow::uuid7() * - * @param DateTimeInterface|null $dateTime An optional date/time from which + * @param \DateTimeInterface|null $dateTime An optional date/time from which * to create the version 7 UUID. If not provided, the UUID is generated * using the current date/time. * * @return PromiseInterface */ - public function uuid7(?DateTimeInterface $dateTime = null): PromiseInterface; + public function uuid7(?\DateTimeInterface $dateTime = null): PromiseInterface; /** * Create a mutex. diff --git a/src/Workflow/WorkflowExecution.php b/src/Workflow/WorkflowExecution.php index 9fb18abd7..6703c216e 100644 --- a/src/Workflow/WorkflowExecution.php +++ b/src/Workflow/WorkflowExecution.php @@ -72,7 +72,7 @@ public function toProtoWorkflowExecution(): \Temporal\Api\Common\V1\WorkflowExec { $e = new \Temporal\Api\Common\V1\WorkflowExecution(); $e->setWorkflowId($this->id); - $e->setRunId((string)$this->runId); + $e->setRunId((string) $this->runId); return $e; } diff --git a/src/Workflow/WorkflowExecutionInfo.php b/src/Workflow/WorkflowExecutionInfo.php index dc77b5839..a19221877 100644 --- a/src/Workflow/WorkflowExecutionInfo.php +++ b/src/Workflow/WorkflowExecutionInfo.php @@ -4,7 +4,6 @@ namespace Temporal\Workflow; -use DateTimeInterface; use JetBrains\PhpStorm\Immutable; use Temporal\Common\WorkerVersionStamp; use Temporal\DataConverter\EncodedCollection; @@ -25,13 +24,13 @@ final class WorkflowExecutionInfo public function __construct( public readonly WorkflowExecution $execution, public readonly WorkflowType $type, - public readonly ?DateTimeInterface $startTime, - public readonly ?DateTimeInterface $closeTime, + public readonly ?\DateTimeInterface $startTime, + public readonly ?\DateTimeInterface $closeTime, public readonly WorkflowExecutionStatus $status, public readonly int $historyLength, public readonly ?string $parentNamespaceId, public readonly ?WorkflowExecution $parentExecution, - public readonly ?DateTimeInterface $executionTime, + public readonly ?\DateTimeInterface $executionTime, public readonly EncodedCollection $memo, public readonly EncodedCollection $searchAttributes, public readonly array $autoResetPoints, @@ -39,6 +38,5 @@ public function __construct( public readonly int $stateTransitionCount, public readonly int $historySizeBytes, public readonly ?WorkerVersionStamp $mostRecentWorkerVersionStamp, - ) { - } + ) {} } diff --git a/src/Workflow/WorkflowInterface.php b/src/Workflow/WorkflowInterface.php index a1c7a8389..3caea20fb 100644 --- a/src/Workflow/WorkflowInterface.php +++ b/src/Workflow/WorkflowInterface.php @@ -26,6 +26,4 @@ * Problem is relevant for doctrine/annotations 1.11 or lower on any PHP version. */ #[\Attribute(\Attribute::TARGET_CLASS), NamedArgumentConstructor] -class WorkflowInterface -{ -} +class WorkflowInterface {} diff --git a/src/Workflow/WorkflowStub.php b/src/Workflow/WorkflowStub.php index 95eb13c69..588271210 100644 --- a/src/Workflow/WorkflowStub.php +++ b/src/Workflow/WorkflowStub.php @@ -39,7 +39,7 @@ public static function fromWorkflow(object $workflow): WorkflowStubInterface } throw new InvalidArgumentException( - \sprintf('Only workflow stubs can be started, %s given', \get_debug_type($workflow)) + \sprintf('Only workflow stubs can be started, %s given', \get_debug_type($workflow)), ); } } diff --git a/testing/src/ActivityMocker.php b/testing/src/ActivityMocker.php index ca24f1007..30c1e0b7e 100644 --- a/testing/src/ActivityMocker.php +++ b/testing/src/ActivityMocker.php @@ -6,7 +6,6 @@ use Temporal\Worker\ActivityInvocationCache\ActivityInvocationCacheInterface; use Temporal\Worker\ActivityInvocationCache\RoadRunnerActivityInvocationCache; -use Throwable; final class ActivityMocker { @@ -27,7 +26,7 @@ public function expectCompletion(string $activityMethodName, $value): void $this->cache->saveCompletion($activityMethodName, $value); } - public function expectFailure(string $activityMethodName, Throwable $error): void + public function expectFailure(string $activityMethodName, \Throwable $error): void { $this->cache->saveFailure($activityMethodName, $error); } diff --git a/testing/src/Downloader.php b/testing/src/Downloader.php index ced9d63b3..5ad6ce8ea 100644 --- a/testing/src/Downloader.php +++ b/testing/src/Downloader.php @@ -11,6 +11,7 @@ final class Downloader { public const TAG_LATEST = 'latest'; private const JAVA_SDK_URL_RELEASES = 'https://api.github.com/repos/temporalio/sdk-java/releases/'; + private Filesystem $filesystem; private HttpClientInterface $httpClient; private string $javaSdkUrl; @@ -28,28 +29,13 @@ public function __construct( }; } - private function findAsset(array $assets, string $systemPlatform, string $systemArch): array - { - foreach ($assets as $asset) { - preg_match('/^temporal-test-server_[^_]+_([^_]+)_([^.]+)\.(?:zip|tar.gz)$/', $asset['name'], $match); - [, $assetPlatform, $assetArch] = $match; - - if ($assetPlatform === $systemPlatform) { - // TODO: assetArch === systemArch (no arm builds for test server yet) - return $asset; - } - } - - throw new \RuntimeException("Asset for $systemPlatform not found"); - } - public function download(SystemInfo $systemInfo): void { $asset = $this->getAsset($systemInfo->platform, $systemInfo->arch); $assetUrl = $asset['browser_download_url']; $pathToExtractedAsset = $this->downloadAsset($assetUrl); - $targetPath = getcwd() . DIRECTORY_SEPARATOR . $systemInfo->temporalServerExecutable; + $targetPath = \getcwd() . DIRECTORY_SEPARATOR . $systemInfo->temporalServerExecutable; $this->filesystem->copy($pathToExtractedAsset . DIRECTORY_SEPARATOR . $systemInfo->temporalServerExecutable, $targetPath); $this->filesystem->chmod($targetPath, 0755); $this->filesystem->remove($pathToExtractedAsset); @@ -60,10 +46,25 @@ public function check(string $filename): bool return $this->filesystem->exists($filename); } + private function findAsset(array $assets, string $systemPlatform, string $systemArch): array + { + foreach ($assets as $asset) { + \preg_match('/^temporal-test-server_[^_]+_([^_]+)_([^.]+)\.(?:zip|tar.gz)$/', $asset['name'], $match); + [, $assetPlatform, $assetArch] = $match; + + if ($assetPlatform === $systemPlatform) { + // TODO: assetArch === systemArch (no arm builds for test server yet) + return $asset; + } + } + + throw new \RuntimeException("Asset for $systemPlatform not found"); + } + private function downloadAsset(string $assetUrl): string { $response = $this->httpClient->request('GET', $assetUrl); - $assetPath = getcwd() . DIRECTORY_SEPARATOR . basename($assetUrl); + $assetPath = \getcwd() . DIRECTORY_SEPARATOR . \basename($assetUrl); if ($this->filesystem->exists($assetPath)) { $this->filesystem->remove($assetPath); @@ -72,9 +73,9 @@ private function downloadAsset(string $assetUrl): string $this->filesystem->appendToFile($assetPath, $response->getContent()); $phar = new \PharData($assetPath); - $extractedPath = getcwd() . DIRECTORY_SEPARATOR . $phar->getFilename(); + $extractedPath = \getcwd() . DIRECTORY_SEPARATOR . $phar->getFilename(); if (!$this->filesystem->exists($extractedPath)) { - $phar->extractTo(getcwd()); + $phar->extractTo(\getcwd()); } $this->filesystem->remove($phar->getPath()); diff --git a/testing/src/Environment.php b/testing/src/Environment.php index 5fbb37206..79fcdec40 100644 --- a/testing/src/Environment.php +++ b/testing/src/Environment.php @@ -34,7 +34,7 @@ public static function create(): self new ConsoleOutput(), new Downloader(new Filesystem(), HttpClient::create([ 'headers' => [ - 'authorization' => $token ? 'token ' . $token : null + 'authorization' => $token ? 'token ' . $token : null, ], ])), SystemInfo::detect(), @@ -63,14 +63,16 @@ public function startTemporalServer(int $commandTimeout = 10): void '--dynamic-config-value', 'frontend.enableUpdateWorkflowExecution=true', '--dynamic-config-value', 'frontend.enableUpdateWorkflowExecutionAsyncAccepted=true', '--dynamic-config-value', 'system.enableEagerWorkflowStart=true', + '--search-attribute', 'foo=text', + '--search-attribute', 'bar=int', '--log-level', 'error', - '--headless' - ] + '--headless', + ], ); $this->temporalServerProcess->setTimeout($commandTimeout); $this->temporalServerProcess->start(); $this->output->writeln('done.'); - sleep(1); + \sleep(1); if (!$this->temporalServerProcess->isRunning()) { $this->output->writeln('error'); @@ -87,16 +89,16 @@ public function startTemporalTestServer(int $commandTimeout = 10): void $this->output->writeln('done.'); } - $temporalPort = parse_url(getenv('TEMPORAL_ADDRESS') ?: '127.0.0.1:7233', PHP_URL_PORT); + $temporalPort = \parse_url(\getenv('TEMPORAL_ADDRESS') ?: '127.0.0.1:7233', PHP_URL_PORT); $this->output->write('Starting Temporal test server... '); $this->temporalTestServerProcess = new Process( - [$this->systemInfo->temporalServerExecutable, $temporalPort, '--enable-time-skipping'] + [$this->systemInfo->temporalServerExecutable, $temporalPort, '--enable-time-skipping'], ); $this->temporalTestServerProcess->setTimeout($commandTimeout); $this->temporalTestServerProcess->start(); $this->output->writeln('done.'); - sleep(1); + \sleep(1); if (!$this->temporalTestServerProcess->isRunning()) { $this->output->writeln('error'); @@ -111,14 +113,14 @@ public function startTemporalTestServer(int $commandTimeout = 10): void public function startRoadRunner(string $rrCommand = null, int $commandTimeout = 10, array $envs = []): void { $this->roadRunnerProcess = new Process( - command: $rrCommand ? explode(' ', $rrCommand) : [$this->systemInfo->rrExecutable, 'serve'], - env: $envs + command: $rrCommand ? \explode(' ', $rrCommand) : [$this->systemInfo->rrExecutable, 'serve'], + env: $envs, ); $this->roadRunnerProcess->setTimeout($commandTimeout); $this->output->write('Starting RoadRunner... '); $roadRunnerStarted = false; - $this->roadRunnerProcess->start(static function ($type, $output) use (&$roadRunnerStarted) { + $this->roadRunnerProcess->start(static function ($type, $output) use (&$roadRunnerStarted): void { if ($type === Process::OUT && \str_contains($output, 'RoadRunner server started')) { $roadRunnerStarted = true; } diff --git a/testing/src/Replay/Exception/InternalServerException.php b/testing/src/Replay/Exception/InternalServerException.php index 1afcbe574..80776d26f 100644 --- a/testing/src/Replay/Exception/InternalServerException.php +++ b/testing/src/Replay/Exception/InternalServerException.php @@ -7,6 +7,4 @@ /** * Internal errors (proto parsing, marshaling). */ -final class InternalServerException extends ReplayerException -{ -} +final class InternalServerException extends ReplayerException {} diff --git a/testing/src/Replay/Exception/InvalidArgumentException.php b/testing/src/Replay/Exception/InvalidArgumentException.php index 23106c861..f0cfd685b 100644 --- a/testing/src/Replay/Exception/InvalidArgumentException.php +++ b/testing/src/Replay/Exception/InvalidArgumentException.php @@ -7,6 +7,4 @@ /** * Missing fields in the request or data corruption. */ -final class InvalidArgumentException extends ReplayerException -{ -} +final class InvalidArgumentException extends ReplayerException {} diff --git a/testing/src/Replay/Exception/NonDeterministicWorkflowException.php b/testing/src/Replay/Exception/NonDeterministicWorkflowException.php index 5f4229bcf..a8840a8c8 100644 --- a/testing/src/Replay/Exception/NonDeterministicWorkflowException.php +++ b/testing/src/Replay/Exception/NonDeterministicWorkflowException.php @@ -9,6 +9,4 @@ * * @link https://docs.temporal.io/workflows/#deterministic-constraints */ -final class NonDeterministicWorkflowException extends ReplayerException -{ -} +final class NonDeterministicWorkflowException extends ReplayerException {} diff --git a/testing/src/Replay/Exception/RPCException.php b/testing/src/Replay/Exception/RPCException.php index 7c42bf1fe..f5661da69 100644 --- a/testing/src/Replay/Exception/RPCException.php +++ b/testing/src/Replay/Exception/RPCException.php @@ -7,6 +7,4 @@ /** * PRC connection or request failed. */ -final class RPCException extends ReplayerException -{ -} +final class RPCException extends ReplayerException {} diff --git a/testing/src/Replay/WorkflowReplayer.php b/testing/src/Replay/WorkflowReplayer.php index d7f1d1a25..325f1f1ab 100644 --- a/testing/src/Replay/WorkflowReplayer.php +++ b/testing/src/Replay/WorkflowReplayer.php @@ -11,7 +11,6 @@ use Spiral\Goridge\RPC\Codec\ProtobufCodec; use Spiral\Goridge\RPC\RPC; use Spiral\RoadRunner\Environment; -use SplFileInfo; use Temporal\Api\Common\V1\WorkflowExecution; use Temporal\Api\Common\V1\WorkflowType; use Temporal\Api\History\V1\History; @@ -92,7 +91,7 @@ public function downloadHistory( * You can load a json serialized history file using {@see downloadHistory()} or via Temporal UI. * * @param non-empty-string $workflowType - * @param non-empty-string|SplFileInfo $path + * @param non-empty-string|\SplFileInfo $path * @param int<0, max> $lastEventId The last event ID to replay from. If not specified, the whole history * will be replayed. * @@ -100,12 +99,12 @@ public function downloadHistory( */ public function replayFromJSON( string $workflowType, - string|SplFileInfo $path, + string|\SplFileInfo $path, int $lastEventId = 0, ): void { $request = $this->buildRequest( workflowType: $workflowType, - filePath: $path instanceof SplFileInfo ? $path->getPathname() : $path, + filePath: $path instanceof \SplFileInfo ? $path->getPathname() : $path, lastEventId: $lastEventId, ); $this->sendRequest('temporal.ReplayFromJSON', $request); @@ -113,13 +112,16 @@ public function replayFromJSON( private function sendRequest(string $command, Message $request): ReplayResponse { - $wfType = (string)$request->getWorkflowType()?->getName(); + $wfType = (string) $request->getWorkflowType()?->getName(); try { /** @var string $result */ $result = $this->rpc->call($command, $request); } catch (\Throwable $e) { throw new RPCException( - $wfType, $e->getMessage(), (int)$e->getCode(), $e + $wfType, + $e->getMessage(), + (int) $e->getCode(), + $e, ); } @@ -135,11 +137,15 @@ private function sendRequest(string $command, Message $request): ReplayResponse throw match ($status->getCode()) { StatusCode::INVALID_ARGUMENT => new InvalidArgumentException( - $wfType, $status->getMessage(), $status->getCode(), + $wfType, + $status->getMessage(), + $status->getCode(), ), StatusCode::INTERNAL => new InternalServerException($wfType, $status->getMessage(), $status->getCode()), StatusCode::FAILED_PRECONDITION => new NonDeterministicWorkflowException( - $wfType, $status->getMessage(), $status->getCode(), + $wfType, + $status->getMessage(), + $status->getCode(), ), default => new ReplayerException($wfType, $status->getMessage(), $status->getCode()), }; @@ -156,9 +162,10 @@ private function buildRequest( ->setLastEventId($lastEventId); if ($execution !== null) { - $request->setWorkflowExecution((new WorkflowExecution()) - ->setWorkflowId($execution->getID()) - ->setRunId($execution->getRunID() ?? throw new \LogicException('Run ID is required.')) + $request->setWorkflowExecution( + (new WorkflowExecution()) + ->setWorkflowId($execution->getID()) + ->setRunId($execution->getRunID() ?? throw new \LogicException('Run ID is required.')), ); } diff --git a/testing/src/SystemInfo.php b/testing/src/SystemInfo.php index fc95ca385..02524401b 100644 --- a/testing/src/SystemInfo.php +++ b/testing/src/SystemInfo.php @@ -14,25 +14,21 @@ final class SystemInfo 'linux' => 'linux', 'windows' => 'windows', ]; - private const ARCHITECTURE_MAPPINGS = [ 'x64' => 'amd64', 'amd64' => 'amd64', - 'arm64' => 'aarch64' + 'arm64' => 'aarch64', ]; - private const TEMPORAL_EXECUTABLE_MAP = [ 'darwin' => './temporal-test-server', 'linux' => './temporal-test-server', 'windows' => 'temporal-test-server.exe', ]; - private const TEMPORAL_CLI_EXECUTABLE_MAP = [ 'darwin' => './temporal', 'linux' => './temporal', 'windows' => 'temporal.exe', ]; - private const RR_EXECUTABLE_MAP = [ 'darwin' => './rr', 'linux' => './rr', diff --git a/testing/src/TestService.php b/testing/src/TestService.php index 7a88c4645..26260bbec 100644 --- a/testing/src/TestService.php +++ b/testing/src/TestService.php @@ -29,7 +29,7 @@ public function __construct(TestServiceClient $testServiceClient) public static function create(string $host): self { return new self( - new TestServiceClient($host, ['credentials' => ChannelCredentials::createInsecure()]) + new TestServiceClient($host, ['credentials' => ChannelCredentials::createInsecure()]), ); } diff --git a/testing/src/WithoutTimeSkipping.php b/testing/src/WithoutTimeSkipping.php index 58f45605a..34a9d151b 100644 --- a/testing/src/WithoutTimeSkipping.php +++ b/testing/src/WithoutTimeSkipping.php @@ -11,7 +11,7 @@ trait WithoutTimeSkipping protected function setUp(): void { $this->testService = TestService::create( - getenv('TEMPORAL_ADDRESS') ?: '127.0.0.1:7233' + \getenv('TEMPORAL_ADDRESS') ?: '127.0.0.1:7233', ); $this->testService->lockTimeSkipping(); parent::setUp(); diff --git a/testing/src/WorkerFactory.php b/testing/src/WorkerFactory.php index 72bf4350a..1b04eaa7c 100644 --- a/testing/src/WorkerFactory.php +++ b/testing/src/WorkerFactory.php @@ -64,7 +64,8 @@ public function newWorker( $interceptorProvider ?? new SimplePipelineProvider(), ), $this->rpc, - ), $this->activityCache + ), + $this->activityCache, ); $this->queues->add($worker); diff --git a/testing/src/WorkerMock.php b/testing/src/WorkerMock.php index 0af690311..6b88fbea2 100644 --- a/testing/src/WorkerMock.php +++ b/testing/src/WorkerMock.php @@ -4,15 +4,12 @@ namespace Temporal\Testing; -use Closure; use React\Promise\PromiseInterface; use Temporal\Internal\Events\EventEmitterTrait; use Temporal\Internal\Events\EventListenerInterface; -use Temporal\Internal\Repository\Identifiable; use Temporal\Internal\Repository\RepositoryInterface; use Temporal\Worker\ActivityInvocationCache\ActivityInvocationCacheInterface; use Temporal\Worker\DispatcherInterface; -use Temporal\Worker\Transport\Command\RequestInterface; use Temporal\Worker\Transport\Command\ServerRequestInterface; use Temporal\Worker\WorkerInterface; use Temporal\Worker\WorkerOptions; @@ -26,7 +23,7 @@ final class WorkerMock implements WorkerInterface, EventListenerInterface, Dispa public function __construct( WorkerInterface $wrapped, - ActivityInvocationCacheInterface $activityInvocationCache + ActivityInvocationCacheInterface $activityInvocationCache, ) { $this->wrapped = $wrapped; $this->activityInvocationCache = $activityInvocationCache; @@ -71,7 +68,7 @@ public function registerActivity(string $type, callable $factory = null): Worker return $this->wrapped->registerActivity($type, $factory); } - public function registerActivityFinalizer(Closure $finalizer): WorkerInterface + public function registerActivityFinalizer(\Closure $finalizer): WorkerInterface { return $this->wrapped->registerActivityFinalizer($finalizer); } diff --git a/testing/src/WorkflowTestCase.php b/testing/src/WorkflowTestCase.php index a100b641d..6a64f9e72 100644 --- a/testing/src/WorkflowTestCase.php +++ b/testing/src/WorkflowTestCase.php @@ -15,7 +15,7 @@ class WorkflowTestCase extends TestCase protected function setUp(): void { - $temporalAddress = getenv('TEMPORAL_ADDRESS') ?: '127.0.0.1:7233'; + $temporalAddress = \getenv('TEMPORAL_ADDRESS') ?: '127.0.0.1:7233'; $this->workflowClient = new WorkflowClient(ServiceClient::create($temporalAddress)); $this->testingService = TestService::create($temporalAddress); diff --git a/tests/Acceptance/App/RuntimeBuilder.php b/tests/Acceptance/App/RuntimeBuilder.php index 699bd587e..f0ce64937 100644 --- a/tests/Acceptance/App/RuntimeBuilder.php +++ b/tests/Acceptance/App/RuntimeBuilder.php @@ -5,12 +5,12 @@ namespace Temporal\Tests\Acceptance\App; use PHPUnit\Framework\Attributes\Test; -use Temporal\FeatureFlags; +use Temporal\Activity\ActivityInterface; +use Temporal\DataConverter\PayloadConverterInterface; use Temporal\Tests\Acceptance\App\Input\Command; use Temporal\Tests\Acceptance\App\Input\Feature; use Temporal\Tests\Acceptance\App\Runtime\State; -use Temporal\Activity\ActivityInterface; -use Temporal\DataConverter\PayloadConverterInterface; +use Temporal\Worker\FeatureFlags; use Temporal\Workflow\WorkflowInterface; final class RuntimeBuilder diff --git a/tests/Acceptance/App/TestCase.php b/tests/Acceptance/App/TestCase.php index e7a2a9bb9..485a31853 100644 --- a/tests/Acceptance/App/TestCase.php +++ b/tests/Acceptance/App/TestCase.php @@ -12,7 +12,7 @@ use Temporal\Tests\Acceptance\App\Runtime\RRStarter; use Temporal\Tests\Acceptance\App\Runtime\State; -abstract class TestCase extends \PHPUnit\Framework\TestCase +abstract class TestCase extends \Temporal\Tests\TestCase { protected function setUp(): void { diff --git a/tests/Acceptance/Extra/Schedule/ScheduleUpdateTest.php b/tests/Acceptance/Extra/Schedule/ScheduleUpdateTest.php new file mode 100644 index 000000000..33ee56fc6 --- /dev/null +++ b/tests/Acceptance/Extra/Schedule/ScheduleUpdateTest.php @@ -0,0 +1,174 @@ +createSchedule( + Schedule::new() + ->withAction( + StartWorkflowAction::new('TestWorkflow') + )->withSpec( + ScheduleSpec::new() + ->withStartTime('+1 hour') + ), + ScheduleOptions::new() + ->withMemo(['memokey2' => 'memoval2']) + ->withSearchAttributes( + EncodedCollection::fromValues([ + 'foo' => 'bar', + 'bar' => 42, + ]) + ) + ); + + try { + $description = $handle->describe(); + self::assertEquals(2, $description->searchAttributes->count()); + + // Update the schedule search attribute by clearing them + $handle->update(function (ScheduleUpdateInput $input): ScheduleUpdate { + $schedule = $input->description->schedule; + return ScheduleUpdate::new($schedule) + ->withSearchAttributes(EncodedCollection::empty()); + }); + + sleep(1); + self::assertEquals(0, $handle->describe()->searchAttributes->count()); + } finally { + $handle->delete(); + } + } + + #[Test] + public function searchAttributesAddViaUpdate( + ScheduleClientInterface $client, + ): void + { + // Create a new schedule + $handle = $client->createSchedule( + Schedule::new() + ->withAction( + StartWorkflowAction::new('TestWorkflow') + )->withSpec( + ScheduleSpec::new() + ->withStartTime('+1 hour') + ), + ScheduleOptions::new() + ->withMemo(['memokey2' => 'memoval2']) + ->withSearchAttributes( + EncodedCollection::fromValues([ + 'foo' => 'bar', + ]) + ) + ); + + try { + $description = $handle->describe(); + self::assertEquals(1, $description->searchAttributes->count()); + + // Update the schedule search attribute by clearing them + $handle->update(function (ScheduleUpdateInput $input): ScheduleUpdate { + $schedule = $input->description->schedule; + return ScheduleUpdate::new($schedule) + ->withSearchAttributes($input->description->searchAttributes->withValue('bar', 69)); + }); + + sleep(1); + self::assertEquals(2, $handle->describe()->searchAttributes->count()); + self::assertSame(69, $handle->describe()->searchAttributes->getValue('bar')); + } finally { + $handle->delete(); + } + } + + #[Test] + public function update( + ScheduleClientInterface $client, + ): void { + // Create a new schedule + $handle = $client->createSchedule( + Schedule::new() + ->withAction( + StartWorkflowAction::new('TestWorkflow') + ->withMemo(['memokey1' => 'memoval1']) + )->withSpec( + ScheduleSpec::new() + ->withStartTime('+1 hour') + ), + ScheduleOptions::new() + ->withMemo(['memokey2' => 'memoval2']) + ->withSearchAttributes(EncodedCollection::fromValues([ + 'foo' => 'bar', + 'bar' => 42, + ])) + ); + + try { + // Describe the schedule + $description = $handle->describe(); + self::assertSame("memoval2", $description->memo->getValue("memokey2")); + self::assertEquals(2, $description->searchAttributes->count()); + + /** @var StartWorkflowAction $startWfAction */ + $startWfAction = $description->schedule->action; + self::assertSame('memoval1', $startWfAction->memo->getValue("memokey1")); + + // Add memo and update task timeout + $handle->update(function (ScheduleUpdateInput $input): ScheduleUpdate { + $schedule = $input->description->schedule; + /** @var StartWorkflowAction $action */ + $action = $schedule->action; + $action = $action->withWorkflowTaskTimeout('7 minutes') + ->withMemo(['memokey3' => 'memoval3']); + return ScheduleUpdate::new($schedule->withAction($action)); + }); + + $description = $handle->describe(); + self::assertInstanceOf(StartWorkflowAction::class, $description->schedule->action); + self::assertSame("memoval2", $description->memo->getValue("memokey2")); + $startWfAction = $description->schedule->action; + self::assertSame("memoval3", $startWfAction->memo->getValue("memokey3")); + $this->assertEqualIntervals(new \DateInterval('PT7M'), $startWfAction->workflowTaskTimeout); + + // Update the schedule state + $expectedUpdateTime = $description->info->lastUpdateAt; + $handle->update(function (ScheduleUpdateInput $input): ScheduleUpdate { + $schedule = $input->description->schedule; + $schedule = $schedule->withState($schedule->state->withPaused(true)); + return ScheduleUpdate::new($schedule); + }); + $description = $handle->describe(); + // + self::assertSame("memoval2", $description->memo->getValue("memokey2")); + $startWfAction = $description->schedule->action; + self::assertSame("memoval3", $startWfAction->memo->getValue("memokey3")); + // + self::assertNotEquals($expectedUpdateTime, $description->info->lastUpdateAt); + self::assertTrue($description->schedule->state->paused); + self::assertEquals(2, $description->searchAttributes->count()); + self::assertSame('bar', $description->searchAttributes->getValue('foo')); + } finally { + $handle->delete(); + } + } +} diff --git a/tests/Acceptance/Extra/Update/DynamicUpdateTest.php b/tests/Acceptance/Extra/Update/DynamicUpdateTest.php new file mode 100644 index 000000000..20d06cd72 --- /dev/null +++ b/tests/Acceptance/Extra/Update/DynamicUpdateTest.php @@ -0,0 +1,91 @@ +update(TestWorkflow::UPDATE_METHOD)->getValue(0); + self::assertNotNull($idResult); + + $id = Uuid::uuid4()->toString(); + $idResult = $stub->startUpdate( + UpdateOptions::new(TestWorkflow::UPDATE_METHOD, LifecycleStage::StageCompleted) + ->withUpdateId($id) + )->getResult(); + self::assertSame($id, $idResult); + } + + #[Test] + public function addUpdateMethodWithValidation( + #[Stub('Extra_Update_DynamicUpdate')] WorkflowStubInterface $stub, + ): void { + // Valid + $result = $stub->update(TestWorkflow::UPDATE_METHOD_WV, 42)->getValue(0); + self::assertSame(42, $result); + + // Invalid input + try { + $stub->update(TestWorkflow::UPDATE_METHOD_WV, -42); + } catch (WorkflowUpdateException $e) { + $previous = $e->getPrevious(); + self::assertInstanceOf(ApplicationFailure::class, $previous); + self::assertSame('Value must be positive', $previous->getOriginalMessage()); + } + } +} + + +#[WorkflowInterface] +class TestWorkflow +{ + public const UPDATE_METHOD = 'update-method'; + public const UPDATE_METHOD_WV = 'update-method-with-validation'; + + private array $result = []; + private bool $exit = false; + + #[WorkflowMethod(name: "Extra_Update_DynamicUpdate")] + public function handle() + { + // Register update methods + Workflow::registerUpdate(self::UPDATE_METHOD, function () { + // Also Update context is tested + $id = Workflow::getUpdateContext()->getUpdateId(); + return $this->result[self::UPDATE_METHOD] = $id; + }); + // Update method with validation + Workflow::registerUpdate( + self::UPDATE_METHOD_WV, + fn(int $value): int => $value, + fn(int $value) => $value > 0 or throw new \InvalidArgumentException('Value must be positive'), + ); + + yield Workflow::await(fn() => $this->exit); + return $this->result; + } + + #[Workflow\SignalMethod] + public function exit(): void + { + $this->exit = true; + } +} diff --git a/tests/Acceptance/Extra/Workflow/WorkflowSearchAttributesTest.php b/tests/Acceptance/Extra/Workflow/WorkflowSearchAttributesTest.php new file mode 100644 index 000000000..219a00589 --- /dev/null +++ b/tests/Acceptance/Extra/Workflow/WorkflowSearchAttributesTest.php @@ -0,0 +1,88 @@ +getResult(timeout: 3); + $this->assertSame([], $result, 'Workflow result contains resolved value'); + } + + #[Test] + public function sendNullAsSearchAttributes( + #[Stub( + 'Extra_Workflow_WorkflowSearchAttributes', + args: [ + null, + ], + )] + WorkflowStubInterface $stub, + ): void { + $result = $stub->getResult(timeout: 3); + $this->assertNull($result); + } + + #[Test] + public function sendSimpleSearchAttributeSet( + #[Stub( + 'Extra_Workflow_WorkflowSearchAttributes', + args: [ + ['foo' => 'bar'], + ], + )] + WorkflowStubInterface $stub, + ): void { + $result = $stub->getResult('array', timeout: 3); + $this->assertSame(['foo' => 'bar'], $result, 'Workflow result contains resolved value'); + } +} + +#[WorkflowInterface] +class TestWorkflow +{ + #[WorkflowMethod(name: "Extra_Workflow_WorkflowSearchAttributes")] + public function handle(?array $searchAttributes): \Generator + { + return yield Workflow::newChildWorkflowStub( + TestWorkflowChild::class, + Workflow\ChildWorkflowOptions::new() + ->withSearchAttributes($searchAttributes) + )->handle(); + } +} + +#[WorkflowInterface] +class TestWorkflowChild +{ + #[WorkflowMethod(name: "Extra_Workflow_WorkflowSearchAttributes_Child")] + public function handle(): ?array + { + return Workflow::getInfo()->searchAttributes; + } +} diff --git a/tests/Acceptance/Harness/Update/AsyncAcceptTest.php b/tests/Acceptance/Harness/Update/AsyncAcceptTest.php index 19da2af7d..080906b81 100644 --- a/tests/Acceptance/Harness/Update/AsyncAcceptTest.php +++ b/tests/Acceptance/Harness/Update/AsyncAcceptTest.php @@ -41,7 +41,7 @@ public function check( # Unblock last update $stub->signal('unblock'); self::assertSame(123, $handle->getResult()); - // self::same($otherHandle->getResult(), 123); + self::assertSame(123, $otherHandle->getResult()); # issue an async update that should throw $updateId = Uuid::uuid4()->toString(); diff --git a/tests/Fixtures/src/Workflow/Inheritance/BaseWorkflowWithHandler.php b/tests/Fixtures/src/Workflow/Inheritance/BaseWorkflowWithHandler.php new file mode 100644 index 000000000..4f3deb314 --- /dev/null +++ b/tests/Fixtures/src/Workflow/Inheritance/BaseWorkflowWithHandler.php @@ -0,0 +1,23 @@ +getResult(Type::TYPE_ARRAY) + $run->getResult(Type::TYPE_ARRAY, 3) ); } diff --git a/tests/Functional/Client/UntypedWorkflowStubTestCase.php b/tests/Functional/Client/UntypedWorkflowStubTestCase.php index dd6b5d202..f1188e09a 100644 --- a/tests/Functional/Client/UntypedWorkflowStubTestCase.php +++ b/tests/Functional/Client/UntypedWorkflowStubTestCase.php @@ -18,7 +18,6 @@ use Temporal\Exception\Failure\TerminatedFailure; use Temporal\Exception\IllegalStateException; use Temporal\Exception\InvalidArgumentException; -use Temporal\Tests\Unit\Declaration\Fixture\WorkflowWithoutHandler; use Temporal\Workflow\WorkflowExecutionStatus; /** @@ -264,7 +263,7 @@ public function testSignalRunningWorkflowWithInheritedSignal() $signaller = $client->newUntypedRunningWorkflowStub($workflowId, $workflowRunId); $signaller->signal('addValue', 'test1'); - $result = $workflowRun->getResult(); + $result = $workflowRun->getResult(timeout: 10); $this->assertEquals(['test1'], $result); } } diff --git a/tests/Functional/SimpleWorkflowTestCase.php b/tests/Functional/SimpleWorkflowTestCase.php index 1d9747229..5e6fb9583 100644 --- a/tests/Functional/SimpleWorkflowTestCase.php +++ b/tests/Functional/SimpleWorkflowTestCase.php @@ -10,6 +10,7 @@ use Temporal\Client\WorkflowOptions; use Temporal\Testing\ActivityMocker; use Temporal\Tests\TestCase; +use Temporal\Tests\Workflow\Inheritance\ExtendingWorkflow; use Temporal\Tests\Workflow\SimpleWorkflow; use Temporal\Tests\Workflow\YieldGeneratorWorkflow; use Temporal\Tests\Workflow\YieldScalarsWorkflow; @@ -119,6 +120,13 @@ public function testYieldGenerator(): void $this->assertSame('bar', $run->getResult()); } + public function testWorkflowMethodInAbstractParent(): void + { + $workflow = $this->workflowClient->newWorkflowStub(ExtendingWorkflow::class); + $run = $this->workflowClient->start($workflow); + $this->assertNull($run->getResult(timeout: 5)); + } + private function assertContainsEvent(WorkflowExecution $execution, int $event): void { $history = $this->workflowClient->getWorkflowHistory( diff --git a/tests/Functional/bootstrap.php b/tests/Functional/bootstrap.php index e4f4065a1..1128b7843 100644 --- a/tests/Functional/bootstrap.php +++ b/tests/Functional/bootstrap.php @@ -2,9 +2,9 @@ declare(strict_types=1); -use Temporal\FeatureFlags; use Temporal\Testing\Environment; use Temporal\Tests\SearchAttributeTestInvoker; +use Temporal\Worker\FeatureFlags; chdir(__DIR__ . '/../..'); require_once __DIR__ . '/../../vendor/autoload.php'; diff --git a/tests/Functional/worker.php b/tests/Functional/worker.php index 2ee7e73a1..0f1a2816c 100644 --- a/tests/Functional/worker.php +++ b/tests/Functional/worker.php @@ -2,11 +2,11 @@ declare(strict_types=1); -use Temporal\FeatureFlags; use Temporal\Testing\WorkerFactory; use Temporal\Tests\Fixtures\PipelineProvider; use Temporal\Tests\Interceptor\HeaderChanger; use Temporal\Tests\Interceptor\InterceptorCallsCounter; +use Temporal\Worker\FeatureFlags; use Temporal\Worker\WorkerInterface; require __DIR__ . '/../../vendor/autoload.php'; @@ -56,7 +56,12 @@ // register all workflows foreach ($getClasses(__DIR__ . '/../Fixtures/src/Workflow', 'Temporal\\Tests\\Workflow\\') as $class) { - if (class_exists($class) && !\interface_exists($class)) { + if (\class_exists($class) && !\interface_exists($class)) { + $wfRef = new \ReflectionClass($class); + if ($wfRef->isAbstract()) { + continue; + } + \array_walk( $workers, static fn (WorkerInterface $worker) => $worker->registerWorkflowTypes($class), diff --git a/tests/Unit/Protocol/EncodingTestCase.php b/tests/Unit/DataConverter/EncodedValuesTestCase.php similarity index 72% rename from tests/Unit/Protocol/EncodingTestCase.php rename to tests/Unit/DataConverter/EncodedValuesTestCase.php index 18abb9dee..e2adb381e 100644 --- a/tests/Unit/Protocol/EncodingTestCase.php +++ b/tests/Unit/DataConverter/EncodedValuesTestCase.php @@ -1,18 +1,13 @@ assertNull($encodedValues->getValue(0)); - } - public static function getNotNullableTypes(): iterable { yield [Type::create(Type::TYPE_ARRAY)]; @@ -78,6 +66,13 @@ public static function getNullableTypes(): iterable yield 'union' => [self::getReturnType(static fn(): int|string|null => null)]; } + #[Test] + public function nullValuesAreReturned(): void + { + $encodedValues = EncodedValues::fromValues([null, 'something'], new DataConverter()); + $this->assertNull($encodedValues->getValue(0)); + } + #[Test] #[DataProvider('getNullableTypes')] public function payloadWithoutValueDecoding(mixed $type): void @@ -91,7 +86,9 @@ public function payloadWithoutValueDecoding(mixed $type): void #[DataProvider('getNotNullableTypes')] public function payloadWithoutValueDecodingNotNullable(mixed $type): void { - $encodedValues = EncodedValues::fromPayloadCollection(new \ArrayIterator([])); + $encodedValues = EncodedValues::fromPayloadCollection(new \ArrayIterator([ + new Payloads(), + ])); self::expectException(\LogicException::class); self::expectExceptionMessage('DataConverter is not set'); @@ -99,6 +96,44 @@ public function payloadWithoutValueDecodingNotNullable(mixed $type): void $encodedValues->getValue(0, $type); } + public function testEmpty(): void + { + $ev = EncodedValues::empty(); + + $this->assertInstanceOf(EncodedValues::class, $ev); + $this->assertEmpty($ev->getValues()); + $this->assertNull($ev->getValue(0)); + } + + public function testGetValuesFromEmptyPayloads(): void + { + $dataConverter = new DataConverter(); + $ev = EncodedValues::fromPayloads(new Payloads(), $dataConverter); + + $this->assertInstanceOf(EncodedValues::class, $ev); + $this->assertEmpty($ev->getValues()); + $this->assertNull($ev->getValue(0)); + } + + public function testGetValueFromEmptyValues(): void + { + $ev = EncodedValues::fromValues([]); + + $this->assertInstanceOf(EncodedValues::class, $ev); + $this->assertEmpty($ev->getValues()); + $this->assertNull($ev->getValue(0)); + } + + public function testOutOfBounds(): void + { + $ev = EncodedValues::fromValues([]); + + $this->expectException(\OutOfBoundsException::class); + $this->expectExceptionMessage('Index 1 is out of bounds.'); + + $ev->getValue(1); + } + private static function getReturnType(\Closure $closure): \ReflectionType { return (new \ReflectionFunction($closure))->getReturnType(); diff --git a/tests/Unit/Declaration/Fixture/Inheritance/BaseWorkflowWithHandler.php b/tests/Unit/Declaration/Fixture/Inheritance/BaseWorkflowWithHandler.php new file mode 100644 index 000000000..a878bb480 --- /dev/null +++ b/tests/Unit/Declaration/Fixture/Inheritance/BaseWorkflowWithHandler.php @@ -0,0 +1,23 @@ +assertSame('ExampleWorkflowName', $prototype->getID()); } + + public function testHierarchicalWorkflow(): void + { + $instantiator = new WorkflowInstantiator(new \Temporal\Interceptor\SimplePipelineProvider()); + + $instance = $instantiator->instantiate( + new WorkflowPrototype( + 'extending', + new \ReflectionMethod(ExtendingWorkflow::class, 'handler'), + new \ReflectionClass(ExtendingWorkflow::class), + ), + ); + + $this->assertInstanceOf(ExtendingWorkflow::class, $instance->getContext()); + } + + public function testWorkflowWithInterface(): void + { + $reader = new WorkflowReader(new AttributeReader()); + + $result = $reader->fromClass(AggregatedWorkflowImpl::class); + + $this->assertSame('AggregatedWorkflow', $result->getID()); + } + + public function testInstantiateWorkflowWithInterface(): void + { + $instantiator = new WorkflowInstantiator(new \Temporal\Interceptor\SimplePipelineProvider()); + $reader = new WorkflowReader(new AttributeReader()); + $prototype = $reader->fromClass(AggregatedWorkflowImpl::class); + + $instance = $instantiator->instantiate($prototype); + + $this->assertInstanceOf(AggregatedWorkflowImpl::class, $instance->getContext()); + $this->assertSame('AggregatedWorkflow', $prototype->getID()); + } } diff --git a/tests/Unit/Declaration/WorkflowNegativeDeclarationTestCase.php b/tests/Unit/Declaration/WorkflowNegativeDeclarationTestCase.php index c96e627b0..a28fde97a 100644 --- a/tests/Unit/Declaration/WorkflowNegativeDeclarationTestCase.php +++ b/tests/Unit/Declaration/WorkflowNegativeDeclarationTestCase.php @@ -21,7 +21,6 @@ use Temporal\Tests\Unit\Declaration\Fixture\WorkflowWithMultipleMethods; use Temporal\Tests\Unit\Declaration\Fixture\WorkflowWithoutHandler; use Temporal\Workflow\WorkflowInterface; -use Temporal\Workflow\WorkflowMethod; /** * @group unit diff --git a/tests/Unit/Exception/FailureConverterTestCase.php b/tests/Unit/Exception/FailureConverterTestCase.php index 6772d3e83..ee32ee962 100644 --- a/tests/Unit/Exception/FailureConverterTestCase.php +++ b/tests/Unit/Exception/FailureConverterTestCase.php @@ -4,7 +4,10 @@ namespace Temporal\Tests\Unit\Exception; +use Carbon\CarbonInterval; use Exception; +use Google\Protobuf\Duration; +use Temporal\Api\Failure\V1\Failure; use Temporal\DataConverter\DataConverter; use Temporal\DataConverter\EncodedValues; use Temporal\Exception\Failure\ApplicationFailure; @@ -19,13 +22,13 @@ public function testApplicationFailureCanTransferData(): void 'message', 'type', true, - EncodedValues::fromValues(['abc', 123]) + EncodedValues::fromValues(['abc', 123]), ); $failure = FailureConverter::mapExceptionToFailure($exception, DataConverter::createDefault()); $restoredDetails = EncodedValues::fromPayloads( $failure->getApplicationFailureInfo()->getDetails(), - DataConverter::createDefault() + DataConverter::createDefault(), ); $this->assertSame('abc', $restoredDetails->getValue(0)); @@ -57,7 +60,7 @@ public function testStackTraceStringForAdditionalContextEvenWhenClassIsNotPresen try { $trace = FailureConverter::mapExceptionToFailure( - call_user_func(fn () => new Exception()), + call_user_func(fn() => new Exception()), DataConverter::createDefault(), )->getStackTrace(); } finally { @@ -104,4 +107,69 @@ public function testStackTraceStringWithoutExceptionArgs(): void $trace, ); } + + public function testMapFailureToException(): void + { + $converter = DataConverter::createDefault(); + $failure = new Failure(); + $failure->setApplicationFailureInfo($info = new \Temporal\Api\Failure\V1\ApplicationFailureInfo()); + $failure->setStackTrace("test stack trace:\n#1\n#2\n#3"); + // Populate the info + $info->setType('testType'); + $info->setDetails(EncodedValues::fromValues(['foo', 'bar'], $converter)->toPayloads()); + $info->setNonRetryable(true); + $info->setNextRetryDelay((new Duration())->setSeconds(13)->setNanos(15_000)); + + $exception = FailureConverter::mapFailureToException($failure, $converter); + + $this->assertInstanceOf(ApplicationFailure::class, $exception); + $this->assertSame('testType', $exception->getType()); + $this->assertTrue($exception->isNonRetryable()); + $this->assertSame(['foo', 'bar'], $exception->getDetails()->getValues()); + // Next retry delay + $this->assertSame(13, $exception->getNextRetryDelay()->seconds); + $this->assertSame(15, $exception->getNextRetryDelay()->microseconds); + $this->assertTrue($exception->hasOriginalStackTrace()); + $this->assertSame("test stack trace:\n#1\n#2\n#3", $exception->getOriginalStackTrace()); + } + + public function testMapExceptionToFailureWithNextRetryDelay(): void + { + $converter = DataConverter::createDefault(); + $exception = new ApplicationFailure( + 'message', + 'type', + true, + EncodedValues::fromValues(['foo', 'bar'], $converter), + nextRetryDelay: CarbonInterval::fromString('5 minutes 13 seconds 15 microseconds'), + ); + + $failure = FailureConverter::mapExceptionToFailure($exception, $converter); + + $this->assertSame('type', $failure->getApplicationFailureInfo()->getType()); + $this->assertTrue($failure->getApplicationFailureInfo()->getNonRetryable()); + $this->assertSame(['foo', 'bar'], EncodedValues::fromPayloads( + $failure->getApplicationFailureInfo()->getDetails(), + $converter, + )->getValues()); + $this->assertSame(5 * 60 + 13, $failure->getApplicationFailureInfo()->getNextRetryDelay()->getSeconds()); + $this->assertSame(15_000, $failure->getApplicationFailureInfo()->getNextRetryDelay()->getNanos()); + } + + public function testMapExceptionToFailure(): void + { + $converter = DataConverter::createDefault(); + $exception = new ApplicationFailure( + 'message', + 'type', + true, + ); + + $failure = FailureConverter::mapExceptionToFailure($exception, $converter); + + $this->assertSame('type', $failure->getApplicationFailureInfo()->getType()); + $this->assertTrue($failure->getApplicationFailureInfo()->getNonRetryable()); + $this->assertEmpty($failure->getApplicationFailureInfo()->getDetails()); + $this->assertNull($failure->getApplicationFailureInfo()->getNextRetryDelay()); + } } diff --git a/tests/Unit/Internal/Support/DateIntervalTestCase.php b/tests/Unit/Internal/Support/DateIntervalTestCase.php index 383f70085..5ec7dfdaa 100644 --- a/tests/Unit/Internal/Support/DateIntervalTestCase.php +++ b/tests/Unit/Internal/Support/DateIntervalTestCase.php @@ -32,6 +32,18 @@ public function testParseAndFormat(): void self::assertSame('0/0/0/6', $i->format('%y/%h/%i/%s')); } + public function testParseFromDuration(): void + { + $duration = (new \Google\Protobuf\Duration()) + ->setSeconds(5124) + ->setNanos(123456000); + + $i = DateInterval::parse($duration); + + self::assertSame(5124, (int)$i->totalSeconds); + self::assertSame(123_456, $i->microseconds); + } + public static function provideValuesToParse(): iterable { yield [1, DateInterval::FORMAT_MICROSECONDS, 1, '0/0/0/0']; diff --git a/tests/Unit/Schedule/ScheduleHandleTestCase.php b/tests/Unit/Schedule/ScheduleHandleTestCase.php index 75837e465..b81de5baf 100644 --- a/tests/Unit/Schedule/ScheduleHandleTestCase.php +++ b/tests/Unit/Schedule/ScheduleHandleTestCase.php @@ -24,8 +24,11 @@ use Temporal\Client\Schedule\Policy\ScheduleOverlapPolicy; use Temporal\Client\Schedule\Schedule; use Temporal\Client\Schedule\ScheduleHandle; +use Temporal\Client\Schedule\Update\ScheduleUpdate; +use Temporal\Client\Schedule\Update\ScheduleUpdateInput; use Temporal\DataConverter\DataConverter; use Temporal\DataConverter\DataConverterInterface; +use Temporal\DataConverter\EncodedCollection; use Temporal\Exception\InvalidArgumentException; use Temporal\Internal\Marshaller\Mapper\AttributeMapperFactory; use Temporal\Internal\Marshaller\Marshaller; @@ -222,9 +225,48 @@ public function testUpdate(): void $this->assertSame('test-id', $testContext->request->getScheduleId()); $this->assertSame('test-identity', $testContext->request->getIdentity()); $this->assertSame('test-conflict-token', $testContext->request->getConflictToken()); + $this->assertNotNull($testContext->request->getRequestId()); $this->assertNotNull($testContext->request->getSchedule()); } + public function testUpdateUsingClosureDefaults(): void + { + $testContext = new class { + public UpdateScheduleRequest $request; + }; + // Prepare mocks + $clientMock = $this->createMock(ServiceClientInterface::class); + $clientMock->expects($this->once()) + ->method('DescribeSchedule') + ->willReturn((new DescribeScheduleResponse())); + $clientMock->expects($this->once()) + ->method('UpdateSchedule') + ->with($this->callback(fn (UpdateScheduleRequest $request) => $testContext->request = $request or true)) + ->willReturn(new UpdateScheduleResponse()); + $scheduleHandle = $this->createScheduleHandle(client: $clientMock); + + $scheduleHandle->update(function (ScheduleUpdateInput $input): ScheduleUpdate { + $schedule = Schedule::new(); + $sa = EncodedCollection::fromValues(['foo' => 'bar']); + return ScheduleUpdate::new($schedule) + ->withSearchAttributes($sa); + }, 'test-conflict-token'); + + $this->assertTrue(isset($testContext->request)); + $this->assertSame('default', $testContext->request->getNamespace()); + $this->assertSame('test-id', $testContext->request->getScheduleId()); + $this->assertSame('test-identity', $testContext->request->getIdentity()); + $this->assertSame('test-conflict-token', $testContext->request->getConflictToken()); + $this->assertNotNull($testContext->request->getRequestId()); + $this->assertNotNull($testContext->request->getSchedule()); + // Search attributes + $sa = EncodedCollection::fromPayloadCollection( + $testContext->request->getSearchAttributes()->getIndexedFields(), + DataConverter::createDefault(), + )->getValues(); + $this->assertSame(['foo' => 'bar'], $sa); + } + public function testDescribe(): void { $testContext = new class {