The PHP SDK uses data converters to serialize/deserialize Workflow inputs, outputs, and Activity parameters. JSON is the default format.
The default converter handles:
null- Scalars (
string,int,float,bool) - Arrays (JSON-serialized)
- Objects (JSON-serialized via public properties)
PHP-specific: Workflow methods are generators. To specify the return type of a Workflow method, use the #[ReturnType] attribute on the interface method:
use Temporal\Workflow\ReturnType;
#[WorkflowInterface]
interface OrderWorkflowInterface
{
#[WorkflowMethod]
#[ReturnType(OrderResult::class)]
public function run(OrderInput $input): \Generator;
}Without #[ReturnType], the SDK cannot deserialize the result into the correct class.
Implement a custom PayloadConverter to handle types the default converter does not support:
use Temporal\DataConverter\PayloadConverter;
use Temporal\Api\Common\V1\Payload;
class MyCustomConverter implements PayloadConverter
{
public function getEncodingType(): string
{
return 'json/my-custom';
}
public function toPayload($value): ?Payload
{
if (!$value instanceof MyCustomType) {
return null; // Return null to let other converters handle it
}
$payload = new Payload();
$payload->setMetadata(['encoding' => $this->getEncodingType()]);
$payload->setData(json_encode($value->toArray()));
return $payload;
}
public function fromPayload(Payload $payload, \ReflectionType $type)
{
return MyCustomType::fromArray(json_decode($payload->getData(), true));
}
}Register the custom converter when creating the WorkflowClient:
use Temporal\DataConverter\DataConverter;
use Temporal\DataConverter\JsonPayloadConverter;
use Temporal\DataConverter\NullPayloadConverter;
$dataConverter = new DataConverter(
new NullPayloadConverter(),
new JsonPayloadConverter(),
new MyCustomConverter(),
);
$client = WorkflowClient::create(
ServiceClient::create('localhost:7233'),
dataConverter: $dataConverter
);Encrypt sensitive Workflow data using a custom PayloadCodec:
use Temporal\DataConverter\PayloadCodecInterface;
use Temporal\Api\Common\V1\Payload;
class EncryptionCodec implements PayloadCodecInterface
{
public function __construct(private string $key) {}
public function encode(array $payloads): array
{
return array_map(function (Payload $payload) {
$encrypted = $this->encrypt($payload->serializeToString());
$result = new Payload();
$result->setMetadata(['encoding' => 'binary/encrypted']);
$result->setData($encrypted);
return $result;
}, $payloads);
}
public function decode(array $payloads): array
{
return array_map(function (Payload $payload) {
if (($payload->getMetadata()['encoding'] ?? null) !== 'binary/encrypted') {
return $payload;
}
$decrypted = $this->decrypt($payload->getData());
$result = new Payload();
$result->mergeFromString($decrypted);
return $result;
}, $payloads);
}
private function encrypt(string $data): string { /* ... */ }
private function decrypt(string $data): string { /* ... */ }
}Apply the codec via DataConverter on the client:
$dataConverter = DataConverter::createDefault()->withCodec(new EncryptionCodec($encryptionKey));
$client = WorkflowClient::create(
ServiceClient::create('localhost:7233'),
dataConverter: $dataConverter
);Custom searchable fields for Workflow visibility.
Define Search Attribute keys and set them at Workflow start:
use Temporal\Common\SearchAttributes\SearchAttributeKey;
use Temporal\Common\TypedSearchAttributes;
$orderIdKey = SearchAttributeKey::forKeyword('OrderId');
$orderStatusKey = SearchAttributeKey::forKeyword('OrderStatus');
$workflow = $client->newWorkflowStub(
OrderWorkflowInterface::class,
WorkflowOptions::new()
->withTaskQueue('orders')
->withTypedSearchAttributes(
TypedSearchAttributes::new()
->withSearchAttribute($orderIdKey, $order->id)
->withSearchAttribute($orderStatusKey, 'pending')
)
);Upsert Search Attributes during Workflow execution:
use Temporal\Workflow;
use Temporal\Common\SearchAttributes\SearchAttributeKey;
class OrderWorkflow implements OrderWorkflowInterface
{
public function run(array $order): \Generator
{
// ... process order ...
Workflow::upsertTypedSearchAttributes(
SearchAttributeKey::forKeyword('OrderStatus')->valueSet('completed')
);
return 'done';
}
}$executions = $client->listWorkflowExecutions(
'OrderStatus = "processing" OR OrderStatus = "pending"'
);
foreach ($executions as $execution) {
echo "Workflow {$execution->getExecution()->getWorkflowId()} is still processing\n";
}Store arbitrary metadata with Workflows (not searchable).
// Set memo at Workflow start
$workflow = $client->newWorkflowStub(
OrderWorkflowInterface::class,
WorkflowOptions::new()
->withTaskQueue('orders')
->withMemo([
'customer_name' => $order->customerName,
'notes' => 'Priority customer',
])
);Upsert memo during Workflow execution:
class OrderWorkflow implements OrderWorkflowInterface
{
public function run(array $order): \Generator
{
// ... process order ...
Workflow::upsertMemo(['status' => 'fraud-checked']);
return yield $this->activity->processPayment($order);
}
}- Use
#[ReturnType]on Workflow interface methods to enable correct deserialization - Keep payloads small — see
references/core/gotchas.mdfor limits - Encrypt sensitive data with a
PayloadCodec - Use typed Search Attributes for business-level visibility and querying