# tests/.rr.test.yaml
kv:
  test:
    driver: memory
    config:
      interval: 10
```

If you want to be able to mock Activities, use `WorkerFactory` from the `Temporal\Testing` Namespace
in your PHP Worker:

```php
// worker.test.php
use Temporal\Testing\WorkerFactory;

$factory = WorkerFactory::create();
$worker = $factory->newWorker();

$worker->registerWorkflowTypes(MyWorkflow::class);
$worker->registerActivity(MyActivity::class);
$factory->run();
```

Then, in your tests to mock an Activity, use the`ActivityMocker` class.

Assume we have the following Activity:

```php
#[ActivityInterface(prefix: "SimpleActivity.")]
interface SimpleActivityInterface
{
    #[ActivityMethod('doSomething')]
    public function doSomething(string $input): string;
```

To mock it in the test, you can do this:

```php
final class SimpleWorkflowTestCase extends TestCase
{
    private WorkflowClient $workflowClient;
    private ActivityMocker $activityMocks;

    protected function setUp(): void
    {
        $this->workflowClient = new WorkflowClient(ServiceClient::create('localhost:7233'));
        $this->activityMocks = new ActivityMocker();

        parent::setUp();
    }

    protected function tearDown(): void
    {
        $this->activityMocks->clear();
        parent::tearDown();
    }

