NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
Packagist · #1328 most downloaded on Packagist
Temporal SDK
Last release 24 days ago
14 Sep 2026
Release timing varies
gaps range from 2 weeks to 6 months
Rarely documented
notes for 12 of the last 60 stable releases
Nothing withdrawn
no release was ever pulled
6 years old
93 releases · first in 2021
One column per quarter.
Added patches directory and patches.lock.json to .gitattributes by @root-aza in #721
Full Changelog: v2.17.0...v2.17.1
…enhanced testing & CI reliability . Several deprecations were introduced to improve long-term API consistency.
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.
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.
Workflows can now expose structured “current details” metadata, improving runtime inspection and debugging of workflow state.
Activities can now access their configured retry policy:
$context = Activity::getContext();
$retryPolicy = $context->getRetryOptions();This enables dynamic behavior depending on retry configuration.
ApplicationFailure now exposes ErrorCategory, allowing more precise error classification and handling logic in workflows.
Introduced RawValue for pass-through or untyped payload handling.
Useful when custom serialization control is required.
Improved environment configuration visibility and integration, especially useful for CI and containerized deployments.
Added a wrapper to properly handle non-zero exit codes on Windows systems.
Improves cross-platform development and CI reliability.
#[ActivityMethod] AttributeUsing 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::$warnOnActivityMethodWithoutAttribute
See https://github.com/temporalio/sdk-php/blob/962c897757d9e9c29d579edb12c94e01b1c6fd52/src/Worker/FeatureFlags.php#L66C25-L66C61
Improved error messages when workflow/activity context is misused outside its valid execution scope.
Exceptions are now more descriptive and actionable.
WorkflowRunInterface::getResult($type)Improved typed result retrieval and clearer error feedback when incorrect types are requested.
Improved handling and warnings for ambiguous DateInterval usage to avoid subtle time calculation inconsistencies.
Enhanced validation in Priority::withFairnessWeight() with stricter guarantees and additional test coverage.
Search attribute parsing is now more tolerant to slightly variant input types, improving robustness.
General improvement of error clarity across workflow and activity contexts.
Ensures correct exception propagation and typing consistency.
Fixes mismatch between Temporal runtime behavior and PHP default timezone.
Ensures forward compatibility with PHP 8.4.
symfony/process Minimum VersionMinimum supported version bumped to 5.4.51.
PR: #712
Author: @mjameswh
(New contributor)
Acceptance tests split into Fast / Slow
PR: #708
Author: @xepozz
Use IP instead of localhost in tests
PR: #713
Author: @xepozz
Allow continue-on-error in validate-prefer-lowest
PR: #696
Author: @xepozz
Warning RoadRunner 2025.1.3+ is required.
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:
Promise::race() with a timer to properly handle cancellation.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 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 \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-loggeruse 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
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 #651Full Changelog: v2.15.0...v2.16.0
Remove experimental note from updateWithStart() client method by @roxblnfk in #637
updateWithStart() client method by @roxblnfk in #637Full Changelog: v2.15.0...v2.15.1
Warning RoadRunner 2025.1.2 is required.
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
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 #626temporal-test-server for arm64 by @root-aza in #629Full Changelog: v2.14.1...v2.15.0
Fix WorkflowInit detection by @roxblnfk in #611
Warning RoadRunner 2024.3.3+ is required.
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')
);Full Changelog: v2.13.0...v2.14.0
Fixed memory leak on upsert Memo / Search Attributes / Typed Search Attributes by @roxblnfk in #590
Full Changelog: v2.13.3...v2.13.4
Fixed interaction with Temporal Cloud in custom namespaces by @roxblnfk in #583
Full Changelog: v2.13.2...v2.13.3
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Compare
Compare
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Your coding agent can read these notes before it upgrades. Set up the MCP server →