• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wp-graphql / wp-graphql / 13316763745

13 Feb 2025 08:45PM UTC coverage: 82.712% (-0.3%) from 83.023%
13316763745

push

github

web-flow
Merge pull request #3307 from wp-graphql/release/v2.0.0

release: v2.0.0

195 of 270 new or added lines in 20 files covered. (72.22%)

180 existing lines in 42 files now uncovered.

13836 of 16728 relevant lines covered (82.71%)

299.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.29
/src/Type/WPInterfaceTrait.php
1
<?php
2
namespace WPGraphQL\Type;
3

4
use GraphQL\Type\Definition\InterfaceType;
5
use WPGraphQL\Registry\TypeRegistry;
6

7
/**
8
 * Trait WPInterfaceTrait
9
 *
10
 * This Trait includes methods to help Interfaces and ObjectTypes ensure they implement
11
 * the proper inherited interfaces
12
 *
13
 * @package WPGraphQL\Type
14
 */
15
trait WPInterfaceTrait {
16

17
        /**
18
         * Given an array of interfaces, this gets the Interfaces the Type should implement including inherited interfaces.
19
         *
20
         * @return \GraphQL\Type\Definition\InterfaceType[]
21
         */
22
        protected function get_implemented_interfaces(): array {
619✔
23
                if ( ! isset( $this->config['interfaces'] ) || ! is_array( $this->config['interfaces'] ) || empty( $this->config['interfaces'] ) ) {
619✔
24
                        $interfaces = parent::getInterfaces();
619✔
25
                } else {
26
                        $interfaces = $this->config['interfaces'];
567✔
27
                }
28

29
                /**
30
                 * Filters the interfaces applied to an object type
31
                 *
32
                 * @param string[]                   $interfaces     List of interfaces applied to the Object Type
33
                 * @param array<string,mixed>        $config         The config for the Object Type
34
                 * @param mixed|\WPGraphQL\Type\WPInterfaceType|\WPGraphQL\Type\WPObjectType $type The Type instance
35
                 */
36
                $interfaces = apply_filters( 'graphql_type_interfaces', $interfaces, $this->config, $this );
619✔
37

38
                if ( empty( $interfaces ) || ! is_array( $interfaces ) ) {
619✔
39
                        return $interfaces;
619✔
40
                }
41

42
                $new_interfaces = [];
567✔
43

44
                foreach ( $interfaces as $interface ) {
567✔
45
                        if ( $interface instanceof InterfaceType && ( $this->config['name'] ?? null ) !== $interface->name ) {
567✔
46
                                $new_interfaces[ $interface->name ] = $interface;
×
47
                                continue;
×
48
                        }
49

50
                        // surface when interfaces are trying to be registered with invalid configuration
51
                        if ( ! is_string( $interface ) ) {
567✔
52
                                graphql_debug(
×
53
                                        sprintf(
×
54
                                                // translators: %s is the name of the GraphQL type.
55
                                                __( 'Invalid Interface registered to the "%s" Type. Interfaces can only be registered with an interface name or a valid instance of an InterfaceType', 'wp-graphql' ),
×
NEW
56
                                                $this->config['name'] ?? 'Unknown'
×
57
                                        ),
×
58
                                        [ 'invalid_interface' => $interface ]
×
59
                                );
×
60
                                continue;
×
61
                        }
62

63
                        // Prevent an interface from implementing itself
64
                        if ( ! empty( $this->config['name'] ) && strtolower( $this->config['name'] ) === strtolower( $interface ) ) {
567✔
65
                                graphql_debug(
1✔
66
                                        sprintf(
1✔
67
                                                // translators: %s is the name of the interface.
68
                                                __( 'The "%s" Interface attempted to implement itself, which is not allowed', 'wp-graphql' ),
1✔
69
                                                $interface
1✔
70
                                        )
1✔
71
                                );
1✔
72
                                continue;
1✔
73
                        }
74

75
                        $interface_type = $this->type_registry->get_type( $interface );
567✔
76
                        if ( ! $interface_type instanceof InterfaceType ) {
567✔
77
                                graphql_debug(
3✔
78
                                        sprintf(
3✔
79
                                                // translators: %1$s is the name of the interface, %2$s is the name of the type.
80
                                                __( '"%1$s" is not a valid Interface Type and cannot be implemented as an Interface on the "%2$s" Type', 'wp-graphql' ),
3✔
81
                                                $interface,
3✔
82
                                                $this->config['name'] ?? 'Unknown'
3✔
83
                                        )
3✔
84
                                );
3✔
85
                                continue;
3✔
86
                        }
87

88
                        $new_interfaces[ $interface ] = $interface_type;
567✔
89
                        $interface_interfaces         = $interface_type->getInterfaces();
567✔
90

91
                        if ( empty( $interface_interfaces ) ) {
567✔
92
                                continue;
567✔
93
                        }
94

95
                        foreach ( $interface_interfaces as $interface_interface_name => $interface_interface ) {
559✔
96
                                if ( ! $interface_interface instanceof InterfaceType ) {
559✔
97
                                        continue;
×
98
                                }
99

100
                                $new_interfaces[ $interface_interface_name ] = $interface_interface;
559✔
101
                        }
102
                }
103

104
                return array_unique( $new_interfaces );
567✔
105
        }
106

107

108
        /**
109
         * Returns the fields for a Type, applying any missing fields defined on interfaces implemented on the type
110
         *
111
         * @param array<mixed>                     $config
112
         * @param \WPGraphQL\Registry\TypeRegistry $type_registry
113
         *
114
         * @return array<mixed>
115
         * @throws \Exception
116
         */
117
        protected function get_fields( array $config, TypeRegistry $type_registry ): array {
604✔
118
                $fields = array_filter( $config['fields'] );
604✔
119

120
                /**
121
                 * Get the fields of interfaces and ensure they exist as fields of this type.
122
                 *
123
                 * Types are still responsible for ensuring the fields resolve properly.
124
                 */
125
                $interface_fields = $this->get_fields_from_implemented_interfaces( $type_registry );
604✔
126

127
                // Merge fields with interface fields that are not already in fields
128
                $fields = array_merge( $fields, array_diff_key( $interface_fields, $fields ) );
604✔
129

130
                foreach ( $fields as $field_name => $field ) {
604✔
131
                        $merged_field_config = $this->inherit_field_config_from_interface( $field_name, $field, $interface_fields );
604✔
132

133
                        if ( null === $merged_field_config ) {
604✔
134
                                unset( $fields[ $field_name ] );
×
135
                                continue;
×
136
                        }
137

138
                        // Update the field.
139
                        $fields[ $field_name ] = $merged_field_config;
604✔
140
                }
141

142
                $fields = $this->prepare_fields( $fields, $config['name'], $config );
604✔
143
                $fields = $type_registry->prepare_fields( $fields, $config['name'] );
604✔
144

145
                $this->fields = $fields;
604✔
146
                return $this->fields;
604✔
147
        }
148

149
        /**
150
         * Get the fields from the implemented interfaces.
151
         *
152
         * @param \WPGraphQL\Registry\TypeRegistry $registry The TypeRegistry instance.
153
         *
154
         * @return array<string,array<string,mixed>>
155
         */
156
        private function get_fields_from_implemented_interfaces( TypeRegistry $registry ): array {
604✔
157
                $interface_fields = [];
604✔
158

159
                $interfaces = $this->getInterfaces();
604✔
160

161
                // Get the fields for each interface.
162
                foreach ( $interfaces as $interface_type ) {
604✔
163
                        // Get the resolved InterfaceType instance, if it's not already an instance of InterfaceType.
164
                        if ( ! $interface_type instanceof InterfaceType ) {
567✔
165
                                $interface_type = $registry->get_type( $interface_type );
×
166
                        }
167

168
                        if ( ! $interface_type instanceof InterfaceType ) {
567✔
169
                                continue;
×
170
                        }
171

172
                        $interface_config_fields = $interface_type->getFields();
567✔
173

174
                        if ( empty( $interface_config_fields ) ) {
567✔
175
                                continue;
×
176
                        }
177

178
                        foreach ( $interface_config_fields as $interface_field_name => $interface_field ) {
567✔
179
                                $interface_fields[ $interface_field_name ] = $interface_field->config;
567✔
180
                        }
181
                }
182

183
                return $interface_fields;
604✔
184
        }
185

186
        /**
187
         * Inherit missing field configs from the interface.
188
         *
189
         * @param string                            $field_name The field name.
190
         * @param array<string,mixed>               $field The field config.
191
         * @param array<string,array<string,mixed>> $interface_fields The fields from the interface. This is passed by reference.
192
         *
193
         * @return ?array<string,mixed> The field config with inherited values. Null if the field type cannot be determined.
194
         */
195
        private function inherit_field_config_from_interface( string $field_name, array $field, array $interface_fields ): ?array {
604✔
196

197
                $interface_field = $interface_fields[ $field_name ] ?? [];
604✔
198

199
                // Bail early, if there is no field type or an interface to inherit it from since we won't be able to register it.
200
                if ( empty( $field['type'] ) && empty( $interface_field['type'] ) ) {
604✔
201
                        graphql_debug(
×
202
                                sprintf(
×
203
                                        // translators: %1$s is the field name, %2$s is the type name.
204
                                        __( 'Invalid Interface field %1$s registered to the "%2$s" Type. Fields must be registered a valid GraphQL `type`.', 'wp-graphql' ),
×
205
                                        $field_name,
×
NEW
206
                                        $this->config['name'] ?? 'Unknown'
×
207
                                )
×
208
                        );
×
209

210
                        return null;
×
211
                }
212

213
                // Inherit the field config from the interface if it's not set on the field.
214
                foreach ( $interface_field as $key => $config ) {
604✔
215
                        // Inherit the field config from the interface if it's not set on the field.
216
                        if ( empty( $field[ $key ] ) ) {
567✔
217
                                $field[ $key ] = $config;
566✔
218
                                continue;
566✔
219
                        }
220

221
                        // If the args on both the field and the interface are set, we need to merge them.
222
                        if ( 'args' === $key ) {
567✔
223
                                $field[ $key ] = $this->merge_field_args( $field_name, $field[ $key ], $interface_field[ $key ] );
402✔
224
                        }
225
                }
226

227
                return $field;
604✔
228
        }
229

230
        /**
231
         * Merge the field args from the field and the interface.
232
         *
233
         * @param string                            $field_name The field name.
234
         * @param array<string,array<string,mixed>> $field_args The field args.
235
         * @param array<string,array<string,mixed>> $interface_args The interface args.
236
         *
237
         * @return array<string,array<string,mixed>> The merged field args.
238
         */
239
        private function merge_field_args( string $field_name, array $field_args, array $interface_args ): array {
402✔
240
                // We use the interface args as the base and overwrite them with the field args.
241
                $merged_args = $interface_args;
402✔
242

243
                foreach ( $field_args as $arg_name => $config ) {
402✔
244
                        // If the arg is not defined on the interface, we can use the field arg config.
245
                        if ( empty( $merged_args[ $arg_name ] ) ) {
402✔
246
                                $merged_args[ $arg_name ] = $config;
1✔
247
                                continue;
1✔
248
                        }
249

250
                        // Check if the interface arg type is different from the new field arg type.
251
                        $field_arg_type     = $this->normalize_type_name( $config['type'] );
402✔
252
                        $interface_arg_type = $this->normalize_type_name( $merged_args[ $arg_name ]['type'] );
402✔
253

254
                        if ( ! empty( $field_arg_type ) && $interface_arg_type !== $field_arg_type ) {
402✔
255
                                graphql_debug(
1✔
256
                                        sprintf(
1✔
257
                                                /* translators: 1: Object type name, 2: Field name, 3: Argument name, 4: Expected argument type, 5: Actual argument type. */
258
                                                __(
1✔
259
                                                        'Interface field argument "%1$s.%2$s(%3$s:)" expected to be of type "%4$s" but got "%5$s". Please ensure the field arguments match the interface field arguments or rename the argument.',
1✔
260
                                                        'wp-graphql'
1✔
261
                                                ),
1✔
262
                                                $this->config['name'] ?? 'Unknown',
1✔
263
                                                $field_name,
1✔
264
                                                $arg_name,
1✔
265
                                                $interface_arg_type,
1✔
266
                                                $field_arg_type
1✔
267
                                        )
1✔
268
                                );
1✔
269
                                continue;
1✔
270
                        }
271

272
                        // Merge the field arg config with the interface arg config.
273
                        $merged_args[ $arg_name ] = array_merge( $merged_args[ $arg_name ], $config );
401✔
274
                }
275

276
                return $merged_args;
402✔
277
        }
278

279
        /**
280
         * Given a type it will return a string representation of the type.
281
         *
282
         * This is used for optimistic comparison of the arg types.
283
         *
284
         * @param string|array<string,mixed>|callable|\GraphQL\Type\Definition\Type $type The type to normalize.
285
         */
286
        private function normalize_type_name( $type ): string {
402✔
287
                // Bail early if the type is empty.
288
                if ( empty( $type ) ) {
402✔
289
                        return '';
×
290
                }
291

292
                // If the type is a callable, we need to resolve it.
293
                if ( is_callable( $type ) ) {
402✔
294
                        $type = $type();
402✔
295
                }
296

297
                // If the type is an instance of a Type, we can get the name.
298
                if ( $type instanceof \GraphQL\Type\Definition\Type ) {
402✔
299
                        $type = $type->name ?? $type->toString();
402✔
300
                }
301

302
                // If the type is *now* a string, we can return it.
303
                if ( is_string( $type ) ) {
402✔
304
                        return $type;
402✔
305
                } elseif ( ! is_array( $type ) ) {
1✔
306
                        // If the type is not an array, we can't do anything with it.
UNCOV
307
                        return '';
×
308
                }
309

310
                // Arrays mean the type can be nested in modifiers.
311
                $output   = '';
1✔
312
                $modifier = array_keys( $type )[0];
1✔
313
                $type     = $type[ $modifier ];
1✔
314

315
                // Convert the type wrappers to a string, and recursively get the internals.
316
                switch ( $modifier ) {
317
                        case 'list_of':
1✔
318
                                $output = '[' . $this->normalize_type_name( $type ) . ']';
1✔
319
                                break;
1✔
320
                        case 'non_null':
×
321
                                $output = '!' . $this->normalize_type_name( $type );
×
322
                                break;
×
323
                }
324

325
                return $output;
1✔
326
        }
327
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc