Temporal is the simple, scalable open source way to write and run reliable cloud applications.
Make sure that your server is configured with following PHP version and extensions:
- PHP 8.1+
- Spiral framework 3.0+
You can install the package via composer:
composer require spiral/temporal-bridge
After package install you need to register bootloader from the package.
protected const LOAD = [
// ...
\Spiral\TemporalBridge\Bootloader\TemporalBridgeBootloader::class,
];
Note: if you are using
spiral-packages/discoverer
, you don't need to register bootloader by yourself.
The package is already configured by default, use these features only if you need to change the default configuration.
The package provides the ability to configure address
, namespace
, defaultWorker
, workers
parameters.
Create file app/config/temporal.php
and configure options. For example:
declare(strict_types=1);
use Temporal\Worker\WorkerFactoryInterface;
use Temporal\Worker\WorkerOptions;
return [
'address' => env('TEMPORAL_ADDRESS', 'localhost:7233'),
'namespace' => 'App\\Workflow',
'defaultWorker' => WorkerFactoryInterface::DEFAULT_TASK_QUEUE,
'workers' => [
'workerName' => WorkerOptions::new()
],
];
Add temporal
plugin section in your RoadRunner rr.yaml
config:
temporal:
address: localhost:7233
activities:
num_workers: 10
You can run temporal server via docker by using the example below:
You can find official docker compose files here https://github.com/temporalio/docker-compose
version: '3.5'
services:
postgresql:
container_name: temporal-postgresql
image: postgres:13
environment:
POSTGRES_PASSWORD: temporal
POSTGRES_USER: temporal
ports:
- 5432:5432
temporal:
container_name: temporal
image: temporalio/auto-setup:1.14.2
depends_on:
- postgresql
environment:
- DB=postgresql
- DB_PORT=5432
- POSTGRES_USER=temporal
- POSTGRES_PWD=temporal
- POSTGRES_SEEDS=postgresql
- DYNAMIC_CONFIG_FILE_PATH=config/dynamicconfig/development.yaml
ports:
- 7233:7233
volumes:
- ./temporal:/etc/temporal/config/dynamicconfig
temporal-web:
container_name: temporal-web
image: temporalio/web:1.13.0
depends_on:
- temporal
environment:
- TEMPORAL_GRPC_ENDPOINT=temporal:7233
- TEMPORAL_PERMIT_WRITE_API=true
ports:
- 8088:8088
Please make sure that a configuration file for temporal server exists.
mkdir temporal && touch temporal/development.yaml
You are able to create a new workflow via console command:
php app.php temporal:make-workflow MySuperWorkflow
The command will generate the following files with default namespace App\Workflow
:
project/
src/
Workflow/
MySuperWorkflow/
MySuperWorkflowInterface
MySuperWorkflow
You can redefine default namespace via
app/config/temporal.php
config file.
php app.php temporal:make-workflow MySuperWorkflow --with-activity
project/
src/
Workflow/
MySuperWorkflow/
...
MySuperWorkflowHandlerInterface
MySuperWorkflowHandler
php app.php temporal:make-workflow MySuperWorkflow --with-handler
project/
src/
Workflow/
MySuperWorkflow/
...
MySuperWorkflowActivityInterface
MySuperWorkflowActivity
You can mixin options
--with-activity --with-handler
temporal:make-workflow PingSite -m ping
#[WorkflowInterface]
interface PingSiteWorkflowInterface
{
#[WorkflowMethod]
public function ping(string $name): \Generator;
}
temporal:make-workflow PingSite ... -p url:string -p name:string
#[WorkflowInterface]
interface PingSiteWorkflowInterface
{
#[WorkflowMethod]
public function ping(string $url, string $name): \Generator;
}
temporal:make-workflow PingSite ... -r getStatusCode -r getHeaders:array
#[WorkflowInterface]
interface PingSiteWorkflowInterface
{
#[WorkflowMethod]
public function ping(...): \Generator;
#[QueryMethod]
function getStatusCode(): string;
#[QueryMethod]
function getHeaders(): array;
}
temporal:make-workflow Domain\\MyPackage\\MoneyTransfer ... -s withdraw -s deposit
The package provides the ability to create predefined Workflows. Presets for the package can be provided via third-party packages.
Example of usage
php app.php temporal:make-preset subscribtion-trial CustomerTrialSubscription
A preset will create all necessary classes.
You can show list of available presets using the console command
php app.php temporal:presets
A preset class should implement Spiral\TemporalBridge\Preset\PresetInterface
and should have an
attribute Spiral\TemporalBridge\Preset\WorkflowPreset
use Spiral\TemporalBridge\Generator\WorkflowInterfaceGenerator;
use Spiral\TemporalBridge\Generator\SignalWorkflowGenerator;
use Spiral\TemporalBridge\Generator\ActivityInterfaceGenerator;
use Spiral\TemporalBridge\Generator\ActivityGenerator;
use Spiral\TemporalBridge\Generator\HandlerInterfaceGenerator;
use Spiral\TemporalBridge\Generator\HandlerGenerator;
use Spiral\TemporalBridge\Preset\PresetInterface;
use Spiral\TemporalBridge\Preset\WorkflowPreset;
#[WorkflowPreset('signal')]
final class SignalWorkflow implements PresetInterface
{
public function getDescription(): ?string
{
return 'Workflow with signals';
}
public function generators(Context $context): array
{
$generators = [
'WorkflowInterface' => new WorkflowInterfaceGenerator(),
'Workflow' => new SignalWorkflowGenerator(),
];
if ($context->hasActivity()) {
$generators = \array_merge($generators, [
'ActivityInterface' => new ActivityInterfaceGenerator(),
'Activity' => new ActivityGenerator(),
]);
}
if ($context->hasHandler()) {
$generators = \array_merge($generators, [
'HandlerInterface' => new HandlerInterfaceGenerator(),
'Handler' => new HandlerGenerator(),
]);
}
return $generators;
}
}
Please note: If you are using WorkflowPreset
you have to add a directory with presets to tokenizer.
use Spiral\Tokenizer\Bootloader\TokenizerBootloader;
class MyBootloader extends \Spiral\Boot\Bootloader\Bootloader
{
protected const DEPENDENCIES = [
TokenizerBootloader::class
];
public function start(TokenizerBootloader $tokenizer)
{
$tokenizer->addDirectory(__DIR__..'/presets');
}
}
You can omit WorkflowPreset
attribute and register your preset via Bootloader
use Spiral\TemporalBridge\Preset\PresetRegistryInterface;
class MyBootloader extends \Spiral\Boot\Bootloader\Bootloader
{
public function start(PresetRegistryInterface $registry)
{
$registry->register('signal', new SignalWorkflow());
}
}
temporal:make-workflow MoneyTransfer ... -s withdraw -s deposit
#[WorkflowInterface]
interface MoneyTransferWorkflowInterface
{
#[WorkflowMethod]
public function ping(...): \Generator;
#[SignalMethod]
function withdraw(): void;
#[SignalMethod]
function deposit(): void;
}
You may discover available workflow samples here
Configure temporal address via env variables .env
TEMPORAL_ADDRESS=127.0.0.1:7233
class PingController
{
public function ping(StoreRequest $request, PingSiteHandler $handler): void
{
$this->handler->handle(
$request->url,
$request->name
);
}
}
Add a Spiral\TemporalBridge\Attribute\AssignWorker
attribute to your Workflow or Activity with the name
of the worker.
This Workflow or Activity will be processed by the specified worker.
Workflow example:
<?php
declare(strict_types=1);
namespace App\Workflow;
use Spiral\TemporalBridge\Attribute\AssignWorker;
use Temporal\Workflow\WorkflowInterface;
#[AssignWorker(name: 'worker1')]
#[WorkflowInterface]
interface MoneyTransferWorkflowInterface
{
#[WorkflowMethod]
public function transfer(...): \Generator;
#[SignalMethod]
function withdraw(): void;
#[SignalMethod]
function deposit(): void;
}
Activity example:
<?php
declare(strict_types=1);
namespace App\Workflow;
use Spiral\TemporalBridge\Attribute\AssignWorker;
use Temporal\Activity\ActivityInterface;
use Temporal\Activity\ActivityMethod;
#[AssignWorker(name: 'worker1')]
#[ActivityInterface(...)]
interface MoneyTransferActivityInterface
{
#[ActivityMethod]
public function transfer(...): int;
#[ActivityMethod]
public function cancel(...): bool;
}
You can configure worker options via config file app/config/temporal.php
declare(strict_types=1);
use Temporal\Worker\WorkerFactoryInterface;
use Temporal\Worker\WorkerOptions;
return [
//...
'workers' => [
'worker1' => WorkerOptions::new()
],
];
Or via application bootloader
<?php
declare(strict_types=1);
namespace App\Bootloader;
use Spiral\Bootloader\DomainBootloader;
use Spiral\TemporalBridge\WorkersRegistryInterface;use Temporal\Worker\WorkerOptions;
class AppBootloader extends DomainBootloader
{
public function init(WorkersRegistryInterface $workersRegistry): void
{
$workersRegistry->register(
'worker1',
WorkerOptions::new()->...
);
}
}
Temporal SDK hsa an ability to define custom data converters
By default it uses the following list of data converters:
Temporal\DataConverter\NullConverter
Temporal\DataConverter\BinaryConverter
Temporal\DataConverter\ProtoJsonConverter
Temporal\DataConverter\JsonConverter
If you want to specify custom list of data converters you need to bind your own implementation for
Temporal\DataConverter\DataConverterInterface
via container.
use Spiral\Boot\Bootloader\Bootloader;
use Temporal\DataConverter\DataConverter;
use Temporal\DataConverter\DataConverterInterface;
class AppBootloader extends Bootloader
{
protected const SINGLETONS = [
DataConverterInterface::class => [self::class, 'initDataConverter'],
];
protected function initDataConverter(): DataConverterInterface
{
return new DataConverter(
new \Temporal\DataConverter\NullConverter(),
new \Temporal\DataConverter\BinaryConverter(),
new \App\DataConverter\JmsSerializerConverter(),
new \Temporal\DataConverter\ProtoJsonConverter(),
new \Temporal\DataConverter\JsonConverter(),
);
}
}
composer test
Please see CHANGELOG for more information on what has changed recently.
Please see CONTRIBUTING for details.
Please review our security policy on how to report security vulnerabilities.
The MIT License (MIT). Please see License File for more information.