Перейти до основного вмісту
Версія: 2.0

PHP-обробник invocable activity

Invocable HTTP carrier дає змогу Server надсилати leased activity task через POST до HTTPS endpoint замість очікування long-poll worker. PHP-помічник Workflow\V2\Support\InvocableActivityHandler є еталонною реалізацією, за допомогою якої зовнішній PHP-процес перетворює запит на оболонку результату, яку Server може узгодити.

Використовуйте його для інтеграції обробника activity з:

  • AWS Lambda або Google Cloud Function, яку викликають через HTTPS
  • Laravel controller за невеликим HTTP-сервісом
  • контейнером, який надає один POST endpoint на чергу activities

Помічник розбирає незалежну від carrier оболонку вхідних даних external task, знаходить зареєстрований callable, контролює deadline lease і формує незалежну від carrier оболонку результату external task, включно з формами помилок, яких очікує Server.

Швидкий початок​

Установіть пакет Durable Workflow у зовнішньому процесі, зареєструйте один callable для кожної назви обробника activity та передайте тіло запиту до handle():

use Workflow\V2\Support\InvocableActivityHandler;

$handler = new InvocableActivityHandler([
'billing.charge-card' => static function (int $amount, string $currency): array {
// Real charge-card logic. Must be idempotent per task id.
return [
'approved' => true,
'amount' => $amount,
'currency' => $currency,
];
},
]);

$envelope = json_decode(file_get_contents('php://input'), associative: true);
$result = $handler->handle($envelope);

header('Content-Type: application/vnd.durable-workflow.external-task-result+json');
echo json_encode($result, JSON_UNESCAPED_SLASHES);

Обробник отримує вхідну оболонку й повертає оболонку результату. Invocable HTTP carrier відповідає за контракт carrier: HTTPS, POST, автентифікацію, timeouts та retry budget. PHP-помічник відповідає за декодування аргументів, dispatch обробника, контроль deadline, кодування результату й класифікацію помилок.

Реєстрація обробників​

Перший аргумент конструктора є map із ключами, що відповідають task.handler у вхідній оболонці від Server. Це значення поля handler відповідного запису конфігурації зовнішнього виконавця:

new InvocableActivityHandler(
handlers: [
'billing.charge-card' => [$billingService, 'chargeCard'],
'billing.refund' => [$billingService, 'refund'],
'ops.rotate-key' => static fn (string $keyId): array => $keys->rotate($keyId),
],
carrier: 'billing-lambda',
resultCodec: 'avro',
);

Вхідні дані з незареєстрованим task.handler дають результат failed із failure.kind = application, classification = application_error та type = UnknownActivityHandler. Налаштована retry policy activity продовжує діяти на Server.

Необов'язкове ім'я carrier повертається в metadata.carrier кожної оболонки результату, щоб оператор міг визначити зовнішній runtime відповіді. Використовуйте сталий ідентифікатор без секретів, наприклад billing-lambda, ops-cloud-run або laravel-admin-api.

Необов'язковий resultCodec визначає серіалізацію поверненого значення в result.payload.blob. Стандартно це avro, що відповідає codec durable payload PHP workers. Codec результату має бути відомим CodecRegistry Server. protobuf не приймається й негайно відхиляється в конструкторі.

Оболонка результату​

За успіху handle() повертає незалежну від carrier оболонку успіху:

{
"schema": "durable-workflow.v2.external-task-result",
"version": 1,
"outcome": {
"status": "succeeded",
"recorded": true
},
"task": {
"id": "acttask_01HV7D3G3G61TAH2YB5RK45XJS",
"kind": "activity_task",
"attempt": 1,
"idempotency_key": "attempt_01HV7D3KJ1C8WQNNY8MVM8J40X"
},
"result": {
"payload": {
"codec": "avro",
"blob": "<base64-encoded payload>"
},
"metadata": {
"content_type": "application/vnd.durable-workflow.result+json"
}
},
"metadata": {
"handler": "billing.charge-card",
"carrier": "billing-lambda",
"duration_ms": 42
}
}

Поля task.id, task.attempt і task.idempotency_key копіюються з вхідної оболонки, щоб Server міг узгодити результат із початковим lease.

За помилки handle() повертає оболонку помилки. Блок failure містить вид, classification, повідомлення, початковий PHP-тип, stack trace і можливість retry. Помилка deadline також містить назву deadline та значення expires_at:

