temporal/sdk
Temporal PHP SDK for building durable, scalable workflow orchestration with Temporal. Author Workflows and Activities in PHP, run them with RoadRunner workers, and manage executions via gRPC clients. Composer-installable with optional protobuf for performance.
Full Changelog: https://github.com/temporalio/sdk-php/compare/v2.17.0...v2.17.1
This release focuses on better observability, improved error handling, clearer APIs, and significantly enhanced testing & CI reliability. Several deprecations were introduced to improve long-term API consistency.
PR: https://github.com/temporalio/sdk-php/pull/691 Author: @xepozz
WorkflowInfo now exposes:
getFirstRunId()getOriginalRunId()This improves traceability across retries, resets and continue-as-new chains.
$info = Workflow::getInfo();
$firstRun = $info->getFirstRunId();
$originalRun = $info->getOriginalRunId();
This is particularly useful for observability tooling and debugging long-running workflow chains.
PR: https://github.com/temporalio/sdk-php/pull/710 Author: @xepozz
Workflows can now expose structured “current details” metadata, improving runtime inspection and debugging of workflow state.
PR: https://github.com/temporalio/sdk-php/pull/656 Author: @roxblnfk
Activities can now access their configured retry policy:
$context = Activity::getContext();
$retryPolicy = $context->getRetryOptions();
This enables dynamic behavior depending on retry configuration.
PR: https://github.com/temporalio/sdk-php/pull/666 Author: @roxblnfk
ApplicationFailure now exposes ErrorCategory, allowing more precise error classification and handling logic in workflows.
PR: https://github.com/temporalio/sdk-php/pull/683 Author: @xepozz
Introduced RawValue for pass-through or untyped payload handling.
Useful when custom serialization control is required.
PR: https://github.com/temporalio/sdk-php/pull/661 Author: @roxblnfk
Improved environment configuration visibility and integration, especially useful for CI and containerized deployments.
PR: https://github.com/temporalio/sdk-php/pull/707 Author: @xepozz
Added a wrapper to properly handle non-zero exit codes on Windows systems. Improves cross-platform development and CI reliability.
#[ActivityMethod] AttributePR: https://github.com/temporalio/sdk-php/pull/677 Author: @xepozz
Using an activity method without the required #[\Temporal\Activity\ActivityMethod] attribute now triggers a deprecation warning.
#[\Temporal\Activity\ActivityMethod]
public function sendEmail(): void
{
// ...
}
This ensures explicit activity registration and prevents subtle misconfiguration.
[!TIP] You may disable this behavior with the feature flag
\Temporal\Worker\FeatureFlags::$warnOnActivityMethodWithoutAttributeSee https://github.com/temporalio/sdk-php/blob/962c897757d9e9c29d579edb12c94e01b1c6fd52/src/Worker/FeatureFlags.php#L66C25-L66C61
PR: https://github.com/temporalio/sdk-php/pull/662 Author: @roxblnfk
Improved error messages when workflow/activity context is misused outside its valid execution scope. Exceptions are now more descriptive and actionable.
WorkflowRunInterface::getResult($type)PR: https://github.com/temporalio/sdk-php/pull/678 Author: @xepozz
Improved typed result retrieval and clearer error feedback when incorrect types are requested.
PR: https://github.com/temporalio/sdk-php/pull/663 Author: @roxblnfk
Improved handling and warnings for ambiguous DateInterval usage to avoid subtle time calculation inconsistencies.
PR: https://github.com/temporalio/sdk-php/pull/719 Author: @xepozz
Enhanced validation in Priority::withFairnessWeight() with stricter guarantees and additional test coverage.
PR: https://github.com/temporalio/sdk-php/pull/716 Author: @xepozz
Search attribute parsing is now more tolerant to slightly variant input types, improving robustness.
PR: https://github.com/temporalio/sdk-php/pull/679 Author: @xepozz
General improvement of error clarity across workflow and activity contexts.
PR: https://github.com/temporalio/sdk-php/pull/687 Author: @xepozz
Ensures correct exception propagation and typing consistency.
PR: https://github.com/temporalio/sdk-php/pull/686 Author: @xepozz
Fixes mismatch between Temporal runtime behavior and PHP default timezone.
PR: https://github.com/temporalio/sdk-php/pull/672 Author: @xepozz
Ensures forward compatibility with PHP 8.4.
symfony/process Minimum VersionPR: https://github.com/temporalio/sdk-php/pull/706 Author: @xepozz
Minimum supported version bumped to 5.4.51.
PR: https://github.com/temporalio/sdk-php/pull/712 Author: @mjameswh (New contributor)
ROADRUNNER_BINARY support PR: https://github.com/temporalio/sdk-php/pull/667 Author: @roxblnfk
PHPUnit run presets PR: https://github.com/temporalio/sdk-php/pull/676 Author: @xepozz
Xdebug support PR: https://github.com/temporalio/sdk-php/pull/675 Author: @xepozz
Reduced process timeouts PR: https://github.com/temporalio/sdk-php/pull/680 Author: @xepozz
Acceptance tests split into Fast / Slow PR: https://github.com/temporalio/sdk-php/pull/708 Author: @xepozz
Use IP instead of localhost in tests PR: https://github.com/temporalio/sdk-php/pull/713 Author: @xepozz
Reuse test environment variables PR: https://github.com/temporalio/sdk-php/pull/714 Author: @xepozz
Remove feature tests PR: https://github.com/temporalio/sdk-php/pull/673 Author: @xepozz
Validate issue #623 PR: https://github.com/temporalio/sdk-php/pull/681 Author: @xepozz Issue: https://github.com/temporalio/sdk-php/issues/623
Allow continue-on-error in validate-prefer-lowest PR: https://github.com/temporalio/sdk-php/pull/696 Author: @xepozz
PHPUnit update to 10.5.63 PR: https://github.com/temporalio/sdk-php/pull/701 Author: @xepozz
Symfony 8 support PR: https://github.com/temporalio/sdk-php/pull/671 Author: @xepozz
Drop internal/promise v2 support PR: https://github.com/temporalio/sdk-php/pull/693 Author: @xepozz
Fix broken testing guide link PR: https://github.com/temporalio/sdk-php/pull/688 Author: @xepozz
Docs & code style improvements PR: https://github.com/temporalio/sdk-php/pull/682 Author: @xepozz
https://github.com/temporalio/sdk-php/compare/v2.16.0...v2.17.0
[!WARNING] RoadRunner 2025.1.3+ is required.
Added a new feature flag FeatureFlags::$cancelAbandonedChildWorkflows to control the cancellation behavior of abandoned Child Workflows.
Previously, when a parent workflow was canceled, all child workflows would be canceled, including those with ParentClosePolicy::Abandon.
This behavior was incorrect - abandoned child workflows should continue running independently when their parent is canceled.
# worker.php
use Temporal\Worker\FeatureFlags;
// Fixed behavior (does NOT cancel abandoned children) - recommended
FeatureFlags::$cancelAbandonedChildWorkflows = false;
// Default behavior (cancels abandoned children - matches previous SDK versions)
FeatureFlags::$cancelAbandonedChildWorkflows = true;
[!WARNING] When setting
$cancelAbandonedChildWorkflows = false:
- If you start an abandoned child workflow in the main workflow scope, it may miss the cancellation signal if you await only on the child workflow. Use
Promise::race()with a timer to properly handle cancellation.- If you start an abandoned child workflow in an async scope that is later canceled, the child workflow will not be affected by the scope cancellation.
- You can still cancel abandoned child workflows manually by calling
WorkflowStubInterface::cancel().
The PHP SDK now supports React Promise v3.
To make this work correctly in the Workflow Worker environment,
the promises have been forked and improved in the internal/promise package.
The fork addresses critical issues for long-running Workflow Workers:
made rejection handler reusable (a v3 feature),
removed exit(255) calls from rejection handling that would terminate the worker process,
added declare(strict_types=1) throughout, and improved type annotations for better static analysis support.
A key improvement is the [@yield](https://github.com/yield) annotation added to PromiseInterface,
which enables proper type inference when using promises with generators in Workflows.
This annotation is recognized by IDEs (PHPStorm) and static analysis tools (Psalm), significantly improving DX:
interface SomeActivity {
/**
* [@return](https://github.com/return) \React\Promise\PromiseInterface<ResultDto>
*/
public function doSomething(int $value): ResultDto;
}
final class Workflow {
public function handle(): \Generator
{
$activity = \Temporal\Workflow::newActivityStub(SomeActivity::class);
$result = yield $activity->doSomething(42); // IDE and Psalm infer $result as ResultDto
}
}
The SDK supports both React Promise v2 and v3 - the version used depends on what you require in your composer.json.
[!WARNING] React Promise v3 includes optimizations that may slightly change promise resolution order compared to v2. This could potentially affect Workflow determinism in edge cases.
If you experience issues after upgrading, lock to React Promise v2 in your
composer.json:{ "require": { "react/promise": "^2.11" } }
Workflows can now implement the Destroyable interface from the internal/destroy package to explicitly manage resource cleanup when the Workflow instance is evicted from memory.
This is particularly useful when your Workflow contains circular references between objects that prevent PHP's garbage collector from properly cleaning up memory. While this is not a common scenario, having explicit control over resource cleanup is critical for long-running Workers handling many workflow executions.
The SDK automatically calls the destroy() method when a Workflow instance needs to be evicted from memory,
allowing you to break circular references and release resources deterministically.
final class Workflow implements Destroyable
{
/** Collection with cross-linked objects that also implements Destroyable */
private LinkedCollection $collection;
// ...
public function destroy(): void
{
// Must be idempotent - safe to call multiple times
$collection = $this->collection ?? null;
unset($this->collection);
$collection?->destroy();
}
}
Added new fields to Workflow::getInfo():
$rootExecution = Workflow::getInfo()->rootExecution;
$retryOptions = Workflow::getInfo()->retryOptions;
The RoadRunner ecosystem now includes a new roadrunner/psr-logger package that can be used with Temporal SDK.
By default, the SDK uses \Temporal\Worker\Logger\StderrLogger which outputs messages to STDERR.
RoadRunner captures these messages and logs them at the INFO level.
The new \RoadRunner\PsrLogger\RpcLogger sends logs to RoadRunner via RPC with precise log levels and structured context data.
Get Started:
composer require roadrunner/psr-logger
use RoadRunner\PsrLogger\RpcLogger;
use Spiral\Goridge\RPC\RPC;
use Temporal\WorkerFactory;
$rpc = RPC::create('tcp://127.0.0.1:6001');
$logger = new RpcLogger($rpc);
$factory = WorkerFactory::create(logger: $logger);
$worker = $factory->newWorker('my-task-queue');
Worker Versioning enables safe deployment of workflow changes by controlling how Workflows move between different worker versions. Each worker deployment is identified by a unique Build ID, and workflows can be pinned to specific versions or automatically upgrade to the latest version.
Worker Configuration
Configure versioning when creating a worker:
use Temporal\Worker\WorkerOptions;
use Temporal\Worker\WorkerDeploymentOptions;
use Temporal\Common\Versioning\VersioningBehavior;
$worker = $factory->newWorker(
'my-task-queue',
WorkerOptions::new()
->withDeploymentOptions(
WorkerDeploymentOptions::new()
->withUseVersioning(true)
->withVersion('build-v1.2.3')
->withDefaultVersioningBehavior(VersioningBehavior::Pinned)
)
);
Workflow Versioning Behavior
Control versioning behavior per workflow using the #[WorkflowVersioningBehavior] attribute:
use Temporal\Workflow;
use Temporal\Common\Versioning\VersioningBehavior;
#[Workflow\WorkflowInterface]
class MyWorkflow
{
#[Workflow\WorkflowMethod]
#[Workflow\WorkflowVersioningBehavior(VersioningBehavior::Pinned)]
public function handle(): \Generator
{
// Workflow will stay pinned to its original deployment version
yield Workflow::timer(3600);
return 'Done';
}
}
Versioning Behaviors:
Pinned: Workflow stays on its original deployment version until completionAutoUpgrade: Workflow automatically moves to the current deployment version on the next workflow taskClient Override
Override versioning behavior when starting a workflow:
use Temporal\Client\WorkflowOptions;
use Temporal\Common\Versioning\VersioningOverride;
use Temporal\Common\Versioning\WorkerDeploymentVersion;
// Pin to specific version
$workflow = $client->newWorkflowStub(
MyWorkflow::class,
WorkflowOptions::new()
->withVersioningOverride(
VersioningOverride::pinned(
WorkerDeploymentVersion::fromString('build-v1.2.3')
)
)
);
// Or enable auto-upgrade
$workflow = $client->newWorkflowStub(
MyWorkflow::class,
WorkflowOptions::new()
->withVersioningOverride(VersioningOverride::autoUpgrade())
);
[!NOTE] This feature is experimental and requires RoadRunner 2025.1.3+. See the Worker Versioning documentation for deployment strategies and best practices.
Priority Fairness extends the Task Queue Priority feature with fairness keys and weights, enabling balanced task processing across multiple tenants or execution groups within a single task queue. This is particularly valuable for multi-tenant SaaS applications where you need to prevent large tenants from monopolizing worker resources.
Key Concepts:
The fairness mechanism ensures tasks are dispatched in proportion to their weights. For example, with 1000 tenants each having a weight of 1.0, each tenant receives roughly equal task processing throughput regardless of their individual workload size.
Setting Fairness Parameters:
use Temporal\Common\Priority;
use Temporal\Client\WorkflowOptions;
// Start workflow with fairness settings
$workflow = $workflowClient->newWorkflowStub(
OrderWorkflow::class,
WorkflowOptions::new()
->withTaskQueue('task-queue')
->withPriority(
Priority::new()
->withFairnessKey('tenant-123')
->withFairnessWeight(2.5)
),
);
In Workflow Context:
use Temporal\Workflow;
use Temporal\Common\Priority;
// Set fairness for an Activity
$activity = Workflow::newActivityStub(
ActivityInterface::class,
ActivityOptions::new()
->withScheduleToCloseTimeout('5 minutes')
->withPriority(
Priority::new()
->withFairnessKey('premium-tenant')
->withFairnessWeight(10.0)
),
);
// Set fairness for a Child Workflow
$childWorkflow = Workflow::newChildWorkflowStub(
ChildWorkflowInterface::class,
ChildWorkflowOptions::new()
->withPriority(
Priority::new()
->withFairnessKey('tenant-456')
->withFairnessWeight(1.0)
),
);
Accessing Fairness Information:
// In Workflow
$priority = Workflow::getInfo()->priority;
$fairnessKey = $priority->fairnessKey;
$fairnessWeight = $priority->fairnessWeight;
// In Activity
$priority = Activity::getInfo()->priority;
$fairnessKey = $priority->fairnessKey;
$fairnessWeight = $priority->fairnessWeight;
Weight Precedence:
Fairness weights can be configured from multiple sources, with the following precedence (highest to lowest):
[!NOTE]
- Weight values are automatically clamped to the range [0.001, 1000]
- Fairness keys are inherited by child workflows and activities by default
- The fairness mechanism works in conjunction with priority keys for fine-grained control
- Watch the Temporal Task Queue Fairness | Multi-Tenant Workflows Made Easy video for more details
Added a new feature flag FeatureFlags::$throwDestructMemorizedInstanceException to control an internal memory cleanup mechanism.
When enabled (default), the SDK throws DestructMemorizedInstanceException into pending promises during Workflow eviction.
This exception may occasionally surface in user code where it should be ignored - which is not obvious and adds complexity.
# worker.php
use Temporal\Worker\FeatureFlags;
// Default behavior
FeatureFlags::$throwDestructMemorizedInstanceException = true;
// Experimental - disable exception throwing
FeatureFlags::$throwDestructMemorizedInstanceException = false;
[!WARNING] You can experiment with disabling this flag in non-production environments to monitor memory usage. Future SDK versions will move away from this mechanism toward promise implementations that self-cleanup without exceptions.
throwDestructMemorizedInstanceException by @roxblnfk in https://github.com/temporalio/sdk-php/pull/651Full Changelog: https://github.com/temporalio/sdk-php/compare/v2.15.0...v2.16.0
updateWithStart() client method by @roxblnfk in https://github.com/temporalio/sdk-php/pull/637Full Changelog: https://github.com/temporalio/sdk-php/compare/v2.15.0...v2.15.1
[!WARNING] RoadRunner 2025.1.2 is required.
Task Queue Priority allows you to control the execution order of workflows, activities, and child workflows based on assigned priority values within a single task queue. You can select a priority level in the integer range 1...5. A lower value implies higher priority. The default priority if unspecified is in the middle of the range, 3.
[!NOTE] As this feature is currently in Pre-release stage, it is not intended for production use at this time. See product release stages for more information.
Pre-requisites
matching.useNewMatcher dynamic config on the relevant task queues (or namespaces).# New Priority DTO
$priority = Priority::new(priorityKey: 1);
# Set Priority on a Workflow
$workflow = $workflowClient->newWorkflowStub(
OrderWorkflowInterface::class,
WorkflowOptions::new()
->withTaskQueue('task-queue')
->withPriority($priority),
);
# New Priority DTO
$priority = Priority::new(priorityKey: 1);
# Set Priority on an Activity
$activity = Workflow::newActivityStub(
ActivityInterface::class,
ActivityOptions::new()
->withTaskQueue('task-queue')
->withStartToCloseTimeout('5 minutes')
->withPriority($priority),
);
# Set Priority on a Child Workflow
$childWorkflow = Workflow::newChildWorkflowStub(
ChildWorkflowInterface::class,
ChildWorkflowOptions::new()
->withTaskQueue('task-queue')
->withPriority($priority),
);
// Get
$priority = Activity::getInfo()->priority;
$priority = Workflow::getInfo()->priority;
[!NOTE]
- Lower numbers = higher priority.
- Tasks with the same priority are scheduled in FIFO order.
- If priority is unsupported by the server, these settings are silently ignored.
- Remember this feature is not production ready at this stage.
You can now add descriptions to Query, Signal, and Update handlers. Descriptions are available through the description parameter in QueryMethod, SignalMethod, and UpdateMethod attributes, as well as in the Workflow::registerSignal(), Workflow::registerQuery(), and Workflow::registerUpdate() methods. These descriptions will be displayed in the Temporal UI for better handler documentation.
Using Attributes:
#[QueryMethod('get_counter', description: 'Get the current counter value')]
public function getCounter(): int
{
return $this->counter;
}
#[SignalMethod('inc_counter', description: 'Increment the counter value')]
public function incCounter(): void
{
++$this->counter;
}
Using Registration Methods:
Workflow::registerQuery('get_counter', $this->getCounter(...), 'Get the current counter value');
Workflow::registerSignal('increment_counter', $this->incrementCounter(...), 'Increment the counter value');
You can now add custom metadata summaries to Activity and Timer executions. These summaries will be displayed in the Workflow history within the Temporal UI, providing better visibility into workflow execution details.
Activity Summary:
yield Workflow::executeActivity(
type: 'activity_type',
options: ActivityOptions::new()
->withScheduleToCloseTimeout(30)
->withSummary('Process user payment'),
);
Timer Summary:
yield Workflow::timer(
interval: 30,
options: TimerOptions::new()->withSummary('Wait for external service response'),
);
When a heartbeating activity is paused, an ActivityPausedException will be thrown.
Added Activity::getCancellationDetails() that returns ActivityCancellationDetails DTO that provides the reasons for the activity's cancellation.
summary option for timers and activities by @roxblnfk in https://github.com/temporalio/sdk-php/pull/626temporal-test-server for arm64 by @root-aza in https://github.com/temporalio/sdk-php/pull/629Full Changelog: https://github.com/temporalio/sdk-php/compare/v2.14.1...v2.15.0
Full Changelog: https://github.com/temporalio/sdk-php/compare/v2.14.0...v2.14.1
[!WARNING] RoadRunner 2024.3.3+ is required.
Logging is a critical component for monitoring and troubleshooting your Temporal applications. The PHP SDK now provides a dedicated logger for use within Workflows that respects replay semantics and adds contextual information automatically.
To get a PSR-3 compatible logger in your Workflow code, use the Workflow::getLogger() method:
use Temporal\Workflow;
#[Workflow\WorkflowInterface]
class MyWorkflow
{
#[Workflow\WorkflowMethod]
public function execute(string $param): \Generator
{
Workflow::getLogger()->info('Workflow started', ['parameter' => $param]);
// Your workflow implementation
Workflow::getLogger()->info('Workflow completed');
return 'Done';
}
}
An important feature of the Workflow logger is its replay-aware behavior. By default, logs are only emitted during the initial Workflow execution and are suppressed during replay to prevent duplicate log entries.
If you want to enable logging during replay (for debugging purposes), you can configure this with the enableLoggingInReplay option:
$factory = WorkerFactory::create();
$worker = $factory->newWorker('your-task-queue', WorkerOptions::new()
->withEnableLoggingInReplay(true)
);
The Workflow logger automatically enriches log entries with the current task queue information. Every log message will include a task_queue key in its context, making it easier to filter and correlate logs.
For example, if a log statement is:
$logger->info('Processing order', ['order_id' => 123]);
The actual logged context will be:
{ "task_queue": "your-task-queue", "order_id": 123 }
This happens automatically without any additional configuration.
By default, the PHP SDK uses a StderrLogger that outputs log messages to the standard error stream.
These messages are automatically captured by RoadRunner and incorporated into its logging system with the INFO level, ensuring proper log collection in both development and production environments.
For more details on RoadRunner's logging capabilities, see the RoadRunner Logger documentation.
You can configure your Temporal worker to use a custom PSR-3 compatible logger implementation:
$myLogger = new MyLogger();
$workerFactory = WorkerFactory::create(converter: $converter);
$worker = $workerFactory->newWorker(
taskQueue: 'my-task-queue',
logger: $myLogger,
);
Your custom logger will be used throughout the Temporal SDK, including for Workflow logging when accessed through Workflow::getLogger().
Added Activity::getInstance() and Workflow::getInstance() methods to get the current Activity and Workflow instances.
Changed workflow execution flow:
__construct() method is called.
#[WorkflowInit] attribute is present, the handler's arguments are resolved and passed to the constructor.WorkflowInboundCallInterceptor::execute() is called
Workflow::getInstance() returns the initialized Workflow instance.Added methods to define dynamic handlers for Signals, Updates, and Queries that will be called if a handler for a specific name is not found.
// Dynamic Query Handler
\Temporal\Workflow::registerDynamicQuery(function (string $name, ValuesInterface $arguments): string {
return \sprintf(
'Got query `%s` with %d arguments',
$name,
$arguments->count(),
);
});
// Dynamic Update Handler
\Temporal\Workflow::registerDynamicUpdate(
static fn(string $name, ValuesInterface $arguments): string => \sprintf(
'Got update `%s` with %d arguments',
$name,
$arguments->count(),
),
static fn(string $name, ValuesInterface $arguments) => \str_starts_with(
$name,
'update_',
) or throw new \InvalidArgumentException('Invalid update name'),
);
Added support for user metadata in Workflow Start/Schedule methods, improving the ability to attach additional information to workflow executions.
Client API
use Temporal\Client\GRPC\ServiceClient;
use Temporal\Client\ScheduleClient;
use Temporal\Client\Schedule\Action\StartWorkflowAction;
use Temporal\Client\WorkflowClient;
use Temporal\Client\WorkflowOptions;
$serviceClient = ServiceClient::create('127.0.0.1:7233');
// Start Workflow with user metadata
$workflowClient = WorkflowClient::create($serviceClient);
$stub = $workflowClient->newUntypedWorkflowStub(
'SimpleWorkflow',
(new WorkflowOptions())
->withStaticSummary('some text')
->withStaticDetails('details')
);
$workflowClient->start($stub);
// Describe workflow
echo $stub->describe()->config->userMetadata->summary;
echo $stub->describe()->config->userMetadata->details;
// Schedule Workflow with user metadata
$scheduleClient = ScheduleClient::create($serviceClient);
$schedule = $scheduleClient->createSchedule(
\Temporal\Client\Schedule\Schedule::new()
->withAction(StartWorkflowAction::new(SimpleWorkflow::class)
->withStaticSummary('some-summary')
->withStaticDetails('some-details'))
);
// Describe schedule
$action = $schedule->describe()->schedule->action;
assert($action instanceof StartWorkflowAction);
echo $action->userMetadata->details;
echo $action->userMetadata->summary;
Workflow context:
$stub = \Temporal\Workflow::newChildWorkflowStub(
SimpleWorkflow::class,
(new Workflow\ChildWorkflowOptions())
->withStaticSummary('some text')
->withStaticDetails('details')
);
UpdateHandle::fetchResult() by @roxblnfk in #591Full Changelog: https://github.com/temporalio/sdk-php/compare/v2.13.0...v2.14.0
Full Changelog: https://github.com/temporalio/sdk-php/compare/v2.13.3...v2.13.4
Full Changelog: https://github.com/temporalio/sdk-php/compare/v2.13.2...v2.13.3
Full Changelog: https://github.com/temporalio/sdk-php/compare/v2.13.1...v2.13.2
upsertMemo() method in interceptors by @roxblnfk in https://github.com/temporalio/sdk-php/pull/569Full Changelog: https://github.com/temporalio/sdk-php/compare/v2.13.0...v2.13.1
[!WARNING] RoadRunner 2024.3.3 is required.
A new approach for working with Search Attributes has been implemented - Typed Search Attributes.
For this, new methods have been added to WorkflowOptions DTO and Workflow facade.
$keyDestinationTime = SearchAttributeKey::forDatetime('DestinationTime');
$keyOrderId = SearchAttributeKey::forKeyword('OrderId');
$workflow = $workflowClient->newWorkflowStub(
OrderWorkflowInterface::class,
WorkflowOptions::new()
->withWorkflowExecutionTimeout('10 minutes')
->withTypedSearchAttributes(
TypedSearchAttributes::empty()
->withValue($keyOrderId, $orderid)
->withValue($keyDestinationTime, new \DateTimeImmutable('2028-11-05T00:10:07Z'))
),
);
#[Workflow\WorkflowInterface]
class OrderWorkflowInterface {
// ...
#[Workflow\UpdateMethod]
public function postponeDestinationTime(\DateInterval $interval)
{
// Get keys to work with
$keyDestinationTime = SearchAttributeKey::forDatetime('DestinationTime');
$keyToRemove = SearchAttributeKey::forKeyword('SomeFieldToRemove');
/** [@var](https://github.com/var) DateTimeImmutable $destinationTime */
$destinationTime = Workflow::getInfo()->typedSearchAttributes->get($keyDestinationTime);
Workflow::upsertTypedSearchAttributes(
$keyDestinationTime->valueSet($destinationTime->add($interval)),
$keyToRemove->valueUnset(),
);
}
}
When starting the Temporal Dev Server, you can specify types for Search Attributes.
$testEnv->startTemporalServer(searchAttributes: [
'testBool' => ValueType::Bool,
'testInt' => ValueType::Int,
'testFloat' => ValueType::Float,
'testString' => ValueType::String,
'testKeyword' => ValueType::Keyword,
'testKeywordList' => ValueType::KeywordList,
'testDatetime' => ValueType::Datetime,
]);
The new #[WorkflowInit] attribute has been added for the Workflow class constructor
This attribute allows you to receive arguments in the constructor that were passed when the Workflow was started.
The Workflow input arguments are also passed to your #[WorkflowMethod] method -- that always happens, whether or not you use the #[WorkflowInit] attribute.
This is useful if you have message handlers that need access to Workflow input: see Initializing the Workflow first.
use Temporal\Workflow;
#[Workflow\WorkflowInterface]
class GreetingExample
{
private readonly string $nameWithTitle;
private bool $titleHasBeenChecked;
// Note the attribute is on a public constructor
#[Workflow\WorkflowInit]
public function __construct(string $input)
{
$this->nameWithTitle = 'Sir ' . $input;
$this->titleHasBeenChecked = false;
}
#[Workflow\WorkflowMethod]
public function getGreeting(string $input)
{
yield Workflow::await(fn() => $this->titleHasBeenChecked);
return "Hello " . $this->nameWithTitle;
}
#[Workflow\UpdateMethod]
public function checkTitleValidity()
{
// 👉 The handler is now guaranteed to see the workflow input
// after it has been processed by the constructor.
$isValid = yield Workflow::executeActivity('activity.checkTitleValidity', [$this->nameWithTitle]);
$this->titleHasBeenChecked = true;
return $isValid;
}
}
[!WARNING] By default, the Workflow Handler runs before Signals and Updates in PHP SDK v2. This behavior is incorrect. To avoid breaking already written Workflows, since PHP SDK v2.11.0, a feature flag was added to enhance the behavior of the Workflow Handler. Make sure to set this flag to
trueto enable the correct behavior.
Added a method to update the Workflow's Memo Workflow::upsertMemo.
Workflow::upsertMemo([
'key1' => 'foo',
'key2' => 42,
'key3' => ['subkey1' => 'value']
'key4' => null, // remove key4
});
To improve Developer Experience, metadata for the MetaStorm plugin has been added. If you use MetaStorm, the IDE will now suggest Workflow classes and types in the corresponding methods.
Workflow::upsertMemo() by @roxblnfk in https://github.com/temporalio/sdk-php/pull/562InitMethod to WorkflowInit by @roxblnfk in https://github.com/temporalio/sdk-php/pull/564SearchAttributeKey::forString to SearchAttributeKey::forText by @roxblnfk in https://github.com/temporalio/sdk-php/pull/565Full Changelog: https://github.com/temporalio/sdk-php/compare/v2.12.3...v2.13.0
Full Changelog: https://github.com/temporalio/sdk-php/compare/v2.12.2...v2.12.3
namespace from ClientOptions by default on creating of Schedule by @roxblnfk in https://github.com/temporalio/sdk-php/pull/549Full Changelog: https://github.com/temporalio/sdk-php/compare/v2.12.1...v2.12.2
Full Changelog: https://github.com/temporalio/sdk-php/compare/v2.12.0...v2.12.1
Added new class Mutex that provides a deterministic mechanism for concurrency control within Workflow.
It can be used within Signals, Updates, and the main Workflow handlers.
Added method Workflow::runLocked(Mutex $mutex, callable $callable): PromiseInterface to execute a function in a locked context.
use Temporal\Workflow;
#[Workflow\WorkflowInterface]
class TestWorkflow
{
#[Workflow\WorkflowMethod]
public function handle(): \Generator
{
$mutex = new Workflow\Mutex();
// Wait for the Mutex to be unlocked
yield $mutex;
assert(false === $mutex->isLocked(), 'The Mutex is unlocked here');
// Try to lock the Mutex, returns true if the Mutex was successfully locked
assert(true === $mutex->tryLock());
// Try to lock the Mutex again, returns false because the Mutex is already locked
assert(false === $mutex->tryLock());
// We may use it in Workflow::await() and Workflow::awaitWithTimeout() like a condition.
// It means wait until the Mutex is unlocked or timeout.
yield Workflow::awaitWithTimeout('5 seconds', $mutex);
assert(true === $mutex->isLocked(), 'The Mutex is still locked here because of the timeout');
$mutex->unlock(); // Unlock for the next test
// Get the mutex locked state and unlock it after the function is finished.
// The function will be executed asynchronously, so we can use `yield` inside to wait for the result.
// We don't need to control the Mutex manually, it will be locked and unlocked automatically.
yield Workflow::runLocked($mutex, static function () {
// Mutex is locked here
yield Workflow::timer('1 second');
// Mutex is still locked here
});
assert(false === $mutex->isLocked(), 'Mutex is unlocked here');
// In this example, we run 5 functions in parallel, but only one function can be executed at a time.
// When the first function is finished, the next function will be executed.
// All functions will be executed in the order they were added.
yield \Temporal\Promise::all([
Workflow::runLocked($mutex, $this->doSomething()),
Workflow::runLocked($mutex, $this->doSomething()),
Workflow::runLocked($mutex, $this->doSomething()),
Workflow::runLocked($mutex, $this->doSomething()),
Workflow::runLocked($mutex, $this->doSomething()),
]);
// Additionally, you can cancel the Promise returned by `runLocked` to interrupt or discard the function.
$promise = Workflow::runLocked($mutex, $this->doSomethingSomeTime());
$promise->cancel();
}
}
You can also check the related example in the php-samples repository.
Added WorkflowClientInterface::updateWithStart() that starts a new Workflow execution, runs an update function, and returns UpdateHandle.
If the specified workflow execution is not running, then a new workflow execution is started and the update is sent in the first workflow task.
Alternatively if the specified workflow execution is running then, if the WorkflowIDConflictPolicy is UseExisting, the update is issued against the specified workflow, and if the WorkflowIDConflictPolicy is Fail, an error is returned.
The call will block until the update has reached the LifecycleStage in the UpdateOptions. Note that this means that the call will not return successfully until the update has been delivered to a worker.
Note: the feature is experimental, and the flag enableExecuteMultiOperation might be required to be set to true in the Temporal server configuration.
BTW startWithSignal() is deprecated now, added signalWithStart()
Added a new class ServiceCredentials that allows you to set the ApiKey for the RoadRunner worker.
The README file of the repository has been updated. Many useful links and notes have been added.
UpdateWithStart by @roxblnfk in #536query parameter for Schedule list by @roxblnfk in #538ApiKey to RoadRunner with WorkerInfo by @roxblnfk in #539Full Changelog: https://github.com/temporalio/sdk-php/compare/v2.11.3...v2.12.0
Full Changelog: https://github.com/temporalio/sdk-php/compare/v2.11.3...v2.11.4
Full Changelog: https://github.com/temporalio/sdk-php/compare/v2.11.2...v2.11.3
Full Changelog: https://github.com/temporalio/sdk-php/compare/v2.11.1...v2.11.2
hasExecution docstring by @jmortlock in https://github.com/temporalio/sdk-php/pull/519Full Changelog: https://github.com/temporalio/sdk-php/compare/v2.11.0...v2.11.1
Since version 2.11.0, the SDK has introduced feature flags that allow you to change the behavior of the SDK at the level of the entire PHP worker (process). They are introduced for a consistent migration to more correct behavior, which will be established in the next major version or earlier.
To set a feature flag, you need to use the Temporal\Worker\FeatureFlags class in the beginning of your worker script:
use Temporal\Worker\FeatureFlags;
// Include the Composer autoloader
require __DIR__ . '/vendor/autoload.php';
// Set the feature flags
FeatureFlags::$workflowDeferredHandlerStart = true;
FeatureFlags::$warnOnWorkflowUnfinishedHandlers = true;
An important fix was made in the SDK: previously, if a Workflow was started with a Signal, the Workflow method would begin execution first.
This happened because the initialization of the Workflow method generator would start executing the generator code up to the first yield.
However, this behavior does not meet expectations: Signals should start first, followed by the Workflow method.
Since this change may break backward compatibility, it has been hidden behind a Feature Flag Temporal\Worker\FeatureFlags::$workflowDeferredHandlerStart.
Added logging of unfinished Signal and Update handlers when a Workflow finishes.
Logging is performed using the error_log() function and by default is output to stderr.
The flag responsible for this behavior is Temporal\Worker\FeatureFlags::$warnOnWorkflowUnfinishedHandlers, which is also disabled by default.
It is recommended to enable this flag and assess its impact on your application, as this behavior is likely to be enabled by default in future SDK versions.
Additionally, if unfinished handlers are not an error, you can individually set the $unfinishedPolicy option in the corresponding attribute
#[Workflow\WorkflowInterface]
interface MyWorkflow
{
#[Workflow\WorkflowMethod]
public function run();
#[Workflow\SignalMethod(unfinishedPolicy: HandlerUnfinishedPolicy::Abandon)]
public function mySignal(): void;
#[Workflow\UpdateMethod(unfinishedPolicy: HandlerUnfinishedPolicy::Abandon)]
public function myUpdate(): void;
}
To determine if all handlers have finished, you can use the new method Workflow::allHandlersFinished():
#[Workflow\WorkflowMethod]
public function handler()
{
// ...
// Wait for all handlers to finish
yield Workflow::await(
static fn() => Workflow::allHandlersFinished(),
);
}
Client:
WorkflowUpdateRPCTimeoutOrCanceledException that will be thrown instead of TimeoutException in Update cases.WorkflowStub::getUpdateHandle() method that returns UpdateHandle by UpdateId:
$stub = $workflowClient->newUntypedRunningWorkflowStub($wfId, $wfRunId, $wfType);
$handle = $stub->getUpdateHandle($updateId);
$handle->getResult(5);
Worker:
ExceptionInterceptor is used to detect that exception is error that breaks task or failure that fails update.Workflow::getUpdateContext(), you can get UpdateContext that contains UpdateId.Previously, only Signal and Query handlers could be registered in a Workflow dynamically. Now, this is also possible for Update handlers.
The method Workflow::registerUpdate() allows passing a validator along with the handler:
// Workflow scope
Workflow::registerUpdate(
'my-update',
fn(Task $task) => $this->queue->push($task),
fn(Task $task) => $this->isValidTask($task) or throw new \InvalidArgumentException('Invalid task'),
);
A new way to update Schedule via callback, similar to other SDKs, has been added.
The method ScheduleHandle::update() accepts a closure that takes ScheduleUpdateInput and returns ScheduleUpdate.
ScheduleUpdateInput is generated on the SDK side along with the describe() method call.
Updating Schedule via callback allows modifying Search Attributes:
$handle->update(
fn (ScheduleUpdateInput $input): ScheduleUpdate => ScheduleUpdate::new($input->description->schedule)
->withSearchAttributes(
$input->description->searchAttributes
->withValue('foo', 'bar'),
->withValue('bar', 42),
);
);
ActivityPrototype::$factory by @Nyholm in https://github.com/temporalio/sdk-php/pull/492WorkerOptions by @roxblnfk in https://github.com/temporalio/sdk-php/pull/489ShouldContinueAsNew and HistorySize by @roxblnfk in https://github.com/temporalio/sdk-php/pull/475
Workflow::getInfo()->historySize;
Workflow::getInfo()->shouldContinueAsNew;
WorkflowStub::getUpdateHandle by @roxblnfk in https://github.com/temporalio/sdk-php/pull/484Workflow::allHandlersFinished() by @roxblnfk in https://github.com/temporalio/sdk-php/pull/486Full Changelog: https://github.com/temporalio/sdk-php/compare/v2.10.3...v2.11.0
Full Changelog: https://github.com/temporalio/sdk-php/compare/v2.10.2...v2.10.3
Full Changelog: https://github.com/temporalio/sdk-php/compare/v2.10.1...v2.10.2
Full Changelog: https://github.com/temporalio/sdk-php/compare/v2.10.0...v2.10.1
$lifecycleStage into Update\UpdateOptions::new() by @roxblnfk in #439Full Changelog: https://github.com/temporalio/sdk-php/compare/v2.9.2...v2.10.0
Full Changelog: https://github.com/temporalio/sdk-php/compare/v2.9.1...v2.9.2
Full Changelog: https://github.com/temporalio/sdk-php/compare/v2.9.0...v2.9.1
Issue #224
In the Service Client, a method getConnection() has been added, which returns a ConnectionInterface.
ConnectionInterface includes public methods isConnected(), disconnect(), connect(float $timeout), allowing
control over the connection to the Temporal server.
A gRPC connection is lazy in PHP and is established only when the first gRPC method is called.
Using the connect() method, users can immediately establish and verify connection credentials without the need for gRPC method calls.
Users can sequentially call connect() and disconnect() without causing errors related to closed gRPC channels, as the channels are recreated under the hood.
/** [@var](https://github.com/var) \Temporal\Client\WorkflowClient $workflowClient */
// Establish a connection with the server.
// An exception will be thrown if the connection is not established within 5 seconds.
$workflowClient->getServiceClient()->getConnection()->connect(5);
Server Capabilities info now is cached in the Connection object and will be updated on each reconnect.
/** [@var](https://github.com/var) \Temporal\Client\WorkflowClient $workflowClient */
// Establish a connection with the server and call getSystemInfo() RPC method.
$capabilities = $workflowClient->getServiceClient()->getServerCapabilities();
if (!$capabilities?->supportsSchedules) } {
throw new \Temporal\Exception\TemporalException('Server does not support schedules');
}
A deprecation error will now be triggered when a ServiceClient is created directly through the constructor. Static factories are the only recommended way to create a ServiceClient.
One of such static methods has changes:
in ServiceClient::createSSL(), the root certificate parameter has been made optional because it should be skipped when connecting to the Temporal Cloud.
All keys can be passed as a string payload (previously, only by file name was allowed). If the provided file cannot be read, a clear exception will be thrown.
The ClientService now can accept an API key for authentication.
$serviceClient = \Temporal\Client\GRPC\ServiceClient::createSSL(
'temporal.my-project.com:7233',
__DIR__ . '/my-project.key',
__DIR__ . '/my-project.crt',
)->withAuthKey($key); // $key is a string or a \Stringable object
$workflowClient = new \Temporal\Client\WorkflowClient($serviceClient);
You may pass your own Stringable implementation as the $key argument to be able to change the key dynamically.
Issue #338
Added new methods to WorkflowClient, ScheduleClient and ScheduleHandle:
withTimeout(float $timeout)withDeadline()withRetryOptions()withMetadata()They may be used before calling any method that sends a gRPC request to the server.
/** [@var](https://github.com/var) \Temporal\Client\ScheduleClient $scheduleClient */
$list = $scheduleClient->withTimeout(5)->listSchedules();
All the new methods are immutable and return a new instance of the client that will use the same connection as the original client, but with the specified timeout, deadline or retry options.
/** [@var](https://github.com/var) \Temporal\Client\WorkflowClient $workflowClient */
// All the calls $workflow->* will be executed with a 5-second timeout.
$workflow = $workflowClient->withTimeout(5)->newWorkflowStub(MyWorkflow::class);
// Will be called with a 10-second timeout.
$workflowClient->withTimeout(10)->start($workflow, 'foo', 'bar')
// Will be called with a 5-second timeout because the stub was created with a 5-second timeout client.
$workflow->signal();
Note: WorkflowClientInterface and ScheduleClientInterface have been updated with the new methods.
Issue #421
Client RPC requests have a new algorithm for calculating the timeout until the next retry attempt:
RESOURCE_EXHAUSTED error, the interval is 1000ms.All settings are configurable:
$workflowClient->withRetryOptions(
\Temporal\Client\Common\RpcRetryOptions::new()
->withInitialInterval('500 milliseconds')
->withCongestionInitialInterval('5 seconds')
->withMaximumInterval('5 minutes')
->withBackoffCoefficient(5)
->withMaximumAttempts(4)
->withJitter(0.25)
);
A mistake was made in the implementation of several client functions last time: instead of using the Namespace value from ClientOptions, a parameter with the default value "default" was used. This complicates the use of Temporal Cloud, where user's Namespace differs from "default".
Affected methods are:
WorkflowClient::listWorkflowExecutions()WorkflowClient::countWorkflowExecutions()WorkflowClient::getWorkflowHistory()ScheduleClient::getHandle()ScheduleClient::listSchedules()The $namespace parameter is now null by default. If a method receives null, the Namespace from ClientOptions will be used.
Use the API to obtain comprehensive information about a started Workflow.
$stub = $workflowClient->newWorkflowStub(SimpleWorkflow::class);
$run = $workflowClient->start($stub, 'Hello World!');
/** [@var](https://github.com/var) WorkflowExecutionDescription $description */
$description = $run->describe();
You can use the Workflow Describe feature to get the status of a running Workflow.
$stub = $workflowClient->newUntypedRunningWorkflowStub($wfId);
/** [@var](https://github.com/var) WorkflowExecutionStatus $status */
$status = $stub->describe()->info->status;
WorkflowIdConflictPolicy that can be passed to WorkflowOptions in the client API (#417)[@psalm-immutable](https://github.com/psalm-immutable) attribute has been removed from all interceptor interfaces.SystemInfoInterceptor constructor parameter: ConnectionInterface instead of ServiceClient.SystemInfoInterceptor, new features related to caching Server Capabilities inside the Connection are considered.ServiceClient::setServerCapabilities() method has been removed from the ServiceClientInterface. The implementing method just triggers a deprecation error.ServiceClient::getServerCapabilities() method now loads the Server Capabilities from the Connection object instead of just returning the cached value.ServerCapabilities DTO: added all the new fields; the flags are available as public properties.Temporal\Client\WorkflowExecutionHistory, Workflow\Client\CountWorkflowExecutions, Workflow\Client\Paginator and Workflow\Client\ServerCapabilities into other namespaces.WorkerVersionStamp::$bundleId is deprecated now (#417)Full Changelog: https://github.com/temporalio/sdk-php/compare/v2.8.3...v2.9.0
DateInterval to protobuf Duration conversion by @Zylius in #424Full Changelog: https://github.com/temporalio/sdk-php/compare/v2.8.2...v2.8.3
StartDelay by @tlalfano in #409DurationJsonType::parse() fix by @Zylius in #412Full Changelog: https://github.com/temporalio/sdk-php/compare/v2.8.1...v2.8.2
How can I help you explore Laravel packages today?