Skip to content

Releases: temporalio/sdk-php

v2.13.4

08 Apr 12:36
v2.13.4
9d5fb5c
Compare
Choose a tag to compare

What's changed

  • Fixed memory leak on upsert Memo / Search Attributes / Typed Search Attributes by @roxblnfk in #590

Full Changelog: v2.13.3...v2.13.4

v2.13.3

15 Mar 05:40
v2.13.3
a11699a
Compare
Choose a tag to compare

What's Changed

  • Fixed interaction with Temporal Cloud in custom namespaces by @roxblnfk in #583

Full Changelog: v2.13.2...v2.13.3

v2.13.2

19 Feb 10:45
347e344
Compare
Choose a tag to compare

What's Changed

  • Fix decoding of non-nullable interval fields when null is gotten from Temporal Cloud. By @roxblnfk in #570

Full Changelog: v2.13.1...v2.13.2

v2.13.1

12 Feb 20:30
2b4202b
Compare
Choose a tag to compare

What's Changed

  • Add default implementation for upsertMemo() method in interceptors by @roxblnfk in #569

Full Changelog: v2.13.0...v2.13.1

v2.13.0

12 Feb 15:34
v2.13.0
7489256
Compare
Choose a tag to compare

Warning

RoadRunner 2024.3.3 is required.

Search Attributes

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

Workflow Init

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 true to enable the correct behavior.

Memo

Added a method to update the Workflow's Memo Workflow::upsertMemo.

Workflow::upsertMemo([
    'key1' => 'foo',
    'key2' => 42,
    'key3' => ['subkey1' => 'value']
    'key4' => null, // remove key4
});

MetaStorm metadata

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.

image

Pull Requests

Full Changelog: v2.12.3...v2.13.0

v2.12.3

14 Jan 09:12
fc67190
Compare
Choose a tag to compare

What's Changed

  • Activated Acceptance extra tests; bugfixes by @roxblnfk in #551

Full Changelog: v2.12.2...v2.12.3

v2.12.2

09 Jan 20:19
34e49ca
Compare
Choose a tag to compare

What's Changed

  • Use namespace from ClientOptions by default on creating of Schedule by @roxblnfk in #549

Full Changelog: v2.12.1...v2.12.2

v2.12.1

07 Jan 15:01
bf33938
Compare
Choose a tag to compare

What's Changed

  • Fix calling update method with unknown workflow type by @roxblnfk in #547
  • Fix a floating error in the WorkerRestart test by @igancev in #542

New Contributors

Full Changelog: v2.12.0...v2.12.1

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