{
"schema": "durable-workflow.v2.external-task-result",
"version": 1,
"outcome": {
"status": "failed",
"retryable": true,
"recorded": true
},
"task": {"id": "...", "kind": "activity_task", "attempt": 1, "idempotency_key": "..."},
"failure": {
"kind": "timeout",
"classification": "deadline_exceeded",
"message": "Invocable activity task received after lease.expires_at.",
"type": "ExternalTaskDeadlineExceeded",
"stack_trace": null,
"timeout_type": "deadline_exceeded",
"cancelled": false,
"details": {
"deadline": "lease.expires_at",
"expires_at": "2026-04-22T15:14:02.000000Z"
}
},
"metadata": {"handler": "billing.charge-card", "carrier": "billing-lambda", "duration_ms": 3}
}

Класифікація помилок​

failure.kindclassificationRetry можливийКоли виникає
timeoutdeadline_exceededтакDeadline lease чи вхідних даних уже сплив на момент отримання оболонки або сплив під час виконання до повернення обробника.
decode_failuredecode_failureніАргументи не вдалося декодувати оголошеним codec, рядок deadline не вдалося розібрати, обробник підняв TypeError / ValueError через параметри або payload успіху не вдалося закодувати налаштованим codec результату.
applicationapplication_errorзалежить від виняткуОбробник підняв виняток. retryable дорівнює false, якщо виняток реалізує Workflow\Exceptions\NonRetryableExceptionContract, інакше true. Повідомлення береться з винятку, а type є його класом.
applicationapplication_errorніtask.kind не дорівнює activity_task або task.handler не зареєстрований у map.

Помічник ніколи не повертає голий виняток. Кожен шлях виконання формує структуровану оболонку результату, щоб invocable carrier міг детерміновано узгодити відповідь.

Deadlines та ідемпотентність​

Вхідна оболонка містить активний lease.expires_at та оголошені deadlines activity: schedule_to_start, start_to_close, schedule_to_close і heartbeat. Помічник перевіряє всі:

  • Перед dispatch обробника будь-який спливлий deadline одразу дає помилку timeout. details.deadline указує відповідне поле. Зареєстрований callable не викликається.
  • Після повернення обробника помічник повторно перевіряє deadlines. Обробник, що працював довше за свій lease, дає ту саму помилку timeout, а повернене значення не потрапляє в оболонку.

Оскільки carrier повторює транспортну доставку, а runtime повторно доставляє leases, про які не отримав результату, ті самі task.id і task.idempotency_key можуть надійти кілька разів. Код обробника має бути ідемпотентним. Ключ ідемпотентності кожної оболонки залишається сталим для retries тієї самої спроби.

Codecs payload та зовнішнє сховище​

Payload аргументів надходять у payloads.arguments із полем codec. Помічник декодує їх через CodecRegistry Server. Єдиний публічний codec v2 — avro. json, невідомі codecs, некоректні blobs та blobs без позначення відхиляються. JSON залишається документом HTTP carrier, а не codec durable payload.

Коли вхідні дані workflow перевищують налаштований поріг зовнішнього сховища payload, Server зберігає bytes у налаштованому driver і надсилає оболонку посилання замість inline blob. Передайте ExternalPayloadStorageDriver до конструктора, щоб помічник міг отримати дані за посиланнями перед викликом обробника:

use Workflow\V2\Contracts\ExternalPayloadStorageDriver;
use Workflow\V2\Support\InvocableActivityHandler;

$handler = new InvocableActivityHandler(
handlers: $handlers,
carrier: 'billing-lambda',
resultCodec: 'avro',
externalStorage: $driver,
);

Driver має виконувати той самий контракт, який Server використовує для запису payload, щоб посилання, hash і codec збігалися. Посилання, яке зовнішній процес не може отримати, дає decode_failure замість прихованого порожнього payload.

Production-інтеграція​

Laravel controller, який розміщує обробник за одним POST-маршрутом, має такий вигляд:

use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Workflow\V2\Support\InvocableActivityHandler;

final class BillingActivityController
{
public function __construct(private readonly InvocableActivityHandler $handler) {}

public function __invoke(Request $request): JsonResponse
{
$result = $this->handler->handle($request->all());

return new JsonResponse(
data: $result,
headers: ['Content-Type' => 'application/vnd.durable-workflow.external-task-result+json'],
);
}
}

У конфігурації зовнішнього виконавця вкажіть HTTPS URL цього controller і оголосіть auth_ref для автентифікації маршруту. Виняток loopback HTTP діє лише для локальної розробки.

  • Invocable HTTP carrier — серверний контракт carrier для доставки вхідних оболонок та узгодження оболонок результату.
  • Зовнішнє виконання — незалежна від carrier продуктова межа, включно зі schemas вхідних даних і результату, які реалізує помічник.
  • Зовнішнє сховище payload — перенесення завеликих аргументів чи результатів у налаштований driver та подання їх перевірюваними посиланнями.
  • Worker-протокол — ширший контракт worker plane, що публікує invocable carrier поруч із poll-based формами обробників.