Skip to content

Releases: temporalio/sdk-php

v2.12.0

24 Dec 16:23
v2.12.0
b9346fa
Compare
Choose a tag to compare

Mutex

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.

An explanation (click to expand)
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.

UpdateWithStart

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()

Worker options

Added a new class ServiceCredentials that allows you to set the ApiKey for the RoadRunner worker.

Readme update

The README file of the repository has been updated. Many useful links and notes have been added.

Pull Requests

Full Changelog: v2.11.3...v2.12.0

v2.11.4

17 Dec 19:17
ef3f586
Compare
Choose a tag to compare

What's Changed

  • Fix exception propagation from nested promises inside yielded generator by @roxblnfk in #537

Full Changelog: v2.11.3...v2.11.4

v2.11.3

09 Dec 14:56
5c86db5
Compare
Choose a tag to compare

What's Changed

Full Changelog: v2.11.2...v2.11.3

v2.11.2

28 Oct 22:22
Compare
Choose a tag to compare

What's Changed

Full Changelog: v2.11.1...v2.11.2

v2.11.1

28 Oct 14:37
14f69e9
Compare
Choose a tag to compare

What's Changed

  • Fix hierarchical Workflow classes processing by @roxblnfk in #513
  • Fix SearchAttributes in ChildWorkflow by @roxblnfk in #515
  • Fix typo in hasExecution docstring by @jmortlock in #519
  • Fix: use environment options to create RoadRunnerActivityInvocationCache by @jmortlock in #520

New Contributors

Full Changelog: v2.11.0...v2.11.1

v2.11.0

25 Sep 13:34
v2.11.0
b714bab
Compare
Choose a tag to compare

Worker

Feature Flags

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;

Signal with start

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.

Warn about unfinished handlers

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(),
    );
}

Workflow Update

Common

Client:

  • Added WorkflowUpdateRPCTimeoutOrCanceledException that will be thrown instead of TimeoutException in Update cases.
  • Exposed the new WorkflowStub::getUpdateHandle() method that returns UpdateHandle by UpdateId:
    $stub = $workflowClient->newUntypedRunningWorkflowStub($wfId, $wfRunId, $wfType);
    $handle = $stub->getUpdateHandle($updateId);
    $handle->getResult(5);

Worker:

  • Now ExceptionInterceptor is used to detect that exception is error that breaks task or failure that fails update.
  • Using the new method Workflow::getUpdateContext(), you can get UpdateContext that contains UpdateId.

Register Update handler dynamically

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'),
);

Schedule Update with Search Attributes

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),
        );
);

Other changes

  • Added getter for ActivityPrototype::$factory by @Nyholm in #492
  • Updated WorkerOptions by @roxblnfk in #489
  • Exposed ShouldContinueAsNew and HistorySize by @roxblnfk in #475
    Workflow::getInfo()->historySize;
    Workflow::getInfo()->shouldContinueAsNew;

Pull Requests

New Contributors

Full Changelog: v2.10.3...v2.11.0

v2.10.3

05 Jul 16:25
v2.10.3
adcfc27
Compare
Choose a tag to compare

What's Changed

  • Fix Child Workflow Task Queue Inheritance by @roxblnfk in #452
  • Fix EncodedValues to return null if possible when there is no value in Payloads by @roxblnfk in #467
  • Fix default Data Converters set to be able to decode binary/protobuf messages by @roxblnfk in #468
  • Fix empty scheduled workflow id by @roxblnfk in #469
  • Fix signaling for child workflow when it was continued as new by @roxblnfk in #470
  • Fix TaskQueue inheritance when workflow is continued as new by @roxblnfk in #471

Full Changelog: v2.10.2...v2.10.3

v2.10.2

04 Jun 06:14
v2.10.2
a73b1fb
Compare
Choose a tag to compare

What's Changed

Full Changelog: v2.10.1...v2.10.2

v2.10.1

27 May 14:55
v2.10.1
337f6ee
Compare
Choose a tag to compare

What's Changed

  • Fix Payloads failing decoding in case when an external Temporal SDK returns empty payload that doesn't contain even a NULL value by @wolfy-j in #442
  • Fix a Workflow hang in cases where a non-promise value is yielded by @roxblnfk in #443

Full Changelog: v2.10.0...v2.10.1

v2.10.0

21 May 18:26
v2.10.0
cae430c
Compare
Choose a tag to compare

What's Changed

  • Convert Policy Constants to Enums by @roxblnfk in #438
  • Add a new parameter $lifecycleStage into Update\UpdateOptions::new() by @roxblnfk in #439

Full Changelog: v2.9.2...v2.10.0