Temporal PHP SDK 2.6.0

Temporal PHP SDK 2.6.0

Hello, Temporal community! We are glad to announce the release of the Temporal PHP SDK v2.6.0.

Some changes are related to RoadRunner 2023, which has not yet been released at the time of the SDK release. However, you can download the pre-release version of RoadRunner 2023.3 and try out the new features right now.

vendor/bin/rr get --stability=RC

ReactPHP Promise v3

The ReactPHP Promise dependency has been updated from version 2 to version 3.

Note
There are no BC breaks in the Temporal SDK, but there are BC breaks in the ReactPHP Promise library. If you rely on the functions of the ReactPHP Promise library and use them directly, please familiarize yourself with the list of changes there. We have not completely abandoned compatibility with Promise v2, and it will be available for some time — just add "react/promise": "^2.9" in your composer.json, if you are tied to Promise v2.

Despite the fact that some functions (some(), map(), reduce()) have been removed from React, they have remained in our Temporal\Promise helper with the same behavior.

Either way, welcome the new features:

  • the new convenient methods of PromiseInterface: catch() and finally();
  • @template annotation for the PromiseInterface, which allows you to specify the type of the promise result.
    So start specifying types in Activity and Workflow, and when this initiative is implemented, the Workflow code will become fully typed and understandable for IDE and static analysis.
php
#[\Temporal\Activity]
class MyActivityClass {
    /**
     * @return PromiseInterface<non-empty-string>
     */
    #[\Temporal\ActivityMethod('MyWorkflow')]
    public function myActivityMethod(): string {
        return 'some string';
    }
}

#[\Temporal\Workflow]
class MyWorkflowClass {
    #[\Temporal\WorkflowMethod]
    public function handle() {
        $activity = \Temporal\Workflow::newActivityStub(MyActivityClass::class);

        // IDE will know that the $uuid is a UuidInterface instance
        $uuid = yield \Temporal\Workflow::uuid();

        // IDE will know that the $result is a string
        $result = yield $activity->myActivityMethod();
    }
}

UUID support

The ramsey/uuid package is now a dependency of the PHP SDK and is supported at many levels:

  • In the Marshaller: use the `UuidInterface type in object properties - it will automatically be serialized and deserialized through a string representation.
  • In the DataConverter: specify UuidInterface in the method parameters of Workflow and Activity - this will work, in the returned types or in getResult().
    This also applies to SideEffect.

Added new methods:

php
$uuid  = yield \Temporal\Workflow::uuid();  // Generate a UUID (if you don't care about the UUID version)
$uuid4 = yield \Temporal\Workflow::uuid4(); // Generate a UUID v4
$uuid7 = yield \Temporal\Workflow::uuid7(); // Generate a UUID v7

Eager start

Eager Workflow Dispatch is a mechanism that minimizes the duration from starting a workflow to the processing of the first workflow task, making Temporal more suitable for latency sensitive applications.

Eager Workflow Dispatch can be enabled if the server supports it and a local worker is available the task is fed directly to the worker.

Note
That this will require some extra work to allocate a workflow slot on a Temporal worker on the same task queue and delivering a workflow task to the workflow from the StartWorfklowExecutionResponse.

php
/** @var \Temporal\Client\WorkflowClientInterface $workflowClient */
$workflow = $workflowClient->newWorkflowStub(
    SimpleWorkflow::class,
    \Temporal\Client\WorkflowOptions::new()
        ->withEagerStart()  // <=
);

$workflowClient->start($workflow, 'hello');

Returning array of objects

Added the possibility to return a list of objects from activity/workflow.

php
/** @var \Temporal\Client\WorkflowClientInterface $workflowClient */
/** @var \Temporal\Internal\Workflow\ActivityStub $activity */

//When executing workflow:

/** @var Message[] $result */
$result = $workflowClient->start($workflow, 'John')->getResult(Type::arrayOf(Message::class));

//When executing activity within a workflow:

/** @var Message[] $result */
$result = yield $activity->execute(
    name: 'myActivity',
    args: [$input],
    returnType: Type::arrayOf(Message::class),
);

History length

Now you don't need to guess how many events your code produces to decide if it's time to call continue-as-new. Just check Workflow::getInfo()->historyLength property. This field will be updated with any response from the RoadRunner server.

Available since RoadRunner 2023.2.

Visibility: listWorkflowExecutions and listWorkflowExecutions

Added WorkflowClient::listWorkflowExecutions() and WorkflowClient::countWorkflowExecutions() methods that can help to get a list of workflow executions using a list filter query syntax. As a result, a Paginator object is returned which can be used in a simple way:

php
/** @var \Temporal\Client\WorkflowClientInterface $client */
$paginator = $client->listWorkflowExecutions(
    query: "WorkflowType='MyWorkflow' and StartTime  between '2022-08-22T15:04:05+00:00' and  '2023-08-22T15:04:05+00:00'",
    namespace: 'default',
);

// Iterate all items (pages will be fetched automatically)
foreach ($paginator as $execution) {
    // ...
}

// Get current page items
$items = $paginator->getPageItems();

// Get next page
$nextPage = $paginator->getNextPage();

// Get items count (an RPC method will be called that may require a preconfigured advanced Visibility)
$count = $paginator->count();

Documentation about Visibility: https://docs.temporal.io/visibility

Replay API

PR: https://github.com/temporalio/sdk-php/pull/336

Replay API is very useful tool for a workflow determinism testing. It recreates the exact state of a Workflow Execution. You can replay a Workflow from the beginning of its Event History. Replay succeeds only if the Workflow Definition is compatible with the provided history from a deterministic point of view.

Examples

To replay Workflow Executions, use the \Temporal\Testing\Replay\WorkflowReplayer class.

In the following example, Event Histories are downloaded from the server, and then replayed. Note that this requires Advanced Visibility to be enabled.

php
/**
 * We assume you already have a WorkflowClient instance in the scope.
 * @var \Temporal\Client\WorkflowClientInterface $workflowClient
 */

// Find all workflow executions of type "MyWorkflow" and task queue "MyTaskQueue".
$executions = $workflowClient->listWorkflowExecutions(
    "WorkflowType='MyWorkflow' AND TaskQueue='MyTaskQueue'"
);

$replayer = new \Temporal\Testing\Replay\WorkflowReplayer();
// Replay each workflow execution.
foreach ($executions as $executionInfo) {
    try {
        $replayer->replayFromServer(
            workflowType: $executionInfo->type->name,
            execution: $executionInfo->execution,
        );
    } catch (\Temporal\Testing\Replay\Exception\ReplayerException $e) {
        // Handle the replay error.
    }
}

You can download a workflow history manually from Temporal UI or using PHP, and then replay it from a memorized History object:

php
$history = $this->workflowClient->getWorkflowHistory(
    execution: $run->getExecution(),
)->getHistory();

(new WorkflowReplayer())->replayHistory($history);

To store a History JSON file, use the \Temporal\Testing\Replay\WorkflowReplayer::downloadHistory() method.

Available since RoadRunner 2023.3

Stability improvements

We have introduced a new way of identifying workflow operations. It's internal run_id, that helps to avoid bugs related with async operations between Temporal, RoadRunner and SDK. There will be no any "Got the response to undefined request" errors!

Available since RoadRunner 2023.3