How to determine is it query or mutation field executed from field middleware? #477
-
How can I know about the type of field from middleware? I want to do some logic in case of a retrieved request for operation (started only for queries as they mean to be idempotent). Example of minimalistic middleware: <?php
declare(strict_types=1);
namespace App\Graphql;
use GraphQL\Type\Definition\FieldDefinition;
use TheCodingMachine\GraphQLite\Middlewares\FieldHandlerInterface;
use TheCodingMachine\GraphQLite\Middlewares\FieldMiddlewareInterface;
use TheCodingMachine\GraphQLite\QueryFieldDescriptor;
final class IdempotentLogicMiddleware implements FieldMiddlewareInterface
{
public function process(
QueryFieldDescriptor $queryFieldDescriptor,
FieldHandlerInterface $fieldHandler
): ?FieldDefinition {
$resolver = $queryFieldDescriptor->getResolver();
$queryFieldDescriptor->setResolver(function (...$args) use ($queryFieldDescriptor, $resolver) {
$idempotent = false /* is it a query? */;
if ($idempotent) {
// do some logic (fetch from cache? increment requested queries counter? track query execution time?)
}
$result = $resolver(...$args);
if ($idempotent) {
// do some logic (persist cache? increment executed queries counter? track query execution time?)
}
return $result;
});
return $fieldHandler->handle($queryFieldDescriptor);
}
} I have only one idea of how to get it. And this is can be a bit unstable (because it based on search field by name from queries - mutation can have the same name as query, so if user will execute mutation with name /** @var \GraphQL\Server\ServerConfig $serverConfig */
$query = $serverConfig->getSchema()?->getQueryType()?->findField($queryFieldDescriptor->getName());
$idempotent = null !== $query; |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 1 reply
-
Are you trying to determine if fields are being updated here, for the sake of "update" style requests and updating your data persistence? |
Beta Was this translation helpful? Give feedback.
-
We actually have a somewhat similar use case and process the request prior to passing it off to GraphQL. It's not great though, since we have to parse the request body for our specific needs and then GraphQL parses the request body again. I'm not sure there is a great way of doing this at the moment. If you'd like to dig into it a bit further and have suggestions or would like to submit a PR, that'd be great. |
Beta Was this translation helpful? Give feedback.
-
After many investigation iterations, I found, that important information bypassed with It is pretty complex to inject it into the resolver (and don't break other arguments bypassing) from the middleware perspective, but it just works. Here's a working example of how to know about operation type in the middleware (example with checking for <?php
declare(strict_types=1);
namespace App\Graphql;
use GraphQL\Type\Definition\FieldDefinition;
use GraphQL\Type\Definition\ResolveInfo;
use TheCodingMachine\GraphQLite\Middlewares\FieldHandlerInterface;
use TheCodingMachine\GraphQLite\Middlewares\FieldMiddlewareInterface;
use TheCodingMachine\GraphQLite\Parameters\ResolveInfoParameter;
use TheCodingMachine\GraphQLite\QueryFieldDescriptor;
class IdempotentLogicMiddleware implements FieldMiddlewareInterface
{
private const OPERATION_QUERY = 'query';
public function process(
QueryFieldDescriptor $queryFieldDescriptor,
FieldHandlerInterface $fieldHandler
): ?FieldDefinition {
$resolver = $queryFieldDescriptor->getResolver();
$this->appendRequiredResolveInfoParameter($queryFieldDescriptor);
$queryFieldDescriptor->setResolver(function (...$args) use ($resolver) {
[$toResolverArgs, $resolveInfo] = $this->extractResolveInfoArgument($args);
if (false === $this->isQueryOperation($resolveInfo)) {
return $resolver(...$toResolverArgs);
}
// some logic for query-only operations
$result = $resolver(...$toResolverArgs);
// some logic for query-only operations
return $result;
});
return $fieldHandler->handle($queryFieldDescriptor);
}
private function appendRequiredResolveInfoParameter(QueryFieldDescriptor $queryFieldDescriptor): void
{
$arguments = $queryFieldDescriptor->getParameters();
$arguments['__idempotent_logic_middleware_resolve_info'] = new ResolveInfoParameter();
$queryFieldDescriptor->setParameters($arguments);
}
/**
* @param array $args
*
* @return array{0: array, 1: ?ResolveInfo}
*/
private function extractResolveInfoArgument(array $args): array
{
// search resolve info from parameters
// from last to first argument (to avoid extracting 'real' argument)
// when resolve info found - drop it from arguments (as it is internal)
$toResolverArgs = [];
$resolveInfo = null;
foreach (\array_reverse($args) as $arg) {
if (null === $resolveInfo && $arg instanceof ResolveInfo) {
$resolveInfo = $arg;
continue;
}
$toResolverArgs[] = $arg;
}
$toResolverArgs = \array_reverse($toResolverArgs);
return [$toResolverArgs, $resolveInfo];
}
private function isQueryOperation(?ResolveInfo $resolveInfo): bool
{
return self::OPERATION_QUERY === $resolveInfo?->operation->operation;
}
} Unfortunately, I don't know how to convert this example to PR for the graphqlite. |
Beta Was this translation helpful? Give feedback.
After many investigation iterations, I found, that important information bypassed with
GraphQL\Type\Definition\ResolveInfo
(https://github.com/webonyx/graphql-php/blob/0.13.x/docs/reference.md#graphqltypedefinitionresolveinfo).It is pretty complex to inject it into the resolver (and don't break other arguments bypassing) from the middleware perspective, but it just works.
Here's a working example of how to know about operation type in the middleware (example with checking for
query
operation type).