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