diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 045712fb..e6ccb4cd 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -509,9 +509,6 @@ - - getInstance($prototype)]]> - @@ -527,12 +524,6 @@ - - getInstance($prototype)]]> - - - - @@ -595,12 +586,10 @@ - - @@ -615,7 +604,6 @@ - @@ -752,13 +740,9 @@ - - - - @@ -1144,10 +1128,6 @@ - - - - diff --git a/src/Internal/Declaration/Dispatcher/AutowiredPayloads.php b/src/Internal/Declaration/Dispatcher/AutowiredPayloads.php index f7a5f11e..e659c933 100644 --- a/src/Internal/Declaration/Dispatcher/AutowiredPayloads.php +++ b/src/Internal/Declaration/Dispatcher/AutowiredPayloads.php @@ -16,15 +16,11 @@ /** * @psalm-type FunctionExecutor = \Closure(object|null, array): mixed + * @internal */ class AutowiredPayloads extends Dispatcher { - /** - * @param object|null $ctx - * @param ValuesInterface $values - * @return mixed - */ - public function dispatchValues(?object $ctx, ValuesInterface $values) + public function dispatchValues(object $ctx, ValuesInterface $values): mixed { $arguments = []; for ($i = 0; $i < $values->count(); $i++) { diff --git a/src/Internal/Declaration/Dispatcher/Dispatcher.php b/src/Internal/Declaration/Dispatcher/Dispatcher.php index e50bd8ef..2db1af61 100644 --- a/src/Internal/Declaration/Dispatcher/Dispatcher.php +++ b/src/Internal/Declaration/Dispatcher/Dispatcher.php @@ -15,7 +15,7 @@ use ReflectionType; /** - * @psalm-type FunctionExecutor = \Closure(object|null, array): mixed + * @psalm-type FunctionExecutor = \Closure(object, array): mixed */ class Dispatcher implements DispatcherInterface { @@ -30,8 +30,8 @@ class Dispatcher implements DispatcherInterface public const SCOPE_STATIC = 0x02; /** + * @var \Closure(object, array): mixed * @psalm-var FunctionExecutor - * @var \Closure */ private \Closure $executor; @@ -95,12 +95,7 @@ public function getArgumentTypes(): array return $this->types; } - /** - * @param object|null $ctx - * @param array $arguments - * @return mixed - */ - public function dispatch(?object $ctx, array $arguments) + public function dispatch(object $ctx, array $arguments): mixed { return ($this->executor)($ctx, $arguments); } @@ -119,13 +114,13 @@ private function scopeMatches(int $scope): bool * @psalm-return FunctionExecutor * * @param \ReflectionMethod $fun - * @return \Closure + * @return \Closure(object, array): mixed */ private function createExecutorFromMethod(\ReflectionMethod $fun): \Closure { - return static function (?object $ctx, array $arguments) use ($fun) { + return static function (object $object, array $arguments) use ($fun) { try { - return $fun->invokeArgs($ctx, $arguments); + return $fun->invokeArgs($object, $arguments); } catch (\ReflectionException $e) { throw new \BadMethodCallException($e->getMessage(), $e->getCode(), $e); } @@ -136,15 +131,11 @@ private function createExecutorFromMethod(\ReflectionMethod $fun): \Closure * @psalm-return FunctionExecutor * * @param \ReflectionFunction $fun - * @return \Closure + * @return \Closure(object, array): mixed */ private function createExecutorFromFunction(\ReflectionFunction $fun): \Closure { - return static function (?object $ctx, array $arguments) use ($fun) { - if ($ctx === null) { - return $fun->invoke(...$arguments); - } - + return static function (object $ctx, array $arguments) use ($fun) { $closure = $fun->getClosure(); try { diff --git a/src/Internal/Declaration/Dispatcher/DispatcherInterface.php b/src/Internal/Declaration/Dispatcher/DispatcherInterface.php index 3a60fef5..695c7ec9 100644 --- a/src/Internal/Declaration/Dispatcher/DispatcherInterface.php +++ b/src/Internal/Declaration/Dispatcher/DispatcherInterface.php @@ -11,14 +11,12 @@ namespace Temporal\Internal\Declaration\Dispatcher; +/** + * @internal + */ interface DispatcherInterface { - /** - * @param object|null $ctx - * @param array $arguments - * @return mixed - */ - public function dispatch(?object $ctx, array $arguments); + public function dispatch(object $ctx, array $arguments): mixed; /** * @return array<\ReflectionType> diff --git a/src/Internal/Declaration/Instance.php b/src/Internal/Declaration/Instance.php index 44394068..f34b1d7d 100644 --- a/src/Internal/Declaration/Instance.php +++ b/src/Internal/Declaration/Instance.php @@ -21,18 +21,15 @@ */ abstract class Instance implements InstanceInterface { - protected object $context; /** * @var \Closure(ValuesInterface): mixed */ private \Closure $handler; - /** - * @param Prototype $prototype - * @param object $context - */ - public function __construct(Prototype $prototype, object $context) - { + public function __construct( + Prototype $prototype, + protected readonly object $context, + ) { $handler = $prototype->getHandler(); if ($handler === null) { @@ -42,14 +39,10 @@ public function __construct(Prototype $prototype, object $context) )); } - $this->context = $context; $this->handler = $this->createHandler($handler); } - /** - * @return object|null - */ - public function getContext(): ?object + public function getContext(): object { return $this->context; } diff --git a/src/Internal/Declaration/InstanceInterface.php b/src/Internal/Declaration/InstanceInterface.php index b8f23c99..d58c714d 100644 --- a/src/Internal/Declaration/InstanceInterface.php +++ b/src/Internal/Declaration/InstanceInterface.php @@ -23,8 +23,5 @@ interface InstanceInterface */ public function getHandler(): callable; - /** - * @return object|null - */ - public function getContext(): ?object; + public function getContext(): object; } diff --git a/src/Internal/Declaration/Instantiator/Instantiator.php b/src/Internal/Declaration/Instantiator/Instantiator.php index 89b03aca..ebde7d2c 100644 --- a/src/Internal/Declaration/Instantiator/Instantiator.php +++ b/src/Internal/Declaration/Instantiator/Instantiator.php @@ -17,24 +17,11 @@ abstract class Instantiator implements InstantiatorInterface { /** * @param PrototypeInterface $prototype - * @return \ReflectionClass|null - */ - protected function getClass(PrototypeInterface $prototype): ?\ReflectionClass - { - return $prototype->getClass(); - } - - /** - * @param PrototypeInterface $prototype - * @return object|null + * @return object * @throws \ReflectionException */ - protected function getInstance(PrototypeInterface $prototype): ?object + protected function getInstance(PrototypeInterface $prototype): object { - if ($class = $this->getClass($prototype)) { - return $class->newInstance(); - } - - return null; + return $prototype->getClass()->newInstance(); } } diff --git a/src/Internal/Declaration/Instantiator/WorkflowInstantiator.php b/src/Internal/Declaration/Instantiator/WorkflowInstantiator.php index fcb56eb4..028ee115 100644 --- a/src/Internal/Declaration/Instantiator/WorkflowInstantiator.php +++ b/src/Internal/Declaration/Instantiator/WorkflowInstantiator.php @@ -12,6 +12,7 @@ namespace Temporal\Internal\Declaration\Instantiator; use Temporal\Exception\InstantiationException; +use Temporal\Interceptor\PipelineProvider; use Temporal\Interceptor\WorkflowInboundCallsInterceptor; use Temporal\Internal\Declaration\Prototype\PrototypeInterface; use Temporal\Internal\Declaration\Prototype\WorkflowPrototype; @@ -23,7 +24,7 @@ final class WorkflowInstantiator extends Instantiator { public function __construct( - private \Temporal\Interceptor\PipelineProvider $interceptorProvider, + private PipelineProvider $interceptorProvider, ) { } @@ -43,26 +44,16 @@ public function instantiate(PrototypeInterface $prototype): WorkflowInstance /** * @param PrototypeInterface $prototype - * @return object|null + * @return object * @throws \ReflectionException */ - protected function getInstance(PrototypeInterface $prototype): ?object + protected function getInstance(PrototypeInterface $prototype): object { - $handler = $prototype->getHandler(); + $handler = $prototype->getHandler() ?? throw new InstantiationException(\sprintf( + 'Unable to instantiate workflow "%s" without handler method', + $prototype->getID(), + )); - if ($handler === null) { - throw new InstantiationException(\sprintf( - 'Unable to instantiate workflow "%s" without handler method', - $prototype->getID(), - )); - } - - $class = $handler->getDeclaringClass(); - - if ($class !== null) { - return $class->newInstanceWithoutConstructor(); - } - - return null; + return $handler->getDeclaringClass()->newInstanceWithoutConstructor(); } } diff --git a/src/Internal/Declaration/WorkflowInstance.php b/src/Internal/Declaration/WorkflowInstance.php index bbe3ca9b..5cc99271 100644 --- a/src/Internal/Declaration/WorkflowInstance.php +++ b/src/Internal/Declaration/WorkflowInstance.php @@ -11,22 +11,22 @@ namespace Temporal\Internal\Declaration; +use React\Promise\PromiseInterface; use Temporal\DataConverter\ValuesInterface; use Temporal\Interceptor\WorkflowInbound\QueryInput; use Temporal\Interceptor\WorkflowInbound\UpdateInput; use Temporal\Interceptor\WorkflowInboundCallsInterceptor; use Temporal\Internal\Declaration\Prototype\WorkflowPrototype; use Temporal\Internal\Declaration\WorkflowInstance\SignalQueue; -use Temporal\Internal\Declaration\WorkflowInstance\UpdateQueue; use Temporal\Internal\Interceptor; /** * @psalm-import-type DispatchableHandler from InstanceInterface * @psalm-type QueryHandler = \Closure(QueryInput): mixed - * @psalm-type UpdateHandler = \Closure(UpdateInput): mixed + * @psalm-type UpdateHandler = \Closure(UpdateInput): PromiseInterface * @psalm-type ValidateUpdateHandler = \Closure(UpdateInput): void * @psalm-type QueryExecutor = \Closure(QueryInput, callable(ValuesInterface): mixed): mixed - * @psalm-type UpdateExecutor = \Closure(UpdateInput, callable(ValuesInterface): mixed): mixed + * @psalm-type UpdateExecutor = \Closure(UpdateInput, callable(ValuesInterface): mixed): PromiseInterface * @psalm-type ValidateUpdateExecutor = \Closure(UpdateInput, callable(ValuesInterface): mixed): mixed * @psalm-type UpdateValidator = \Closure(UpdateInput, UpdateHandler): void */ @@ -65,7 +65,7 @@ final class WorkflowInstance extends Instance implements WorkflowInstanceInterfa /** * @param WorkflowPrototype $prototype - * @param object $context + * @param object $context Workflow object * @param Interceptor\Pipeline $pipeline */ public function __construct( @@ -141,7 +141,7 @@ public function setUpdateValidator(\Closure $validator): self */ public function initConstructor(): void { - if (method_exists($this->context, '__construct')) { + if (\method_exists($this->context, '__construct')) { $this->context->__construct(); } } @@ -156,8 +156,8 @@ public function getSignalQueue(): SignalQueue /** * @param non-empty-string $name - * @return null|\Closure(ValuesInterface):mixed * + * @return null|\Closure(QueryInput): mixed * @psalm-return QueryHandler|null */ public function findQueryHandler(string $name): ?\Closure @@ -166,8 +166,10 @@ public function findQueryHandler(string $name): ?\Closure } /** - * @param string $name - * @return \Closure + * @param non-empty-string $name + * + * @return null|\Closure(UpdateInput): PromiseInterface + * @psalm-return UpdateHandler|null */ public function findUpdateHandler(string $name): ?\Closure { @@ -176,6 +178,9 @@ public function findUpdateHandler(string $name): ?\Closure /** * @param non-empty-string $name + * + * @return null|\Closure(UpdateInput): void + * @psalm-return ValidateUpdateHandler|null */ public function findValidateUpdateHandler(string $name): ?\Closure { @@ -234,10 +239,6 @@ public function getUpdateHandlerNames(): array return \array_keys($this->updateHandlers); } - /** - * @param string $name - * @return \Closure - */ public function getSignalHandler(string $name): \Closure { return fn (ValuesInterface $values) => $this->signalQueue->push($name, $values); diff --git a/src/Internal/Declaration/WorkflowInstance/SignalQueue.php b/src/Internal/Declaration/WorkflowInstance/SignalQueue.php index afa98fbf..13e3b17d 100644 --- a/src/Internal/Declaration/WorkflowInstance/SignalQueue.php +++ b/src/Internal/Declaration/WorkflowInstance/SignalQueue.php @@ -36,7 +36,7 @@ final class SignalQueue private $onSignal; /** - * @param string $signal + * @param non-empty-string $signal * @param ValuesInterface $values */ public function push(string $signal, ValuesInterface $values): void diff --git a/src/Internal/Declaration/WorkflowInstanceInterface.php b/src/Internal/Declaration/WorkflowInstanceInterface.php index 1b07f3cf..1c9862f8 100644 --- a/src/Internal/Declaration/WorkflowInstanceInterface.php +++ b/src/Internal/Declaration/WorkflowInstanceInterface.php @@ -11,6 +11,11 @@ namespace Temporal\Internal\Declaration; +use React\Promise\PromiseInterface; +use Temporal\DataConverter\ValuesInterface; +use Temporal\Interceptor\WorkflowInbound\QueryInput; +use Temporal\Interceptor\WorkflowInbound\UpdateInput; + interface WorkflowInstanceInterface extends InstanceInterface { /** @@ -19,8 +24,8 @@ interface WorkflowInstanceInterface extends InstanceInterface public function initConstructor(): void; /** - * @param string $name - * @return \Closure|null + * @param non-empty-string $name + * @return null|\Closure(QueryInput): mixed */ public function findQueryHandler(string $name): ?\Closure; @@ -37,14 +42,14 @@ public function addQueryHandler(string $name, callable $handler): void; public function addUpdateHandler(string $name, callable $handler): void; /** - * @param string $name - * @return \Closure + * @param non-empty-string $name + * @return \Closure(ValuesInterface): void */ public function getSignalHandler(string $name): \Closure; /** * @param non-empty-string $name - * @return \Closure + * @return null|\Closure(UpdateInput): PromiseInterface */ public function findUpdateHandler(string $name): ?\Closure; diff --git a/src/Internal/Marshaller/Type/ArrayType.php b/src/Internal/Marshaller/Type/ArrayType.php index 3edadda6..271331a4 100644 --- a/src/Internal/Marshaller/Type/ArrayType.php +++ b/src/Internal/Marshaller/Type/ArrayType.php @@ -69,11 +69,11 @@ public static function makeRule(\ReflectionProperty $property): ?MarshallingRule } /** - * @param array $value + * @psalm-assert array $value + * @param mixed $value * @param array $current - * @return array|mixed */ - public function parse($value, $current) + public function parse($value, $current): array { if (!\is_array($value)) { throw new \InvalidArgumentException(\sprintf(self::ERROR_INVALID_TYPE, \get_debug_type($value))); diff --git a/src/Internal/Transport/Router/InvokeQuery.php b/src/Internal/Transport/Router/InvokeQuery.php index 4036ddda..9ce45a1a 100644 --- a/src/Internal/Transport/Router/InvokeQuery.php +++ b/src/Internal/Transport/Router/InvokeQuery.php @@ -52,6 +52,7 @@ public function __construct( */ public function handle(ServerRequestInterface $request, array $headers, Deferred $resolver): void { + /** @var non-empty-string $name */ $name = $request->getOptions()['name']; $process = $this->findProcessOrFail($request->getID()); $context = $process->getContext(); @@ -79,19 +80,17 @@ static function () use ($name, $request, $resolver, $handler, $context): void { /** * @param WorkflowInstanceInterface $instance - * @param string $name - * @return \Closure + * @param non-empty-string $name + * @return \Closure(QueryInput): mixed */ private function findQueryHandlerOrFail(WorkflowInstanceInterface $instance, string $name): \Closure { - $handler = $instance->findQueryHandler($name); - - if ($handler === null) { - $available = \implode(' ', $instance->getQueryHandlerNames()); - - throw new \LogicException(\sprintf(self::ERROR_QUERY_NOT_FOUND, $name, $available)); - } - - return $handler; + return $instance->findQueryHandler($name) ?? throw new \LogicException( + \sprintf( + self::ERROR_QUERY_NOT_FOUND, + $name, + \implode(' ', $instance->getQueryHandlerNames()) + ), + ); } } diff --git a/src/Internal/Transport/Router/InvokeUpdate.php b/src/Internal/Transport/Router/InvokeUpdate.php index f73d8459..60858c76 100644 --- a/src/Internal/Transport/Router/InvokeUpdate.php +++ b/src/Internal/Transport/Router/InvokeUpdate.php @@ -36,6 +36,7 @@ public function handle(ServerRequestInterface $request, array $headers, Deferred try { $instance = $process->getWorkflowInstance(); + /** @var non-empty-string $name */ $name = $request->getOptions()['name']; $handler = $this->getUpdateHandler($instance, $name); /** @psalm-suppress InaccessibleProperty */ @@ -86,12 +87,11 @@ public function handle(ServerRequestInterface $request, array $headers, Deferred return; } - // There validation is passed + // Validation has passed - /** @var PromiseInterface $promise */ $promise = $handler($input); $promise->then( - static function (mixed $value) use ($updateId, $context, $resolver): void { + static function (mixed $value) use ($updateId, $context): void { $context->getClient()->send(new UpdateResponse( command: UpdateResponse::COMMAND_COMPLETED, values: EncodedValues::fromValues([$value]), @@ -99,7 +99,7 @@ static function (mixed $value) use ($updateId, $context, $resolver): void { updateId: $updateId, )); }, - static function (\Throwable $err) use ($updateId, $context, $resolver): void { + static function (\Throwable $err) use ($updateId, $context): void { $context->getClient()->send(new UpdateResponse( command: UpdateResponse::COMMAND_COMPLETED, values: null, @@ -112,6 +112,7 @@ static function (\Throwable $err) use ($updateId, $context, $resolver): void { /** * @param non-empty-string $name + * @return \Closure(UpdateInput): PromiseInterface */ private function getUpdateHandler(WorkflowInstanceInterface $instance, string $name): \Closure { diff --git a/src/Internal/Workflow/Process/Process.php b/src/Internal/Workflow/Process/Process.php index 588d9db2..37440778 100644 --- a/src/Internal/Workflow/Process/Process.php +++ b/src/Internal/Workflow/Process/Process.php @@ -139,7 +139,7 @@ function (SignalInput $input) use ($handler) { )->onClose( function (?\Throwable $error): void { if ($error !== null) { - // we want to fail process when signal scope fails + // Fail process when signal scope fails $this->complete($error); } } diff --git a/src/Internal/Workflow/Process/Scope.php b/src/Internal/Workflow/Process/Scope.php index bd1671f8..a9671b5b 100644 --- a/src/Internal/Workflow/Process/Scope.php +++ b/src/Internal/Workflow/Process/Scope.php @@ -28,7 +28,6 @@ use Temporal\Worker\Transport\Command\RequestInterface; use Temporal\Workflow; use Temporal\Workflow\CancellationScopeInterface; -use Temporal\Workflow\WorkflowContextInterface; /** * Unlike Java implementation, PHP has merged coroutine and cancellation scope into a single instance. @@ -447,6 +446,7 @@ protected function makeCurrent(): void protected function next(): void { $this->makeCurrent(); + begin: $this->context->resolveConditions(); if (!$this->coroutine->valid()) { @@ -482,6 +482,7 @@ protected function next(): void default: $this->coroutine->send($current); + goto begin; } } diff --git a/src/Internal/Workflow/ProcessCollection.php b/src/Internal/Workflow/ProcessCollection.php index 4006a932..344ab782 100644 --- a/src/Internal/Workflow/ProcessCollection.php +++ b/src/Internal/Workflow/ProcessCollection.php @@ -12,7 +12,6 @@ namespace Temporal\Internal\Workflow; use Temporal\Internal\Repository\ArrayRepository; -use Temporal\Internal\Transport\ClientInterface; use Temporal\Internal\Workflow\Process\Process; /** @@ -22,11 +21,6 @@ class ProcessCollection extends ArrayRepository { private const ERROR_PROCESS_NOT_FOUND = 'Process #%s not found.'; - public function __construct() - { - parent::__construct(); - } - /** * @param string $runId * @param non-empty-string|null $error Error message if the process was not found. diff --git a/tests/Fixtures/src/Workflow/YieldScalarsWorkflow.php b/tests/Fixtures/src/Workflow/YieldScalarsWorkflow.php new file mode 100644 index 00000000..a5eec719 --- /dev/null +++ b/tests/Fixtures/src/Workflow/YieldScalarsWorkflow.php @@ -0,0 +1,30 @@ +fail('LocalActivity not found in history'); } + public function testYieldNonPromises(): void + { + $workflow = $this->workflowClient->newWorkflowStub(YieldScalarsWorkflow::class); + $run = $this->workflowClient->start($workflow, ['hello', 'world', '!']); + $this->assertSame([ 'hello', 'world', '!'], $run->getResult('array')); + } + private function assertContainsEvent(WorkflowExecution $execution, int $event): void { $history = $this->workflowClient->getWorkflowHistory( diff --git a/tests/Unit/Worker/AutowiringTestCase.php b/tests/Unit/Worker/AutowiringTestCase.php index 3b20607e..83a25865 100644 --- a/tests/Unit/Worker/AutowiringTestCase.php +++ b/tests/Unit/Worker/AutowiringTestCase.php @@ -83,19 +83,4 @@ public function testInstanceCallMethodInvocation(\ReflectionFunctionAbstract $fn $this->assertSame(0xDEAD_BEEF, $handler->dispatch($this, [])); } - - #[TestDox("Checks invocation without an object context or exception otherwise (if object context required)")] - #[DataProvider('reflectionDataProvider')] - public function testStaticCallMethodInvocation(\ReflectionFunctionAbstract $fn): void - { - $handler = new AutowiredPayloads($fn, new DataConverter(new JsonConverter())); - - // If the object context is required, then the method invocation without - // "this" context should return an BadMethodCallException error. - if ($handler->isObjectContextRequired()) { - $this->expectException(\BadMethodCallException::class); - } - - $this->assertSame(0xDEAD_BEEF, $handler->dispatch(null, [])); - } }