    public function testWorkflowReturnsUpperCasedInput(): void
    {
        $this->activityMocks->expectCompletion('SimpleActivity.doSomething', 'world');
        $workflow = $this->workflowClient->newWorkflowStub(SimpleWorkflow::class);
        $run = $this->workflowClient->start($workflow, 'hello');
        $this->assertSame('world', $run->getResult('string'));
    }
}
```

In the preceding test case, we do the following:

1. Instantiate `ActivityMocker` in the `setUp()` method of the test.
2. Clear the cache after each test in `tearDown()`.
3. Mock an Activity call to return a string `world`.

To mock a failure, use the `expectFailure()` method:

```php
$this->activityMocks->expectFailure('SimpleActivity.echo', new \LogicException('something went wrong'));
```

### How to skip time {#skip-time}

Some long-running Workflows can persist for months or even years.
Implementing the test framework allows your Workflow code to skip time and complete your tests in seconds rather than the Workflow's specified amount.

For example, if you have a Workflow sleep for a day, or have an Activity failure with a long retry interval, you don't need to wait the entire length of the sleep period to test whether the sleep function works.
Instead, test the logic that happens after the sleep by skipping forward in time and complete your tests in a timely manner.

The test framework included in most SDKs is an in-memory implementation of Temporal Server that supports skipping time.
Time is a global property of an instance of `TestWorkflowEnvironment`: skipping time (either automatically or manually) applies to all currently running tests.
If you need different time behaviors for different tests, run your tests in a series or with separate instances of the test server.
For example, you could run all tests with automatic time skipping in parallel, and then all tests with manual time skipping in series, and then all tests without time skipping in parallel.

#### Set up time skipping {#setting-up}

Set up the time-skipping test framework in the SDK of your choice.

1. In the `tests` folder, create `bootstrap.php` with the following contents:

```php
declare(strict_types=1);

require __DIR__ . '/../vendor/autoload.php';

use Temporal\Testing\Environment;

$environment = Environment::create();
$environment->start();
register_shutdown_function(fn () => $environment->stop());
```

If you don't want to run the test server with all of your tests, you can add a condition to start a test only if the `RUN_TEMPORAL_TEST_SERVER` environment variable is present:

```php
if (getenv('RUN_TEMPORAL_TEST_SERVER') !== false) {
    $environment = Environment::create();
    $environment->start('./rr serve -c .rr.silent.yaml --workflow-id tests');
    register_shutdown_function(fn() => $environment->stop());
}
```

2. Add `bootstrap.php` and the `TEMPORAL_ADDRESS` environment variable to `phpunit.xml`:

```xml
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
         bootstrap="tests/bootstrap.php"
>
    <php>
        <env name="TEMPORAL_ADDRESS" value="127.0.0.1:7233" />
    </php>
</phpunit>
```

3. Add the test server executable to `.gitignore`:

```gitignore
temporal-test-server
```

## How to Replay a Workflow Execution {#replay}

Replay recreates the exact state of a Workflow Execution.
You can replay a Workflow from the beginning of its Event History.

Replay succeeds only if the [Workflow Definition](/workflow-definition) is compatible with the provided history from a deterministic point of view.

When you test changes to your Workflow Definitions, we recommend doing the following as part of your CI checks:

1. Determine which Workflow Types or Task Queues (or both) will be targeted by the Worker code under test.
2. Download the Event Histories of a representative set of recent open and closed Workflows from each Task Queue, either programmatically using the SDK client or via the Temporal CLI.
3. Run the Event Histories through replay.
4. Fail CI if any error is encountered during replay.

The following are examples of fetching and replaying Event Histories:

To replay Workflow Executions, use the `\Temporal\Testing\Replay\WorkflowReplayer` class.

In the following example, Event Histories are fetching from the Temporal, and then replayed.
If the Workflow is non-deterministic, a `NonDeterministicWorkflowException` will be thrown.
Note that this requires [Advanced Visibility](/visibility#advanced-visibility) to be enabled.

```php
/**
 * We assume you already have a WorkflowClient and WorkflowReplayer in scope.
 * @var \Temporal\Client\WorkflowClientInterface $workflowClient
 * @var \Temporal\Testing\Replay\WorkflowReplayer $replayer
 */

// Find all workflow executions of type "MyWorkflow" and task queue "MyTaskQueue".
$executions = $workflowClient->listWorkflowExecutions(
    "WorkflowType='MyWorkflow' AND TaskQueue='MyTaskQueue'"
);

// Replay each workflow execution.
foreach ($executions as $executionInfo) {
    try {
        $replayer->replayFromServer(
            workflowType: $executionInfo->type->name,
            execution: $executionInfo->execution,
        );
    } catch (\Temporal\Testing\Replay\Exception\ReplayerException $e) {
        // Handle a replay error.
    }
}
```

In the next example, an Event History is loaded from a JSON file, and the maximum number of replayed Events is limited to 42.

```php
$replayer->replayFromJSON(
    workflowType: 'MyWorkflow',
    path: 'history.json',
    lastEventId: 42,  // optional
);
```

You can download a Workflow History using PHP, and then replay it from a memorized History object:

```php
$history = $this->workflowClient->getWorkflowHistory(
    execution: $run->getExecution(),
)->getHistory();

(new WorkflowReplayer())->replayHistory($history);
```

---

## Durable Timers - PHP SDK

## What is a Timer? {#timers}

A Workflow can set a durable timer for a fixed time period.
In some SDKs, the function is called `sleep()`, and in others, it's called `timer()`.

A Workflow can sleep for months.
Timers are persisted, so even if your Worker or Temporal Service is down when the time period completes, as soon as your Worker and Temporal Service are back up, the `sleep()` call will resolve and your code will continue executing.

Sleeping is a resource-light operation: it does not tie up the process, and you can run millions of Timers off a single Worker.

To set a Timer in PHP, use `Workflow::timer()` and pass the number of seconds you want to wait before continuing.

The following example yields a sleep method for 5 minutes.

```php
yield Workflow::timer(300); // sleep for 5 minutes
```

You cannot set a Timer invocation inside the `await` or `awaitWithTimeout` methods.

---

## Versioning - PHP SDK feature guide

Since Workflow Executions in Temporal can run for long periods — sometimes months or even years — it's common to need to make changes to a Workflow Definition, even while a particular Workflow Execution is in progress.

The Temporal Platform requires that Workflow code is [deterministic](/workflow-definition#deterministic-constraints).
If you make a change to your Workflow code that would cause non-deterministic behavior on Replay, you'll need to use one of our Versioning methods to gracefully update your running Workflows.
With Versioning, you can modify your Workflow Definition so that new executions use the updated code, while existing ones continue running the original version.
There are two primary Versioning methods that you can use:

- [Worker Versioning](/production-deployment/worker-deployments/worker-versioning). The Worker Versioning feature allows you to tag your Workers and programmatically roll them out in versioned deployments, so that old Workers can run old code paths and new Workers can run new code paths.
- [Versioning with Patching](#php-sdk-patching-api). This method works by adding branches to your code tied to specific revisions. It applies a code change to new Workflow Executions while avoiding disruptive changes to in-progress Workflow Executions.

## Worker Versioning

Temporal's [Worker Versioning](/production-deployment/worker-deployments/worker-versioning) feature allows you to tag your Workers and programmatically roll them out in Deployment Versions, so that old Workers can run old code paths and new Workers can run new code paths. This way, you can pin your Workflows to specific revisions, avoiding the need for patching.

## Versioning with Patching {#php-sdk-patching-api}

### Patching with GetVersion

A Patch defines a logical branch in a Workflow for a specific change, similar to a feature flag.
It applies a code change to new Workflow Executions while avoiding disruptive changes to in-progress Workflow Executions.
When you want to make substantive code changes that may affect existing Workflow Executions, create a patch. Note that there's no need to patch [Pinned Workflows](/worker-versioning).

Suppose you have an initial Workflow that runs `prePatchActivity`:

```php
#[WorkflowInterface]
class MyWorkflow
{
    private $activity;

    public function __construct()
    {
        $this->activity = Workflow::newActivityStub(
            YourActivityInterface::class,
            ActivityOptions::new()->withScheduleToStartTimeout(60)
        );
    }

