Commit e5aa140779a for woocommerce

commit e5aa140779adb0a10dfcc4dfa814fe1da9009b53
Author: Dave Lockie <dave.lockie@automattic.com>
Date:   Wed May 13 17:08:57 2026 +0200

    Handle array input types in API builder for the GraphQL API (#64735)

    Co-authored-by: Nestor Soriano <konamiman@konamiman.com>

diff --git a/plugins/woocommerce/changelog/pr-64735 b/plugins/woocommerce/changelog/pr-64735
new file mode 100644
index 00000000000..96441dc5736
--- /dev/null
+++ b/plugins/woocommerce/changelog/pr-64735
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix handling of array input types in the dual API
diff --git a/plugins/woocommerce/src/Internal/Api/DesignTime/Scripts/ApiBuilder.php b/plugins/woocommerce/src/Internal/Api/DesignTime/Scripts/ApiBuilder.php
index 0a3ac414825..b29dfd266e3 100644
--- a/plugins/woocommerce/src/Internal/Api/DesignTime/Scripts/ApiBuilder.php
+++ b/plugins/woocommerce/src/Internal/Api/DesignTime/Scripts/ApiBuilder.php
@@ -2613,21 +2613,7 @@ class ApiBuilder {
 		$class_info = $this->get_class_info( $type_name );
 		if ( $class_info !== null ) {
 			$short = ( new \ReflectionClass( $type_name ) )->getShortName();
-			if ( $class_info['kind'] === 'enum' ) {
-				$alias       = $short . 'Type';
-				$use_stmts[] = $this->autogenerated_namespace . "\\GraphQLTypes\\Enums\\{$short} as {$alias}";
-				$expr        = "{$alias}::get()";
-			} elseif ( $class_info['kind'] === 'type' ) {
-				$use_stmts[] = $this->autogenerated_namespace . "\\GraphQLTypes\\Output\\{$short}";
-				$expr        = "{$short}::get()";
-			} elseif ( $class_info['kind'] === 'input_type' ) {
-				$gen_name    = str_ends_with( $short, 'Input' ) ? substr( $short, 0, -5 ) : $short;
-				$alias       = $gen_name . 'Input';
-				$use_stmts[] = $this->autogenerated_namespace . "\\GraphQLTypes\\Input\\{$gen_name} as {$alias}";
-				$expr        = "{$alias}::get()";
-			} else {
-				$expr = 'Type::string()'; // Fallback.
-			}
+			$expr  = $this->class_info_to_graphql_expr( $class_info, $short, $use_stmts ) ?? 'Type::string()';
 			return $nullable ? $expr : "Type::nonNull({$expr})";
 		}

@@ -2656,18 +2642,33 @@ class ApiBuilder {

 		$short = ( new \ReflectionClass( $fqcn ) )->getShortName();

-		return match ( $class_info['kind'] ) {
-			'enum' => ( function () use ( $short, &$use_stmts ) {
+		return $this->class_info_to_graphql_expr( $class_info, $short, $use_stmts ) ?? 'Type::string()';
+	}
+
+	/**
+	 * Resolve a registered class kind into the GraphQL expression that references its generated type,
+	 * pushing the matching use-statement into $use_stmts as a side effect. Returns null for kinds this
+	 * builder does not know how to emit; callers decide what fallback to use.
+	 */
+	private function class_info_to_graphql_expr( array $class_info, string $short, array &$use_stmts ): ?string {
+		switch ( $class_info['kind'] ) {
+			case 'enum':
 				$alias       = $short . 'Type';
 				$use_stmts[] = $this->autogenerated_namespace . "\\GraphQLTypes\\Enums\\{$short} as {$alias}";
 				return "{$alias}::get()";
-			} )(),
-			'type' => ( function () use ( $short, &$use_stmts ) {
+
+			case 'type':
 				$use_stmts[] = $this->autogenerated_namespace . "\\GraphQLTypes\\Output\\{$short}";
 				return "{$short}::get()";
-			} )(),
-			default => 'Type::string()',
-		};
+
+			case 'input_type':
+				$gen_name    = str_ends_with( $short, 'Input' ) ? substr( $short, 0, -5 ) : $short;
+				$alias       = $gen_name . 'Input';
+				$use_stmts[] = $this->autogenerated_namespace . "\\GraphQLTypes\\Input\\{$gen_name} as {$alias}";
+				return "{$alias}::get()";
+		}
+
+		return null;
 	}

 	private function param_type_to_graphql_expr( Parameter $param, array &$use_stmts ): string {
diff --git a/plugins/woocommerce/tests/php/src/Internal/Api/Autogenerated/AttributesTest.php b/plugins/woocommerce/tests/php/src/Internal/Api/Autogenerated/AttributesTest.php
index 138c23cba8d..eac43c9711c 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Api/Autogenerated/AttributesTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Api/Autogenerated/AttributesTest.php
@@ -131,6 +131,26 @@ class AttributesTest extends AutogeneratedTestCase {
 		$this->assertSame( 'WidgetReview', $inner->name );
 	}

+	/**
+	 * @testdox #[ArrayOf] with an input type produces a list of that InputObjectType.
+	 */
+	public function test_array_of_input_type_produces_list_of_input_object_type(): void {
+		$field = RootMutationType::get()->getField( 'createWidget' );
+		$arg   = null;
+		foreach ( $field->args as $candidate ) {
+			if ( 'related_inputs' === $candidate->name ) {
+				$arg = $candidate;
+				break;
+			}
+		}
+
+		$this->assertNotNull( $arg );
+		$type = $this->unwrap_non_null( $arg->getType() );
+		$this->assertInstanceOf( ListOfType::class, $type );
+		$inner = $this->unwrap_non_null( $type->getWrappedType() );
+		$this->assertSame( CreateWidgetInputType::get(), $inner );
+	}
+
 	/**
 	 * @testdox #[ConnectionOf] produces a NodeNameConnection field.
 	 */
diff --git a/plugins/woocommerce/tests/php/src/Internal/Api/Fixtures/DummyApi/Mutations/CreateWidget.php b/plugins/woocommerce/tests/php/src/Internal/Api/Fixtures/DummyApi/Mutations/CreateWidget.php
index e51e2095e52..60d86666204 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Api/Fixtures/DummyApi/Mutations/CreateWidget.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Api/Fixtures/DummyApi/Mutations/CreateWidget.php
@@ -4,6 +4,7 @@ declare(strict_types=1);

 namespace Automattic\WooCommerce\Tests\Internal\Api\Fixtures\DummyApi\Mutations;

+use Automattic\WooCommerce\Api\Attributes\ArrayOf;
 use Automattic\WooCommerce\Api\Attributes\Description;
 use Automattic\WooCommerce\Api\Attributes\RequiredCapability;
 use Automattic\WooCommerce\Tests\Internal\Api\Fixtures\DummyApi\InputTypes\CreateWidgetInput;
@@ -22,6 +23,9 @@ class CreateWidget {
 	public function execute(
 		#[Description( 'The data for the new widget' )]
 		CreateWidgetInput $input,
+		#[Description( 'Related widget inputs for array input generation coverage' )]
+		#[ArrayOf( CreateWidgetInput::class )]
+		?array $related_inputs = null,
 	): Widget {
 		$widget = Store::create_widget( $input->label, $input->color );
 		if ( null !== $input->weight ) {
diff --git a/plugins/woocommerce/tests/php/src/Internal/Api/Fixtures/DummyApiAutogenerated/GraphQLMutations/CreateWidget.php b/plugins/woocommerce/tests/php/src/Internal/Api/Fixtures/DummyApiAutogenerated/GraphQLMutations/CreateWidget.php
index 216b70fc2cc..48625783c9e 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Api/Fixtures/DummyApiAutogenerated/GraphQLMutations/CreateWidget.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Api/Fixtures/DummyApiAutogenerated/GraphQLMutations/CreateWidget.php
@@ -23,6 +23,11 @@ class CreateWidget {
 					'type' => Type::nonNull(CreateWidgetInput::get()),
 						'description' => __( 'The data for the new widget', 'woocommerce' ),
 						),
+				'related_inputs' => array(
+					'type' => Type::listOf(Type::nonNull(CreateWidgetInput::get())),
+						'description' => __( 'Related widget inputs for array input generation coverage', 'woocommerce' ),
+						'defaultValue' => NULL,
+					),
 			),
 			'resolve' => array( self::class, 'resolve' ),
 		);
@@ -41,6 +46,9 @@ class CreateWidget {
 		if ( array_key_exists( 'input', $args ) ) {
 			$execute_args['input'] = self::convert_create_widget_input( $args['input'] );
 		}
+		if ( array_key_exists( 'related_inputs', $args ) ) {
+			$execute_args['related_inputs'] = $args['related_inputs'];
+		}

 		$result = Utils::execute_command( $command, $execute_args );

diff --git a/plugins/woocommerce/tests/php/src/Internal/Api/Fixtures/DummyApiAutogenerated/api_generation_date.txt b/plugins/woocommerce/tests/php/src/Internal/Api/Fixtures/DummyApiAutogenerated/api_generation_date.txt
index 2ccbfb4edf3..17edde5142f 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Api/Fixtures/DummyApiAutogenerated/api_generation_date.txt
+++ b/plugins/woocommerce/tests/php/src/Internal/Api/Fixtures/DummyApiAutogenerated/api_generation_date.txt
@@ -1 +1 @@
-2026-05-12T08:46:19+00:00
\ No newline at end of file
+2026-05-13T13:46:52+00:00
\ No newline at end of file