Automattic\WooCommerce\Api\Infrastructure
GraphQLControllerBase::process_request │ private │ WC 1.0
Process the GraphQL request. Extracted so that handle_request() can wrap everything in a single try/catch that respects debug mode.
Method of the class: GraphQLControllerBase{}
No Hooks.
Returns
null. Nothing (null).
Usage
// private - for code of main (parent) class only $result = $this->process_request( $request, $principal ): \WP_REST_Response;
- $request(WP_REST_Request) (required)
- The REST request.
- $principal(object) (required)
- The principal resolved by handle_request(); never null when this is reached.
GraphQLControllerBase::process_request() GraphQLControllerBase::process request code WC 10.9.1
private function process_request( \WP_REST_Request $request, object $principal ): \WP_REST_Response {
// 2. Parse request. GET query-string `variables` and `extensions`
// arrive as JSON strings; decode_json_param() unifies them with the
// already-decoded-array path from POST bodies and rejects malformed
// or non-object payloads up front so they surface as HTTP 400
// INVALID_ARGUMENT instead of as confusing resolver errors (null
// decode) or HTTP 500 TypeErrors (scalar decode).
$query = $request->get_param( 'query' );
$operation_name = $request->get_param( 'operationName' );
$variables = $this->decode_json_param( $request->get_param( 'variables' ), 'variables' );
$extensions = $this->decode_json_param( $request->get_param( 'extensions' ), 'extensions' );
// 3. Resolve query (cache lookup / APQ / parse).
$source = $this->query_cache->resolve( $query, $extensions );
if ( is_array( $source ) ) {
$default = $this->get_resolve_error_status( $source );
return new \WP_REST_Response( $source, $this->pick_status( $default, $source, $request ) );
}
// 4. Reject mutations over GET (GraphQL over HTTP spec).
if ( 'GET' === $request->get_method() && $this->document_has_mutation( $source, $operation_name ) ) {
$method_not_allowed_output = array(
'errors' => array(
array(
'message' => 'Mutations are not allowed over GET requests. Use POST instead.',
'extensions' => array( 'code' => 'METHOD_NOT_ALLOWED' ),
),
),
);
return new \WP_REST_Response(
$method_not_allowed_output,
$this->pick_status( 405, $method_not_allowed_output, $request )
);
}
// 5. Load schema.
$schema = $this->get_engine_schema();
// 6. Build validation rules.
// A single complexity-rule instance is kept so its computed score can
// be surfaced in the debug extensions after execution.
$complexity_rule = new QueryComplexityRule( self::get_max_query_complexity() );
$validation_rules = array_values( DocumentValidator::allRules() );
$validation_rules[] = new QueryDepthRule( self::get_max_query_depth() );
$validation_rules[] = $complexity_rule;
if ( ! $this->is_introspection_allowed( $principal, $request ) ) {
$validation_rules[] = new DisableIntrospection( DisableIntrospection::ENABLED );
}
// 7. Execute. The context value is an ArrayObject (not a plain array)
// so root resolvers can mutate it — specifically to thread the root
// query's metadata into `$context['_query_metadata']` for downstream
// field-level authorization gates. ArrayObject preserves the
// `$context['key']` read syntax via ArrayAccess. The context carries
// the resolved principal through to autogenerated resolvers, which
// expose it as the `_principal` infrastructure parameter when commands
// declare it on their authorize()/execute() methods. Request-derived
// data that resolvers need is carried by the principal class itself —
// populated by the PrincipalResolver, the only component wired to the
// HTTP transport.
$result = GraphQL::executeQuery(
schema: $schema,
source: $source,
contextValue: new \ArrayObject(
array(
'principal' => $principal,
)
),
variableValues: $variables,
operationName: $operation_name,
validationRules: $validation_rules,
);
// Install an error formatter that guarantees every error carries an
// `extensions.code`. Our resolvers route everything through
// Utils::execute_command / Utils::authorize_command, which already
// translate domain exceptions (ApiException, InvalidArgumentException,
// generic Throwable) into coded GraphQL errors at the throw site.
// What reaches us uncoded here is webonyx-native validation and
// execution output, so we infer from webonyx's ClientAware signal:
// client-safe errors become BAD_USER_INPUT (400), the rest become
// INTERNAL_ERROR (500).
//
// In debug mode the same formatter also walks the previous-exception
// chain so wrapped errors (e.g. a \ValueError caught by a resolver and
// re-thrown as INTERNAL_ERROR) stay visible to the developer instead
// of being masked behind the generic "Internal server error" message.
$debug_mode = $this->is_debug_mode( $principal, $request );
$result->setErrorFormatter(
function ( \Throwable $error ) use ( $debug_mode ): array {
$formatted = \Automattic\WooCommerce\Vendor\GraphQL\Error\FormattedError::createFromException( $error );
if ( ! isset( $formatted['extensions']['code'] ) ) {
$client_safe = $error instanceof \Automattic\WooCommerce\Vendor\GraphQL\Error\ClientAware && $error->isClientSafe();
$formatted['extensions']['code'] = $client_safe ? 'BAD_USER_INPUT' : 'INTERNAL_ERROR';
}
// SerializationError (thrown during schema-type coercion, e.g. when
// a resolver returns an Int that doesn't fit 32 bits) extends
// \Exception rather than webonyx's ClientAware Error, so it lands
// in the INTERNAL_ERROR bucket above. Its message is actually
// client-actionable ("value out of range — send smaller inputs"),
// so promote it to BAD_USER_INPUT when it shows up anywhere in
// the previous-exception chain.
if ( 'BAD_USER_INPUT' !== ( $formatted['extensions']['code'] ?? null ) ) {
$cursor = $error;
while ( $cursor instanceof \Throwable ) {
if ( $cursor instanceof \Automattic\WooCommerce\Vendor\GraphQL\Error\SerializationError ) {
$formatted['extensions']['code'] = 'BAD_USER_INPUT';
break;
}
$cursor = $cursor->getPrevious();
}
}
if ( $debug_mode ) {
$chain = $this->extract_previous_chain( $error );
if ( ! empty( $chain ) ) {
$formatted['extensions']['previous'] = $chain;
}
}
return $formatted;
}
);
$debug_flags = $this->get_debug_flags( $request, $principal );
$output = $result->toArray( $debug_flags );
// 8. Debug-mode metrics: expose the computed complexity and depth so
// clients tuning queries can see what the server scored the request at.
if ( $this->is_debug_mode( $principal, $request ) ) {
if ( ! isset( $output['extensions'] ) ) {
$output['extensions'] = array();
}
if ( ! isset( $output['extensions']['debug'] ) ) {
$output['extensions']['debug'] = array();
}
$output['extensions']['debug']['complexity'] = $complexity_rule->getQueryComplexity();
$output['extensions']['debug']['depth'] = $this->compute_query_depth( $source, $operation_name );
}
// 9. Determine HTTP status code. GraphQL emits `data: { field: null }`
// for nullable root fields even when the resolver errored, so gating
// the status override on `data` being absent would leave nearly every
// error response on HTTP 200. Always derive the status from the
// errors array when one is present — clients that need "200 with
// partial data" semantics can still read the `errors` array.
$default = isset( $output['errors'] ) ? $this->get_error_status( $output['errors'] ) : 200;
$status = $this->pick_status( $default, $output, $request );
return new \WP_REST_Response( $output, $status );
}