    #[WorkflowMethod]
    public function runAsync()
    {
        $result = yield $this->activity->prePatchActivity();
    }
}
```

Suppose you replaced `prePatchActivity` with `postPatchActivity` and deployed the updated code.

If an existing Workflow Execution was started by the original version of the Workflow code, where `prePatchActivity` was run, and then resumed running on a new Worker where it was replaced with `postPatchActivity`, the server side Event History would be out of sync.
This would cause the Workflow to fail with a nondeterminism error.

To resolve this, you can use [Workflow::getVersion](https://php.temporal.io/classes/Temporal-Workflow.html#method_getVersion) to patch to your Workflow:

```php
#[WorkflowInterface]
class MyWorkflow
{
    // ...

    #[WorkflowMethod]
    public function runAsync()
    {
        $version = yield Workflow::getVersion('Step 1', Workflow::DEFAULT_VERSION, 1);

        $result = $version === Workflow::DEFAULT_VERSION
            ? yield $this->activity->prePatchActivity()
            : yield $this->activity->postPatchActivity();
    }
}
```

When `getVersion()` is run for the new Workflow Execution, it records a marker in the Event History so that all future calls to `getVersion()` for this change Id — `Step 1` in the example — on this Workflow Execution will always return the given version number, which is `1` in the example.

If you make an additional change, such as adding `anotherPatchActivity()`, you need to
add some additional code:

```php
#[WorkflowInterface]
class MyWorkflow
{
    // ...

    #[WorkflowMethod]
    public function runAsync()
    {
        $version = yield Workflow::getVersion('Step 1', Workflow::DEFAULT_VERSION, maxSupported: 2);

        $result = match($version) {
            Workflow::DEFAULT_VERSION => yield $this->activity->prePatchActivity()
            1 => yield $this->activity->postPatchActivity();
            2 => yield $this->activity->anotherPatchActivity();
        };
    }
}
```

Note that we changed `maxSupported` from 1 to 2.
A Workflow that has already passed this `getVersion()` call before it was introduced returns `DEFAULT_VERSION`.
A Workflow that was run with `maxSupported` set to 1 returns 1.
New Workflows return 2.

After all the Workflow Executions prior to version 1 have left retention, you can remove the code for that version:

```php
    #[WorkflowMethod]
    public function runAsync()
    {
        $version = yield Workflow::getVersion('Step 1', minSupported: 1, maxSupported: 2);

        $result = match($version) {
            1 => yield $this->activity->postPatchActivity();
            2 => yield $this->activity->anotherPatchActivity();
        };
    }
```

You'll note that `minSupported` has changed from `DEFAULT_VERSION` to `1`.
If an older version of the Workflow Execution history is replayed on this code, it fails because the minimum expected version is 1.
After all the Workflow Executions for version 1 have left retention, you can remove version 1 so that your code looks like the following:

```php
    #[WorkflowMethod]
    public function runAsync()
    {
        $version = yield Workflow::getVersion('Step 1', minSupported: 2, maxSupported: 2);

        $result = yield $this->activity->anotherPatchActivity();
    }
```

Patching allows you to make changes to currently running Workflows.
It is a powerful method for introducing compatible changes without introducing non-determinism errors.

### Workflow cutovers

To understand why Patching is useful, it's helpful to demonstrate cutting over an entire Workflow.

Since incompatible changes only affect open Workflow Executions of the same type, you can avoid determinism errors by creating a whole new Workflow when making changes.
To do this, you can copy the Workflow Definition function, giving it a different name, and register both names with your Workers.

For example, you would duplicate `MyWorkflow` as `MyWorkflowV2`:

```php
#[WorkflowInterface]
class MyWorkflow
{}

#[WorkflowInterface]
class MyWorkflowV2
{}
```

You would then need to update the Worker configuration, and any other identifier strings, to register both Workflow Types.
The downside of this method is that it requires you to duplicate code and to update any commands used to start the Workflow.
This can become impractical over time.
This method also does not provide a way to version any still-running Workflows -- it is essentially just a cutover, unlike Patching.

## Runtime checking {#runtime-checking}

The Temporal PHP SDK performs a runtime check to help prevent obvious incompatible changes.
Adding, removing, or reordering any of these methods without Versioning triggers the runtime check and results in a nondeterminism error:

- `workflow.ExecuteActivity()`
- `workflow.ExecuteChildWorkflow()`
- `workflow.NewTimer()`
- `workflow.RequestCancelWorkflow()`
- `workflow.SideEffect()`
- `workflow.SignalExternalWorkflow()`
- `workflow.Sleep()`

The runtime check does not perform a thorough check.
For example, it does not check on the Activity's input arguments or the Timer duration.
Each Temporal SDK implements these sanity checks differently, and they are not a complete check for non-deterministic changes.
Instead, you should incorporate [Replay Testing](/develop/php/testing-suite#replay) when making revisions.

---

## Plugins Guide