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

wp-graphql / wp-graphql / 14716683875

28 Apr 2025 07:58PM UTC coverage: 84.287% (+1.6%) from 82.648%
14716683875

push

github

actions-user
release: merge develop into master for v2.3.0

15905 of 18870 relevant lines covered (84.29%)

257.23 hits per line

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

85.61
/src/Type/WPMutationType.php
1
<?php
2
namespace WPGraphQL\Type;
3

4
use GraphQL\Type\Definition\ResolveInfo;
5
use WPGraphQL\AppContext;
6
use WPGraphQL\Registry\TypeRegistry;
7

8
/**
9
 * Class WPMutationType
10
 *
11
 * @package WPGraphQL\Type
12
 */
13
class WPMutationType {
14
        /**
15
         * Configuration for how auth should be handled on the connection field
16
         *
17
         * @var array<string,mixed>
18
         */
19
        protected $auth;
20

21
        /**
22
         * The config for the connection
23
         *
24
         * @var array<string,mixed>
25
         */
26
        protected $config;
27

28
        /**
29
         * The name of the mutation field
30
         *
31
         * @var string
32
         */
33
        protected $mutation_name;
34

35
        /**
36
         * Whether the user must be authenticated to use the mutation.
37
         *
38
         * @var bool
39
         */
40
        protected $is_private;
41

42
        /**
43
         * The mutation input field config.
44
         *
45
         * @var array<string,array<string,mixed>>
46
         */
47
        protected $input_fields;
48

49
        /**
50
         * The mutation output field config.
51
         *
52
         * @var array<string,array<string,mixed>>
53
         */
54
        protected $output_fields;
55

56
        /**
57
         * The resolver function to resolve the mutation
58
         *
59
         * @var callable(mixed $root,array<string,mixed> $args,\WPGraphQL\AppContext $context,\GraphQL\Type\Definition\ResolveInfo $info): array<string,mixed>
60
         */
61
        protected $resolve_mutation;
62

63
        /**
64
         * The WPGraphQL TypeRegistry
65
         *
66
         * @var \WPGraphQL\Registry\TypeRegistry
67
         */
68
        protected $type_registry;
69

70
        /**
71
         * WPMutationType constructor.
72
         *
73
         * @param array<string,mixed>              $config        The config array for the mutation
74
         * @param \WPGraphQL\Registry\TypeRegistry $type_registry Instance of the WPGraphQL Type Registry
75
         *
76
         * @throws \Exception
77
         */
78
        public function __construct( array $config, TypeRegistry $type_registry ) {
593✔
79

80
                /**
81
                 * Filter the config of WPMutationType
82
                 *
83
                 * @param array<string,mixed>            $config           Array of configuration options passed to the WPMutationType when instantiating a new type
84
                 * @param \WPGraphQL\Type\WPMutationType $wp_mutation_type The instance of the WPMutationType class
85
                 *
86
                 * @since 1.13.0
87
                 */
88
                $config = apply_filters( 'graphql_wp_mutation_type_config', $config, $this );
593✔
89

90
                if ( ! $this->is_config_valid( $config ) ) {
593✔
91
                        return;
×
92
                }
93

94
                $this->config        = $config;
593✔
95
                $this->type_registry = $type_registry;
593✔
96
                $this->mutation_name = $config['name'];
593✔
97

98
                // Bail if the mutation should be excluded from the schema.
99
                if ( ! $this->should_register() ) {
593✔
100
                        return;
×
101
                }
102

103
                $this->auth             = array_key_exists( 'auth', $config ) && is_array( $config['auth'] ) ? $config['auth'] : [];
593✔
104
                $this->is_private       = array_key_exists( 'isPrivate', $config ) ? $config['isPrivate'] : false;
593✔
105
                $this->input_fields     = $this->get_input_fields();
593✔
106
                $this->output_fields    = $this->get_output_fields();
593✔
107
                $this->resolve_mutation = $this->get_resolver();
593✔
108

109
                /**
110
                 * Run an action when the WPMutationType is instantiating.
111
                 *
112
                 * @param array<string,mixed>            $config           Array of configuration options passed to the WPObjectType when instantiating a new type
113
                 * @param \WPGraphQL\Type\WPMutationType $wp_mutation_type The instance of the WPMutationType class
114
                 *
115
                 * @since 1.13.0
116
                 */
117
                do_action( 'graphql_wp_mutation_type', $config, $this );
593✔
118

119
                $this->register_mutation();
593✔
120
        }
121

122
        /**
123
         * Validates that essential key/value pairs are passed to the connection config.
124
         *
125
         * @param array<string,mixed> $config The config array for the mutation
126
         */
127
        protected function is_config_valid( array $config ): bool {
593✔
128
                $is_valid = true;
593✔
129

130
                if ( ! array_key_exists( 'name', $config ) || ! is_string( $config['name'] ) ) {
593✔
131
                        graphql_debug(
×
132
                                __( 'Mutation config needs to have a valid name.', 'wp-graphql' ),
×
133
                                [
×
134
                                        'config' => $config,
×
135
                                ]
×
136
                        );
×
137
                        $is_valid = false;
×
138
                }
139

140
                if ( ! array_key_exists( 'mutateAndGetPayload', $config ) || ! is_callable( $config['mutateAndGetPayload'] ) ) {
593✔
141
                        graphql_debug(
×
142
                                __( 'Mutation config needs to have "mutateAndGetPayload" defined as a callable.', 'wp-graphql' ),
×
143
                                [
×
144
                                        'config' => $config,
×
145
                                ]
×
146
                        );
×
147
                        $is_valid = false;
×
148
                }
149

150
                return $is_valid;
593✔
151
        }
152

153
        /**
154
         * Gets the mutation input fields.
155
         *
156
         * @return array<string,array<string,mixed>>
157
         */
158
        protected function get_input_fields(): array {
593✔
159
                $input_fields = [
593✔
160
                        'clientMutationId' => [
593✔
161
                                'type'        => 'String',
593✔
162
                                'description' => static function () {
593✔
163
                                        return __( 'This is an ID that can be passed to a mutation by the client to track the progress of mutations and catch possible duplicate mutation submissions.', 'wp-graphql' );
15✔
164
                                },
593✔
165
                        ],
593✔
166
                ];
593✔
167

168
                if ( ! empty( $this->config['inputFields'] ) && is_array( $this->config['inputFields'] ) ) {
593✔
169
                        $input_fields = array_merge( $input_fields, $this->config['inputFields'] );
593✔
170
                }
171

172
                return $input_fields;
593✔
173
        }
174

175
        /**
176
         * Gets the mutation output fields.
177
         *
178
         * @return array<string,array<string,mixed>>
179
         */
180
        protected function get_output_fields(): array {
593✔
181
                $output_fields = [
593✔
182
                        'clientMutationId' => [
593✔
183
                                'type'        => 'String',
593✔
184
                                'description' => static function () {
593✔
185
                                        return __( 'If a \'clientMutationId\' input is provided to the mutation, it will be returned as output on the mutation. This ID can be used by the client to track the progress of mutations and catch possible duplicate mutation submissions.', 'wp-graphql' );
15✔
186
                                },
593✔
187
                        ],
593✔
188
                ];
593✔
189

190
                if ( ! empty( $this->config['outputFields'] ) && is_array( $this->config['outputFields'] ) ) {
593✔
191
                        $output_fields = array_merge( $output_fields, $this->config['outputFields'] );
593✔
192
                }
193

194
                return $output_fields;
593✔
195
        }
196

197
        /**
198
         * Gets the resolver callable for the mutation.
199
         *
200
         * @return callable(mixed $root,array<string,mixed> $args,\WPGraphQL\AppContext $context,\GraphQL\Type\Definition\ResolveInfo $info): array<string,mixed>
201
         */
202
        protected function get_resolver(): callable {
593✔
203
                return function ( $root, array $args, AppContext $context, ResolveInfo $info ) {
593✔
204
                        $unfiltered_input = $args['input'];
107✔
205

206
                        $unfiltered_input = $args['input'];
107✔
207

208
                        /**
209
                         * Filters the mutation input before it's passed to the `mutateAndGetPayload` callback.
210
                         *
211
                         * @param array<string,mixed>                  $input         The mutation input args.
212
                         * @param \WPGraphQL\AppContext                $context       The AppContext object.
213
                         * @param \GraphQL\Type\Definition\ResolveInfo $info          The ResolveInfo object.
214
                         * @param string                               $mutation_name The name of the mutation field.
215
                         */
216
                        $input = apply_filters( 'graphql_mutation_input', $unfiltered_input, $context, $info, $this->mutation_name );
107✔
217

218
                        /**
219
                         * Filter to short circuit the mutateAndGetPayload callback.
220
                         * Returning anything other than null will stop the callback for the mutation from executing,
221
                         * and will return your data or execute your callback instead.
222
                         *
223
                         * @param array<string,mixed>|callable|null   $payload.            The payload returned from the callback. Null by default.
224
                         * @param string                $mutation_name       The name of the mutation field.
225
                         * @param callable|\Closure     $mutateAndGetPayload The callback for the mutation.
226
                         * @param array<string,mixed>   $input               The mutation input args.
227
                         * @param \WPGraphQL\AppContext $context             The AppContext object.
228
                         * @param \GraphQL\Type\Definition\ResolveInfo $info The ResolveInfo object.
229
                         */
230
                        $pre = apply_filters( 'graphql_pre_mutate_and_get_payload', null, $this->mutation_name, $this->config['mutateAndGetPayload'], $input, $context, $info );
107✔
231

232
                        if ( ! is_null( $pre ) ) {
107✔
233
                                $payload = is_callable( $pre ) ? $pre( $input, $context, $info ) : $pre;
×
234
                        } else {
235
                                $payload = $this->config['mutateAndGetPayload']( $input, $context, $info );
107✔
236

237
                                /**
238
                                 * Filters the payload returned from the default mutateAndGetPayload callback.
239
                                 *
240
                                 * @param array<string,mixed>   $payload The payload returned from the callback.
241
                                 * @param string                $mutation_name The name of the mutation field.
242
                                 * @param array<string,mixed>   $input The mutation input args.
243
                                 * @param \WPGraphQL\AppContext $context The AppContext object.
244
                                 * @param \GraphQL\Type\Definition\ResolveInfo $info The ResolveInfo object.
245
                                 */
246
                                $payload = apply_filters( 'graphql_mutation_payload', $payload, $this->mutation_name, $input, $context, $info );
68✔
247
                        }
248

249
                        /**
250
                         * Fires after the mutation payload has been returned from the `mutateAndGetPayload` callback.
251
                         *
252
                         * @param array<string,mixed>                  $payload          The Payload returned from the mutation.
253
                         * @param array<string,mixed>                  $input            The mutation input args, after being filtered by 'graphql_mutation_input'.
254
                         * @param array<string,mixed>                  $unfiltered_input The unfiltered input args of the mutation
255
                         * @param \WPGraphQL\AppContext                $context          The AppContext object.
256
                         * @param \GraphQL\Type\Definition\ResolveInfo $info             The ResolveInfo object.
257
                         * @param string                               $mutation_name    The name of the mutation field.
258
                         */
259
                        do_action( 'graphql_mutation_response', $payload, $input, $unfiltered_input, $context, $info, $this->mutation_name );
68✔
260

261
                        // Add the client mutation ID to the payload
262
                        if ( ! empty( $input['clientMutationId'] ) ) {
68✔
263
                                $payload['clientMutationId'] = $input['clientMutationId'];
10✔
264
                        }
265

266
                        return $payload;
68✔
267
                };
593✔
268
        }
269

270
        /**
271
         * Registers the input args for the mutation.
272
         */
273
        protected function register_mutation_input(): void {
593✔
274
                $input_name = $this->mutation_name . 'Input';
593✔
275

276
                if ( $this->type_registry->has_type( $input_name ) ) {
593✔
277
                        return;
×
278
                }
279

280
                $this->type_registry->register_input_type(
593✔
281
                        $input_name,
593✔
282
                        [
593✔
283
                                // translators: %s is the name of the mutation.
284
                                'description'       => function () {
593✔
285
                                        // translators: %s is the name of the mutation.
286
                                        return sprintf( __( 'Input for the %1$s mutation.', 'wp-graphql' ), $this->mutation_name );
15✔
287
                                },
593✔
288
                                'fields'            => $this->input_fields,
593✔
289
                                'deprecationReason' => ! empty( $this->config['deprecationReason'] ) ? $this->config['deprecationReason'] : null,
593✔
290
                        ]
593✔
291
                );
593✔
292
        }
293

294
        /**
295
         * Registers the payload type to the Schema.
296
         */
297
        protected function register_mutation_payload(): void {
593✔
298
                $object_name = $this->mutation_name . 'Payload';
593✔
299

300
                if ( $this->type_registry->has_type( $object_name ) ) {
593✔
301
                        return;
×
302
                }
303

304
                $this->type_registry->register_object_type(
593✔
305
                        $object_name,
593✔
306
                        [
593✔
307
                                // translators: %s is the name of the mutation.
308
                                'description'       => function () {
593✔
309
                                        // translators: %s is the name of the mutation.
310
                                        return sprintf( __( 'The payload for the %s mutation.', 'wp-graphql' ), $this->mutation_name );
15✔
311
                                },
593✔
312
                                'fields'            => $this->output_fields,
593✔
313
                                'deprecationReason' => ! empty( $this->config['deprecationReason'] ) ? $this->config['deprecationReason'] : null,
593✔
314
                        ]
593✔
315
                );
593✔
316
        }
317

318
        /**
319
         * Registers the mutation in the Graph.
320
         *
321
         * @throws \Exception
322
         */
323
        protected function register_mutation_field(): void {
593✔
324
                $field_config = array_merge(
593✔
325
                        $this->config,
593✔
326
                        [
593✔
327
                                'args'        => [
593✔
328
                                        'input' => [
593✔
329
                                                'type'              => [ 'non_null' => $this->mutation_name . 'Input' ],
593✔
330
                                                'description'       => function () {
593✔
331
                                                        // translators: %s is the name of the mutation.
332
                                                        return sprintf( __( 'Input for the %s mutation', 'wp-graphql' ), $this->mutation_name );
69✔
333
                                                },
593✔
334
                                                'deprecationReason' => ! empty( $this->config['deprecationReason'] ) ? $this->config['deprecationReason'] : null,
593✔
335
                                        ],
593✔
336
                                ],
593✔
337
                                'auth'        => $this->auth,
593✔
338
                                'description' => function () {
593✔
339
                                        // translators: %s is the name of the mutation.
340
                                        return ! empty( $this->config['description'] ) ? $this->config['description'] : sprintf( __( 'The %s mutation', 'wp-graphql' ), $this->mutation_name );
69✔
341
                                },
593✔
342
                                'isPrivate'   => $this->is_private,
593✔
343
                                'type'        => $this->mutation_name . 'Payload',
593✔
344
                                'resolve'     => $this->resolve_mutation,
593✔
345
                                'name'        => lcfirst( $this->mutation_name ),
593✔
346
                        ]
593✔
347
                );
593✔
348

349
                $this->type_registry->register_field(
593✔
350
                        'RootMutation',
593✔
351
                        lcfirst( $this->mutation_name ),
593✔
352
                        $field_config
593✔
353
                );
593✔
354
        }
355

356
        /**
357
         * Registers the Mutation Types and field to the Schema.
358
         *
359
         * @throws \Exception
360
         */
361
        protected function register_mutation(): void {
593✔
362
                $this->register_mutation_payload();
593✔
363
                $this->register_mutation_input();
593✔
364
                $this->register_mutation_field();
593✔
365
        }
366

367
        /**
368
         * Checks whether the mutation should be registered to the schema.
369
         */
370
        protected function should_register(): bool {
593✔
371
                // Dont register mutations if they have been excluded from the schema.
372
                $excluded_mutations = $this->type_registry->get_excluded_mutations();
593✔
373
                if ( in_array( strtolower( $this->mutation_name ), $excluded_mutations, true ) ) {
593✔
374
                        return false;
×
375
                }
376

377
                return true;
593✔
378
        }
379
}
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

© 2025 Coveralls, Inc