Releases: temporalio/sdk-php
v2.8.0-BETA1
Note
RoadRunner 2023.3.11 is required
How to develop with Updates
An Update is an operation that can mutate the state of a Workflow Execution and return a response.
How to define Updates
An Update handler has a name, arguments, response, and an optional validator.
- The name, also called an Update type, is a string.
- The arguments and response must be serializable.
The #[UpdateMethod]
attribute indicates that the method is used to handle and respond to update requests.
#[UpdateMethod]
public function myUpdate(string $signalName);
How to handle Updates in a Workflow
Workflows listen for Update by the update's name.
The handler method can accept multiple serializable input parameters, but it's recommended using only a single parameter.
The function can return a serializable value or void
.
#[WorkflowInterface]
interface FileProcessingWorkflow {
#[WorkflowMethod]
public function processFile(Arguments $args);
#[UpdateMethod]
public function pauseProcessing(): void;
}
Update handlers, unlike Query handlers, can change Workflow state.
The Updates type defaults to the name of the method. To overwrite this default naming and assign a custom Update type, use the #[UpdateMethod]
attribute with the name
parameter.
#[WorkflowInterface]
interface FileProcessingWorkflow {
#[WorkflowMethod]
public function processFile(Arguments $args);
#[UpdateMethod(name: "pause")]
public function pauseProcessing();
}
How to validate an Update in a Workflow
Validate certain aspects of the data sent to the Workflow using an Update Validator method. For instance, a counter Workflow might never want to accept a non-positive number. Use the #[UpdateValidatorMethod]
attribute and set name
to the name of your Update handler. Your Update Validator should accept the same input parameters as your Update Handler and return void
.
#[WorkflowInterface]
interface GreetingWorkflow {
#[WorkflowMethod]
public function getGreetings(): array;
#[UpdateMethod]
public function addGreeting(string $name): int;
#[UpdateValidatorMethod(forUpdate: "addGreeting")]
public function addGreetingValidator(string $name): void;
}
How to send an Update from a Client
To send an Update to a Workflow Execution from a Client, call the Update method, annotated with #[UpdateMethod]
in the Workflow interface, from the Client code.
In the following Client code example, start the Workflow getGreetings
and call the Update method addGreeting
that is handled in the Workflow.
/** @var \Temporal\Client\WorkflowClientInterface $client */
// Create a typed Workflow stub for GreetingsWorkflow
$workflow = $client->newWorkflowStub(GreetingWorkflow::class, $workflowOptions);
// Start the Workflow
$run = $client->start($workflow);
// Send an update to the Workflow. addGreeting returns
// the number of greetings our workflow has received.
$workflow->addGreeting("World");
v2.7.6
v2.7.5
What's Changed
// Workflow context
public function handle(string $userId, string $email) {
// ...
$activityStub = Workflow::newActivityStub(SubscriptionActivity::class, ActivityOptions::new()->withStartToCloseTimeout(10));
// Called method signature:
// public function subscribe(string $email, string $userId, string $prefix = 'Dear', array $channels = ['main']): void
yield $activityStub->subscribe(user: $userId, email: $email, channels: ['news']);
// Arguments in correct order will be sent: $email, $userId, 'Dear', ['news']
// ...
}
Full Changelog: v2.7.4...v2.7.5
v2.7.4
v2.7.3
v2.7.2
What's Changed
Fixed marshaller when ext-protobuf is installed by @roxblnfk in #384
Also:
- CI: added tests with PHP + ext-protobuf
- Tests: improved test names
Before renaming: "Tests: 332, Assertions: 442"
After renaming: "Tests: 511, Assertions: 1122"
Full Changelog: v2.7.1...v2.7.2
v2.7.1
v2.7.0
Schedule API
Temporal Schedules are a replacement for traditional cron jobs for task scheduling because the Schedules provide a more durable way to execute tasks, allow insight into their progress, enable observability of schedules and workflow runs, and let you start, stop, and pause them.
Starting with this release, the Temporal PHP SDK begins to provide new primitives for Schedule CRUD operations.
use Temporal\Client\Schedule;
// Start from creating the Schedule Client
// If you use a Temporal integration package with your framework, you can
// get the client from the container using the `ScheduleClientInterface` interface.
$client = \Temporal\Client\ScheduleClient::create(
\Temporal\Client\GRPC\ServiceClient::create('localhost:7233'),
);
// Create a Schedule
$handle = $client->createSchedule(
Schedule\Schedule::new()->withAction(
Schedule\Action\StartWorkflowAction::new('testWorkflow')
->withTaskQueue('testTaskQueue')
->withRetryPolicy(\Temporal\Common\RetryOptions::new()->withMaximumAttempts(3))
->withHeader(['foo' => 'bar'])
->withWorkflowExecutionTimeout('40m')
)->withSpec(
Schedule\Spec\ScheduleSpec::new()
->withIntervalList(5 * 60) // every 5 minutes
->withJitter(60) // with jitter of 1 minute
),
scheduleId: 'my-schedule-id',
);
// Pause the Schedule
$handle->pause();
// Trigger the Action to be taken immediately
$handle->trigger(Schedule\Policy\ScheduleOverlapPolicy::CancelOther);
// Delete the Schedule
$handle->delete();
To get a handle to an existing Schedule, use the ScheduleClient::getHandle()
method.
$client = \Temporal\Client\ScheduleClient::create(
\Temporal\Client\GRPC\ServiceClient::create('localhost:7233'),
);
// Get a handle to an existing Schedule
$handle = $client->getHandle(scheduleID: 'my-schedule-id');
// Describe the Schedule
$description = $handle->describe();
// Update the Schedule
$handle->update(
$description->schedule->withSpec(
\Temporal\Client\Schedule\Spec\ScheduleSpec::new()
->withCronStringList('0 12 * * 5', '0 12 * * 1')
),
conflictToken: $description->conflictToken, // To avoid race condition
);
Workflow Start Delay
Use the Workflow Start Delay functionality if you need to delay the execution of the Workflow without the need for regular launches.
Here you simply specify the time to wait before dispatching the first Workflow task.
If the Workflow gets a Signal before the delay, a Workflow task will be dispatched and the rest of the delay will be ignored. A Signal from WorkflowClientInterface::startWithSignal()
won't trigger a workflow task.
Cannot be set the same time as a WorkflowOptions::withCronSchedule()
.
/** @var \Temporal\Client\WorkflowClientInterface $client */
$workflow = $client->newWorkflowStub(
GreeterWorkflowInterface::class,
\Temporal\Client\WorkflowOptions::new()
->withWorkflowStartDelay(\DateInterval::createFromDateString('1 minute'))
);
$client->start($workflow, 'Hello world!');
Interceptors
Temporal Interceptors are a mechanism for modifying inbound and outbound SDK calls. They are commonly used to add tracing and authorization to the scheduling and execution of Workflows and Activities. You can compare these to "middleware" in other frameworks.
The following types of Interceptors have been implemented:
- WorkflowClientCallsInterceptor: Intercepts methods of the WorkflowClient, such as starting or signaling a Workflow.
- WorkflowInboundCallsInterceptor: Intercepts inbound Workflow calls, including execution, Signals, and Queries.
- WorkflowOutboundCallsInterceptor: Intercepts outbound Workflow calls to Temporal APIs, such as scheduling Activities and starting Timers.
- ActivityInboundCallsInterceptor: Intercepts inbound calls to an Activity, such as execute.
- GrpcClientInterceptor: Intercepts all service client gRPC calls (see
ServiceClientInterface
). - WorkflowOutboundRequestInterceptor: Intercepts all commands sent to the RoadRunner server (see
RequestInterface
implementations).
Please note that some interceptor interfaces will be expanded in the future. To avoid compatibility issues, always use the corresponding traits in your implementations. The traits will prevent implementations from breaking when new methods are added to the interfaces.
A reminder of this and a link to the corresponding trait are placed in the comments of the interceptor interfaces.
In this release, we have also added support for headers, which are an integral part of interceptors. Headers, being metadata, are intended for sharing context and auxiliary information. Headers sent at the start of a workflow will be propagated to all subsequent calls from the Workflow and can only be modified in interceptors.
You can find examples of how to use interceptors in the samples repository (PR)
Full Changelog: v2.6.2...v2.7.0
v2.7.0-RC1
What's Changed
- Maintenance and polishing by @roxblnfk in #377, #378
- Schedule API has been fixed, covered with tests, and polished. Added
ScheduleClientInterface
. - Added Symfony 7 packages, removed Symfony 4.
- Repository cleaning.
- Fixed
HistoryLength
update when a Signal method is called.
- Schedule API has been fixed, covered with tests, and polished. Added
Full Changelog: v2.7.0-BETA3...v2.7.0-RC1
v2.7.0-BETA3
What's Changed
Schedule API
Concept
Temporal Schedules: Reliable, Scalable, and More Flexible than Cron Jobs (blog post)
Example
use Temporal\Client\Schedule;
// Create a Schedule Client
$client = \Temporal\Client\ScheduleClient::create(
\Temporal\Client\GRPC\ServiceClient::create('localhost:7233'),
);
// Create a Schedule
$handle = $client->createSchedule(
Schedule\Schedule::new()->withAction(
Schedule\Action\StartWorkflowAction::new('testWorkflow')
->withTaskQueue('testTaskQueue')
->withRetryPolicy(\Temporal\Common\RetryOptions::new()->withMaximumAttempts(3))
->withHeader(['foo' => 'bar'])
->withWorkflowExecutionTimeout('40m')
)->withSpec(
Schedule\Spec\ScheduleSpec::new()
->withCronStringList('0 * * * *')
),
scheduleId: 'testSchedule',
);
// Describe the Schedule
$description = $handle->describe();
// Update the Schedule
$handle->update(
$description->schedule->withSpec(
Schedule\Spec\ScheduleSpec::new()
->withCronStringList('0 12 * * 5', '0 12 * * 1')
),
conflictToken: $description->conflictToken, // To avoid race condition
);
// Pause the Schedule
$handle->pause();
// Trigger the Action to be taken immediately
$handle->trigger(Schedule\Policy\ScheduleOverlapPolicy::CancelOther);
// Delete the Schedule
$handle->delete();
Full Changelog: v2.7.0-BETA2...v2.7.0-BETA3