Repository navigation
Conversation
Replaces the Serverless Workflow 0.8 object model (states, actions, functions) with typed classes for the DSL 1.0.1 document: document, input/output/export, reusable components, the named task list and every task type (call http/openapi/grpc/asyncapi/function, do, emit, for, fork, listen, raise, run, set, switch, try, wait), events, authentication policies, retries, timeouts and schedules. A small attribute-driven hydrator builds the model from arrays, resolves concrete task types from their properties and preserves unknown properties so definitions survive a parse/serialize round trip. Empty objects are serialized as objects, not empty lists. Validation now uses opis/json-schema (JSON Schema 2020-12) against the unmodified v1.0.1 schema bundled in schema/workflow.json, so it works offline. phpdocumentor/reflection-docblock and swaggest/json-schema are no longer needed. PHP 8.2 or later is required and CI pins PHP 8.3. Signed-off-by: Richard McDaniel <richard.lee.mcdaniel@gmail.com>
Copies the 63 workflow examples of specification v1.0.1 as fixtures. Each one is parsed, serialized to arrays, JSON and YAML without loss, validated against the bundled schema and checked to hydrate typed tasks. Unit tests cover task type resolution, named task lists, programmatic building, typed navigation, validation errors and the preservation of unknown properties. Signed-off-by: Richard McDaniel <richard.lee.mcdaniel@gmail.com>
Signed-off-by: Richard McDaniel <richard.lee.mcdaniel@gmail.com>
rmcdaniel
requested review from
cdavernas and
ricardozanini
and
a balanced review from Copilot
October 22, 2026 01:08
There was a problem hiding this comment.
🟡 Changes recommended
Hydration and serialization can alter workflow data and conceal schema violations, undermining lossless conversion and reliable validation.
7 open issues
Preserve strict scalar type errors during raw-array hydration · New Reject DSL entries with zero or multiple names · New Preserve numeric named-list keys as mappings · New Preserve empty-object identity for maps and payloads · New Preserve explicitly supplied null values during serialization · New Avoid rewriting list-like text inside YAML literal blocks · New Compare serialized JSON and YAML shapes independently · New
What changed in this PR
Migrates the PHP SDK from the legacy 0.8 DSL to Open Workflow Specification 1.0.1, replacing the state-based API with typed workflow and task models.
Changes:
- Adds attribute-driven hydration, serialization, reusable components and task configuration models.
- Replaces remote schema validation with bundled-schema validation and structured errors.
- Updates documentation, dependencies and CI, and adds specification fixtures and unit coverage.
| File | Description |
|---|---|
| tests/Unit/WorkflowTest.php | Tests building, navigation, serialization and validation. |
| tests/Unit/TaskTest.php | Tests task resolution and property ordering. |
| tests/Unit/TaskListTest.php | Tests named-task collection behavior. |
| tests/Unit/ExamplesTest.php | Checks specification examples across SDK operations. |
| tests/Fixtures/workflow.yaml | Replaces the legacy YAML fixture. |
| tests/Fixtures/workflow.json | Replaces the legacy JSON fixture. |
| tests/Fixtures/examples/wait-duration-iso8601.yaml | Adds ISO-duration waiting example. |
| tests/Fixtures/examples/wait-duration-inline.yaml | Adds inline-duration waiting example. |
| tests/Fixtures/examples/try-catch.yaml | Adds error-catching example. |
| tests/Fixtures/examples/try-catch-then.yaml | Adds catch-handler task example. |
| tests/Fixtures/examples/try-catch-retry-reusable.yaml | Adds reusable retry example. |
| tests/Fixtures/examples/try-catch-retry-inline.yaml | Adds inline retry example. |
| tests/Fixtures/examples/switch-then-string.yaml | Adds conditional routing example. |
| tests/Fixtures/examples/star-wars-homeworld.yaml | Adds chained HTTP calls example. |
| tests/Fixtures/examples/set.yaml | Adds mapped data assignment example. |
| tests/Fixtures/examples/set-expression.yaml | Adds expression-based assignment example. |
| tests/Fixtures/examples/schedule-event-driven.yaml | Adds event-triggered scheduling example. |
| tests/Fixtures/examples/schedule-cron.yaml | Adds cron scheduling example. |
| tests/Fixtures/examples/run-subflow.yaml | Adds workflow invocation example. |
| tests/Fixtures/examples/run-script-with-arguments.yaml | Adds script arguments example. |
| tests/Fixtures/examples/run-return-stderr.yaml | Adds standard-error output example. |
| tests/Fixtures/examples/run-return-none.yaml | Adds suppressed output example. |
| tests/Fixtures/examples/run-return-code.yaml | Adds exit-code output example. |
| tests/Fixtures/examples/run-return-all.yaml | Adds combined process output example. |
| tests/Fixtures/examples/run-container.yaml | Adds container execution example. |
| tests/Fixtures/examples/run-container-with-name.yaml | Adds container naming example. |
| tests/Fixtures/examples/run-container-cleanup-eventually.yaml | Adds delayed container cleanup example. |
| tests/Fixtures/examples/run-container-cleanup-always.yaml | Adds unconditional container cleanup example. |
| tests/Fixtures/examples/README.md | Documents example provenance. |
| tests/Fixtures/examples/raise-reusable.yaml | Adds reusable error example. |
| tests/Fixtures/examples/raise-inline.yaml | Adds inline error example. |
| tests/Fixtures/examples/mock-service-extension.yaml | Adds task extension example. |
| tests/Fixtures/examples/listen-to-one.yaml | Adds single-event listening example. |
| tests/Fixtures/examples/listen-to-any.yaml | Adds unrestricted listening example. |
| tests/Fixtures/examples/listen-to-any-until-consumed.yaml | Adds event-based stopping example. |
| tests/Fixtures/examples/listen-to-any-until-condition.yaml | Adds condition-based stopping example. |
| tests/Fixtures/examples/listen-to-any-forever-foreach.yaml | Adds continuous event iteration example. |
| tests/Fixtures/examples/listen-to-any-filter.yaml | Adds filtered event listening example. |
| tests/Fixtures/examples/listen-to-all.yaml | Adds combined event listening example. |
| tests/Fixtures/examples/listen-to-all read-envelope.yaml | Adds event-envelope reading example. |
| tests/Fixtures/examples/fork.yaml | Adds competing parallel tasks example. |
| tests/Fixtures/examples/for.yaml | Adds collection iteration example. |
| tests/Fixtures/examples/emit.yaml | Adds event publication example. |
| tests/Fixtures/examples/do-single.yaml | Adds single-task workflow example. |
| tests/Fixtures/examples/do-multiple.yaml | Adds sequential tasks example. |
| tests/Fixtures/examples/conditional-task.yaml | Adds conditional execution example. |
| tests/Fixtures/examples/call-openapi.yaml | Adds OpenAPI invocation example. |
| tests/Fixtures/examples/call-openapi-redirect.yaml | Adds OpenAPI redirect example. |
| tests/Fixtures/examples/call-http-redirect.yaml | Adds HTTP redirect example. |
| tests/Fixtures/examples/call-http-query-parameters.yaml | Adds HTTP query parameters example. |
| tests/Fixtures/examples/call-http-query-headers-expressions.yaml | Adds dynamic headers and query example. |
| tests/Fixtures/examples/call-http-endpoint-interpolation.yaml | Adds endpoint expression example. |
| tests/Fixtures/examples/call-http-endpoint-interpolation-shorthand.yaml | Adds shorthand endpoint example. |
| tests/Fixtures/examples/call-grpc.yaml | Adds gRPC invocation example. |
| tests/Fixtures/examples/call-custom-function-inline.yaml | Adds reusable function example. |
| tests/Fixtures/examples/call-custom-function-cataloged.yaml | Adds external function example. |
| tests/Fixtures/examples/call-asyncapi-subscribe-consume-while.yaml | Adds conditional message consumption example. |
| tests/Fixtures/examples/call-asyncapi-subscribe-consume-until.yaml | Adds bounded message consumption example. |
| tests/Fixtures/examples/call-asyncapi-subscribe-consume-forever-foreach.yaml | Adds continuous message iteration example. |
| tests/Fixtures/examples/call-asyncapi-subscribe-consume-amount.yaml | Adds message-count limit example. |
| tests/Fixtures/examples/call-asyncapi-publish.yaml | Adds AsyncAPI publication example. |
| tests/Fixtures/examples/authentication-reusable.yaml | Adds reusable authentication example. |
| tests/Fixtures/examples/authentication-oidc.yaml | Adds inline OpenID Connect example. |
| tests/Fixtures/examples/authentication-oidc-secret.yaml | Adds secret-based OpenID Connect example. |
| tests/Fixtures/examples/authentication-oauth2.yaml | Adds inline OAuth2 example. |
| tests/Fixtures/examples/authentication-oauth2-secret.yaml | Adds secret-based OAuth2 example. |
| tests/Fixtures/examples/authentication-bearer.yaml | Adds bearer authentication example. |
| tests/Fixtures/examples/authentication-bearer-uri-format.yaml | Adds bearer endpoint-template example. |
| tests/Fixtures/examples/accumulate-room-readings.yaml | Adds correlated event processing example. |
| src/WorkflowValidator.php | Uses bundled schema and structured validation errors. |
| src/WorkflowValidationException.php | Exposes validation messages by JSON pointer. |
| src/WorkflowExecTimeout.php | Removes legacy execution timeout model. |
| src/Workflow.php | Replaces workflow structure and conversion methods. |
| src/TransitionDataCondition.php | Removes legacy transition condition model. |
| src/Transition.php | Removes legacy transition model. |
| src/Timeout.php | Models duration-based timeouts. |
| src/TaskList.php | Adds typed named-task lists. |
| src/Task/WaitTask.php | Models duration-based waiting. |
| src/Task/TryTask.php | Models guarded task execution. |
| src/Task/TryCatch.php | Models catch handlers and retries. |
| src/Task/Task.php | Defines shared properties and task resolution. |
| src/Task/SwitchTask.php | Models ordered conditional routing. |
| src/Task/SwitchCase.php | Models switch conditions and directives. |
| src/Task/SetTask.php | Models data assignment. |
| src/Task/RunTask.php | Models external process execution. |
| src/Task/RunConfiguration.php | Defines execution targets and output options. |
| src/Task/Run/SubflowConfiguration.php | Models workflow invocation arguments. |
| src/Task/Run/Shell.php | Models shell command configuration. |
| src/Task/Run/Script.php | Models inline and external scripts. |
| src/Task/Run/ContainerLifetime.php | Models container cleanup policy. |
| src/Task/Run/Container.php | Models container execution configuration. |
| src/Task/RaiseTask.php | Models explicit error raising. |
| src/Task/RaiseConfiguration.php | Supports inline and reusable errors. |
| src/Task/ListenTask.php | Models event listening and iteration. |
| src/Task/ListenConfiguration.php | Defines event selection and reading mode. |
| src/Task/ForTask.php | Models iterative task execution. |
| src/Task/ForkTask.php | Models concurrent task execution. |
| src/Task/ForkConfiguration.php | Defines branches and competition mode. |
| src/Task/ForConfiguration.php | Defines collection iteration variables. |
| src/Task/EmitTask.php | Models event publication. |
| src/Task/EmitEvent.php | Models emitted event properties. |
| src/Task/EmitConfiguration.php | Defines event emission configuration. |
| src/Task/DoTask.php | Models nested sequential tasks. |
| src/Task/CatchErrors.php | Defines catch error filtering. |
| src/Task/CallTask.php | Defines shared call-task properties. |
| src/Task/CallOpenApi.php | Models OpenAPI calls. |
| src/Task/CallHttp.php | Models HTTP calls. |
| src/Task/CallGrpc.php | Models gRPC calls. |
| src/Task/CallFunction.php | Models custom function calls. |
| src/Task/CallAsyncApi.php | Models AsyncAPI calls. |
| src/Task/Call/OpenApiArguments.php | Defines OpenAPI call arguments. |
| src/Task/Call/HttpArguments.php | Defines HTTP request arguments. |
| src/Task/Call/GrpcService.php | Defines gRPC service connection properties. |
| src/Task/Call/GrpcArguments.php | Defines gRPC call arguments. |
| src/Task/Call/AsyncApiSubscription.php | Defines message subscription behavior. |
| src/Task/Call/AsyncApiServer.php | Defines AsyncAPI server selection. |
| src/Task/Call/AsyncApiMessageConsumptionPolicy.php | Defines message consumption limits. |
| src/Task/Call/AsyncApiMessage.php | Defines message payload and headers. |
| src/Task/Call/AsyncApiArguments.php | Defines AsyncAPI operation arguments. |
| src/StateExecTimeout.php | Removes legacy state timeout model. |
| src/State.php | Removes legacy state abstraction. |
| src/StartDef.php | Removes legacy workflow start model. |
| src/SleepStateTimeout.php | Removes legacy sleep timeout model. |
| src/SleepState.php | Removes legacy sleep state. |
| src/Sleep.php | Removes legacy action sleep model. |
| src/Serialization/Serializer.php | Adds shared recursive normalization. |
| src/Schema.php | Models inline and external schemas. |
| src/Schedule.php | Replaces legacy scheduling properties. |
| src/RetryPolicy.php | Models retry conditions and timing. |
| src/RetryLimitAttempt.php | Defines retry attempt limits. |
| src/RetryLimit.php | Defines retry duration limits. |
| src/RetryJitter.php | Defines retry timing variation. |
| src/RetryBackoff.php | Models backoff strategy selection. |
| src/ProduceEventDef.php | Removes legacy event production model. |
| src/ParallelStateTimeout.php | Removes legacy parallel timeout model. |
| src/ParallelState.php | Removes legacy parallel state. |
| src/Output.php | Models output schemas and transformations. |
| src/OperationStateTimeout.php | Removes legacy operation timeout model. |
| src/OperationState.php | Removes legacy operation state. |
| src/OnEvents.php | Removes legacy event-action grouping. |
| src/NamedList.php | Adds ordered named-item collections. |
| src/Input.php | Models input schemas and transformations. |
| src/InjectState.php | Removes legacy injection state. |
| src/Hydration/NamedListOf.php | Declares named-list element types. |
| src/Hydration/MapOf.php | Declares map value types. |
| src/Hydration/ListOf.php | Declares list element types. |
| src/Hydration/Hydrator.php | Hydrates typed properties and retains unknown data. |
| src/Hydration/Hydratable.php | Defines array hydration contract. |
| src/Hydration/ArrayConvertible.php | Defines array conversion contract. |
| src/ForeachStateTimeout.php | Removes legacy iteration timeout model. |
| src/ForeachState.php | Removes legacy iteration state. |
| src/FlowDirective.php | Defines reserved flow directives. |
| src/ExternalResource.php | Models named external resources. |
| src/Extension.php | Models before/after task extensions. |
| src/Export.php | Models workflow-context exports. |
| src/EventStateTimeout.php | Removes legacy event timeout model. |
| src/EventState.php | Removes legacy event state. |
| src/EventBasedSwitchStateTimeOut.php | Removes legacy event-switch timeout model. |
| src/EventBasedSwitchState.php | Removes legacy event-switch state. |
| src/Event/SubscriptionIterator.php | Models per-event or per-message iteration. |
| src/Event/EventProperties.php | Models event attributes and data. |
| src/Event/EventFilter.php | Models event filtering and correlation. |
| src/Event/EventCorrelation.php | Defines event correlation expressions. |
| src/Event/EventConsumptionStrategy.php | Models event selection and stopping rules. |
| src/ErrorFilter.php | Models static error filters. |
| src/Error.php | Replaces legacy error handling properties. |
| src/Endpoint.php | Models endpoints and authentication. |
| src/Duration.php | Models inline duration units. |
| src/Document.php | Models workflow identity and metadata. |
| src/Components.php | Models reusable workflow components. |
| src/Catalog.php | Models resource catalog endpoints. |
| src/CallbackState.php | Removes legacy callback state. |
| src/BaseObject.php | Centralizes hydration and additional properties. |
| src/BackoffDefinition.php | Models backoff configuration objects. |
| src/Authentication/OpenIdConnectAuthentication.php | Models OpenID Connect authentication. |
| src/Authentication/OAuth2TokenRequest.php | Models token request encoding. |
| src/Authentication/OAuth2Token.php | Models typed OAuth2 tokens. |
| src/Authentication/OAuth2Properties.php | Defines shared OAuth2/OpenID Connect properties. |
| src/Authentication/OAuth2Endpoints.php | Models OAuth2 endpoint paths. |
| src/Authentication/OAuth2Client.php | Models OAuth2 client credentials. |
| src/Authentication/OAuth2Authentication.php | Models OAuth2 authentication. |
| src/Authentication/DigestAuthentication.php | Models digest authentication. |
| src/Authentication/BearerAuthentication.php | Models bearer authentication. |
| src/Authentication/BasicAuthentication.php | Models basic authentication. |
| src/Authentication/AuthenticationPolicy.php | Models authentication schemes and references. |
| schema/README.md | Documents schema provenance and upgrades. |
| README.md | Documents the new API and migration guidance. |
| composer.json | Updates runtime dependencies and test autoloading. |
| .github/workflows/php.yml | Pins PHP 8.3 and updates CI actions. |
Review effort: Balanced
| } | ||
|
|
||
| try { | ||
| $property->setValue($target, self::cast($property, $value)); |
| ) { | ||
| foreach ($items as $key => $value) { | ||
| if (is_int($key)) { | ||
| if (! is_array($value)) { |
Comment on lines
+53
to
+55
| $items[] = [ | ||
| $name => self::normalize($item, $preserveEmptyObjects), | ||
| ]; |
Comment on lines
+65
to
+70
| if (is_array($value)) { | ||
| foreach ($value as $key => $item) { | ||
| $value[$key] = self::normalize($item, $preserveEmptyObjects); | ||
| } | ||
|
|
||
| return $value; |
Comment on lines
+85
to
+87
| $value = $reflectionProperty->getValue($this); | ||
| if ($value !== null) { | ||
| $properties[$reflectionProperty->getName()] = $value; |
| ); | ||
|
|
||
| // Start list items that hold a mapping on the dash line ("- name:"), as the specification examples do. | ||
| return preg_replace('/^( *)-\n\1 (?=\S)/m', '$1- ', $yaml) ?? $yaml; |
Comment on lines
+54
to
+55
| self::assertEquals($expected, Workflow::fromJson($workflow->toJson())->toArray()); | ||
| self::assertEquals($expected, Workflow::fromYaml($workflow->toYaml())->toArray()); |
|
@rmcdaniel, many thanks for this work! A couple of things:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Summary
Updates the PHP SDK from the legacy Serverless Workflow 0.8 DSL to the Open Workflow Specification DSL v1.0.1, the current release of the specification.
Workflow,Document,Input/Output/Export,Schema, reusableComponents(use), the orderedTaskList, every task type (CallHttp,CallOpenApi,CallGrpc,CallAsyncApi,CallFunction,DoTask,EmitTask,ForTask,ForkTask,ListenTask,RaiseTask,RunTask,SetTask,SwitchTask,TryTask,WaitTask) and their configuration objects, events and consumption strategies, authentication policies, retries, timeouts, durations and schedules.src/Hydration) builds objects from arrays, resolves concrete task types from their properties, handles string-or-object unions (timeout,endpoint,wait,duration, …) and preserves unknown properties so definitions survive a parse → serialize round trip. Empty objects such asbackoff: { exponential: {} }serialize as objects, not as empty lists.unevaluatedProperties) against the unmodified v1.0.1 schema bundled inschema/workflow.json, so it works offline.WorkflowValidator::errors()returns messages keyed by JSON pointer;validate()throwsWorkflowValidationException.phpdocumentor/reflection-docblockandswaggest/json-schemaare dropped;symfony/yaml ^6.4 || ^7.0andopis/json-schema ^2.4are required; PHP^8.2is declared. CI now pins PHP 8.3 viasetup-phpand uses the v4 actions.Breaking changes
This is a complete replacement of the object model; 0.8 definitions and the 0.8 classes are gone. It should ship as 2.0.0. The README points users of 0.8 definitions to the migration tool and to the
1.0.0release of this package.Notes for reviewers
v1.0.2andv1.0.3pre-releases (a2aandmcpcall tasks, containerpullPolicy,stdin/argvfor run tasks). The bundled schema is thev1.0.1release; definitions using those additions still parse and round-trip thanks to unknown-property preservation, but they are reported by validation. Moving to a later release means replacingschema/workflow.json, bumpingWorkflow::DSL_VERSIONand refreshing the example fixtures (seeschema/README.md).toYaml()emits list items as- name:like the specification examples rather than Symfony's default dash-on-its-own-line style.Test plan
Run on PHP 8.3 with the regenerated
composer.lock:composer validate --strictvendor/bin/ecs checkvendor/bin/phpstan analyse src tests(level 6)vendor/bin/phpunit— 219 tests, 1080 assertions