From 9a8d4e326d043faa4293e27ae87a95431fd50736 Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Thu, 18 Jun 2026 08:30:20 -0400 Subject: [PATCH] Reuse workflow handlers in nested foreach --- .../class-wp-agent-workflow-runner.php | 5 +- .../class-wp-agent-workflow-step-executor.php | 4 +- tests/workflow-runner-smoke.php | 49 +++++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/Workflows/class-wp-agent-workflow-runner.php b/src/Workflows/class-wp-agent-workflow-runner.php index 6fa13a0..3acaa9e 100644 --- a/src/Workflows/class-wp-agent-workflow-runner.php +++ b/src/Workflows/class-wp-agent-workflow-runner.php @@ -340,7 +340,10 @@ public static function default_foreach_handler( array $step, array $context ) { $as = '' !== $as_value ? $as_value : 'item'; $index_as = '' !== $index_as_value ? $index_as_value : 'index'; $continue_on_error = ! empty( $step['continue_on_error'] ); - $handlers = self::default_step_handlers(); + $handlers = is_array( $context['_workflow_step_handlers'] ?? null ) + ? $context['_workflow_step_handlers'] + : self::default_step_handlers(); + /** @var array $handlers */ $executor = new WP_Agent_Workflow_Step_Executor( $handlers ); $iterations = array(); diff --git a/src/Workflows/class-wp-agent-workflow-step-executor.php b/src/Workflows/class-wp-agent-workflow-step-executor.php index e9fa0b3..daa632d 100644 --- a/src/Workflows/class-wp-agent-workflow-step-executor.php +++ b/src/Workflows/class-wp-agent-workflow-step-executor.php @@ -52,7 +52,9 @@ public function execute( array $step, WP_Agent_Workflow_Run_Context $context ): $resolved = 'foreach' === $type ? self::expand_foreach_outer_step( $step, $context_array ) : WP_Agent_Workflow_Bindings::expand( $step, $context_array ); - $step_output = call_user_func( $handler, $resolved, $context_array ); + $handler_context = $context_array; + $handler_context['_workflow_step_handlers'] = $this->handlers; + $step_output = call_user_func( $handler, $resolved, $handler_context ); if ( is_wp_error( $step_output ) ) { $record['status'] = WP_Agent_Workflow_Run_Result::STATUS_FAILED; diff --git a/tests/workflow-runner-smoke.php b/tests/workflow-runner-smoke.php index a3ec7c9..ae4d5fb 100644 --- a/tests/workflow-runner-smoke.php +++ b/tests/workflow-runner-smoke.php @@ -486,5 +486,54 @@ public function recent( array $args = array() ): array { return array(); } smoke_assert( 10, $result8->get_output()['last']['iterations'][0]['last']['id'] ?? 0, 'foreach first iteration receives scoped item', $failures, $passes ); smoke_assert( 1, $result8->get_output()['last']['iterations'][1]['last']['points'] ?? 0, 'foreach second iteration receives scoped item', $failures, $passes ); +// ─── foreach reuses constructor-injected handlers for nested steps ──── + +add_filter( 'wp_agent_workflow_known_step_types', static fn( $types ) => array_merge( (array) $types, array( 'custom_nested' ) ) ); + +$custom_foreach_spec = WP_Agent_Workflow_Spec::from_array( + array( + 'id' => 'demo/foreach-custom-handler', + 'inputs' => array( + 'items' => array( 'type' => 'array', 'required' => true ), + ), + 'steps' => array( + array( + 'id' => 'custom_each', + 'type' => 'foreach', + 'items' => '${inputs.items}', + 'as' => 'item', + 'steps' => array( + array( + 'id' => 'custom', + 'type' => 'custom_nested', + 'prefix' => 'item', + 'value' => '${vars.item.id}', + ), + ), + ), + ), + ) +); + +$custom_result = ( new WP_Agent_Workflow_Runner( + null, + array( + 'custom_nested' => static function ( array $step, array $context ): array { + unset( $context ); + return array( 'label' => (string) ( $step['prefix'] ?? '' ) . '-' . (string) ( $step['value'] ?? '' ) ); + }, + ) +) )->run( + $custom_foreach_spec, + array( + 'items' => array( + array( 'id' => 42 ), + ), + ) +); + +smoke_assert( WP_Agent_Workflow_Run_Result::STATUS_SUCCEEDED, $custom_result->get_status(), 'foreach nested custom handler run succeeds', $failures, $passes ); +smoke_assert( 'item-42', $custom_result->get_output()['last']['iterations'][0]['last']['label'] ?? '', 'foreach nested step uses constructor-injected handler', $failures, $passes ); + echo "Passed: {$passes}, Failed: " . count( $failures ) . "\n"; exit( count( $failures ) > 0 ? 1 : 0 );