vendor/thecodingmachine/graphqlite/src/QueryField.php line 74

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace TheCodingMachine\GraphQLite;
  4. use GraphQL\Deferred;
  5. use GraphQL\Error\ClientAware;
  6. use GraphQL\Type\Definition\FieldDefinition;
  7. use GraphQL\Type\Definition\ListOfType;
  8. use GraphQL\Type\Definition\NonNull;
  9. use GraphQL\Type\Definition\OutputType;
  10. use GraphQL\Type\Definition\ResolveInfo;
  11. use GraphQL\Type\Definition\Type;
  12. use TheCodingMachine\GraphQLite\Context\ContextInterface;
  13. use TheCodingMachine\GraphQLite\Exceptions\GraphQLAggregateException;
  14. use TheCodingMachine\GraphQLite\Middlewares\MissingAuthorizationException;
  15. use TheCodingMachine\GraphQLite\Middlewares\ResolverInterface;
  16. use TheCodingMachine\GraphQLite\Middlewares\SourceResolverInterface;
  17. use TheCodingMachine\GraphQLite\Parameters\MissingArgumentException;
  18. use TheCodingMachine\GraphQLite\Parameters\ParameterInterface;
  19. use TheCodingMachine\GraphQLite\Parameters\PrefetchDataParameter;
  20. use TheCodingMachine\GraphQLite\Parameters\SourceParameter;
  21. use Webmozart\Assert\Assert;
  22. use function array_unshift;
  23. use function get_class;
  24. use function is_object;
  25. /**
  26.  * A GraphQL field that maps to a PHP method automatically.
  27.  *
  28.  * @internal
  29.  */
  30. class QueryField extends FieldDefinition
  31. {
  32.     /**
  33.      * @param OutputType&Type $type
  34.      * @param array<string, ParameterInterface> $arguments Indexed by argument name.
  35.      * @param ResolverInterface $originalResolver A pointer to the resolver being called (but not wrapped by any field middleware)
  36.      * @param callable $resolver The resolver actually called
  37.      * @param array<string, ParameterInterface> $prefetchArgs Indexed by argument name.
  38.      * @param array<string, mixed> $additionalConfig
  39.      */
  40.     public function __construct(string $nameOutputType $type, array $argumentsResolverInterface $originalResolver, callable $resolver, ?string $comment, ?string $deprecationReason, ?string $prefetchMethodName, array $prefetchArgs, array $additionalConfig = [])
  41.     {
  42.         $config = [
  43.             'name' => $name,
  44.             'type' => $type,
  45.             'args' => InputTypeUtils::getInputTypeArgs($prefetchArgs $arguments),
  46.         ];
  47.         if ($comment) {
  48.             $config['description'] = $comment;
  49.         }
  50.         if ($deprecationReason) {
  51.             $config['deprecationReason'] = $deprecationReason;
  52.         }
  53.         $resolveFn = function ($source, array $args$contextResolveInfo $info) use ($arguments$originalResolver$resolver) {
  54.             if ($originalResolver instanceof SourceResolverInterface) {
  55.                 $originalResolver->setObject($source);
  56.             }
  57.             /*if ($resolve !== null) {
  58.                 $method = $resolve;
  59.             } elseif ($targetMethodOnSource !== null) {
  60.                 $method = [$source, $targetMethodOnSource];
  61.                 Assert::isCallable($method);
  62.             } else {
  63.                 throw new InvalidArgumentException('The QueryField constructor should be passed either a resolve method or a target method on source object.');
  64.             }*/
  65.             $toPassArgs $this->paramsToArguments($arguments$source$args$context$info$resolver);
  66.             $result $resolver(...$toPassArgs);
  67.             try {
  68.                 $this->assertReturnType($result);
  69.             } catch (TypeMismatchRuntimeException $e) {
  70.                 $class $originalResolver->getObject();
  71.                 if (is_object($class)) {
  72.                     $class get_class($class);
  73.                 }
  74.                 $e->addInfo($this->name$originalResolver->toString());
  75.                 throw $e;
  76.             }
  77.             return $result;
  78.         };
  79.         if ($prefetchMethodName === null) {
  80.             $config['resolve'] = $resolveFn;
  81.         } else {
  82.             $config['resolve'] = function ($source, array $args$contextResolveInfo $info) use ($arguments$prefetchArgs$prefetchMethodName$resolveFn$originalResolver) {
  83.                 // The PrefetchBuffer must be tied to the current request execution. The only object we have for this is $context
  84.                 // $context MUST be a ContextInterface
  85.                 if (! $context instanceof ContextInterface) {
  86.                     throw new GraphQLRuntimeException('When using "prefetch", you sure ensure that the GraphQL execution "context" (passed to the GraphQL::executeQuery method) is an instance of \TheCodingMachine\GraphQLite\Context\Context');
  87.                 }
  88.                 $prefetchBuffer $context->getPrefetchBuffer($this);
  89.                 $prefetchBuffer->register($source$args);
  90.                 return new Deferred(function () use ($prefetchBuffer$source$args$context$info$prefetchArgs$prefetchMethodName$arguments$resolveFn$originalResolver) {
  91.                     if (! $prefetchBuffer->hasResult($args)) {
  92.                         if ($originalResolver instanceof SourceResolverInterface) {
  93.                             $originalResolver->setObject($source);
  94.                         }
  95.                         // TODO: originalPrefetchResolver and prefetchResolver needed!!!
  96.                         $prefetchCallable = [$originalResolver->getObject(), $prefetchMethodName];
  97.                         $sources $prefetchBuffer->getObjectsByArguments($args);
  98.                         Assert::isCallable($prefetchCallable);
  99.                         $toPassPrefetchArgs $this->paramsToArguments($prefetchArgs$source$args$context$info$prefetchCallable);
  100.                         array_unshift($toPassPrefetchArgs$sources);
  101.                         Assert::isCallable($prefetchCallable);
  102.                         $prefetchResult $prefetchCallable(...$toPassPrefetchArgs);
  103.                         $prefetchBuffer->storeResult($prefetchResult$args);
  104.                     } else {
  105.                         $prefetchResult $prefetchBuffer->getResult($args);
  106.                     }
  107.                     foreach ($arguments as $argument) {
  108.                         if (! ($argument instanceof PrefetchDataParameter)) {
  109.                             continue;
  110.                         }
  111.                         $argument->setPrefetchedData($prefetchResult);
  112.                     }
  113.                     return $resolveFn($source$args$context$info);
  114.                 });
  115.             };
  116.         }
  117.         $config += $additionalConfig;
  118.         parent::__construct($config);
  119.     }
  120.     /**
  121.      * This method checks the returned value of the resolver to be sure it matches the documented return type.
  122.      * We are sure the returned value is of the correct type... except if the return type is type-hinted as an array.
  123.      * In this case, PHP does nothing for us and we should check the user returned what he documented.
  124.      *
  125.      * @param mixed $result
  126.      */
  127.     private function assertReturnType($result): void
  128.     {
  129.         $type $this->removeNonNull($this->getType());
  130.         if (! $type instanceof ListOfType) {
  131.             return;
  132.         }
  133.         ResolveUtils::assertInnerReturnType($result$type);
  134.     }
  135.     private function removeNonNull(Type $type): Type
  136.     {
  137.         if ($type instanceof NonNull) {
  138.             return $type->getWrappedType();
  139.         }
  140.         return $type;
  141.     }
  142.     /**
  143.      * @param mixed $value A value that will always be returned by this field.
  144.      *
  145.      * @return QueryField
  146.      */
  147.     public static function alwaysReturn(QueryFieldDescriptor $fieldDescriptor$value): self
  148.     {
  149.         $callable = static function () use ($value) {
  150.             return $value;
  151.         };
  152.         $fieldDescriptor->setResolver($callable);
  153.         return self::fromDescriptor($fieldDescriptor);
  154.     }
  155.     /**
  156.      * @param bool $isNotLogged False if the user is logged (and the error is a 403), true if the error is unlogged (the error is a 401)
  157.      *
  158.      * @return QueryField
  159.      */
  160.     public static function unauthorizedError(QueryFieldDescriptor $fieldDescriptorbool $isNotLogged): self
  161.     {
  162.         $callable = static function () use ($isNotLogged): void {
  163.             if ($isNotLogged) {
  164.                 throw MissingAuthorizationException::unauthorized();
  165.             }
  166.             throw MissingAuthorizationException::forbidden();
  167.         };
  168.         $fieldDescriptor->setResolver($callable);
  169.         return self::fromDescriptor($fieldDescriptor);
  170.     }
  171.     private static function fromDescriptor(QueryFieldDescriptor $fieldDescriptor): self
  172.     {
  173.         return new self(
  174.             $fieldDescriptor->getName(),
  175.             $fieldDescriptor->getType(),
  176.             $fieldDescriptor->getParameters(),
  177.             $fieldDescriptor->getOriginalResolver(),
  178.             $fieldDescriptor->getResolver(),
  179.             $fieldDescriptor->getComment(),
  180.             $fieldDescriptor->getDeprecationReason(),
  181.             $fieldDescriptor->getPrefetchMethodName(),
  182.             $fieldDescriptor->getPrefetchParameters()
  183.         );
  184.     }
  185.     public static function fromFieldDescriptor(QueryFieldDescriptor $fieldDescriptor): self
  186.     {
  187.         $arguments $fieldDescriptor->getParameters();
  188.         if ($fieldDescriptor->getPrefetchMethodName() !== null) {
  189.             $arguments = ['__graphqlite_prefectData' => new PrefetchDataParameter()] + $arguments;
  190.         }
  191.         if ($fieldDescriptor->isInjectSource() === true) {
  192.             $arguments = ['__graphqlite_source' => new SourceParameter()] + $arguments;
  193.         }
  194.         $fieldDescriptor->setParameters($arguments);
  195.         return self::fromDescriptor($fieldDescriptor);
  196.     }
  197.     /**
  198.      * Casts parameters array into an array of arguments ready to be passed to the resolver.
  199.      *
  200.      * @param ParameterInterface[] $parameters
  201.      * @param array<string, mixed> $args
  202.      * @param mixed $context
  203.      *
  204.      * @return array<int, mixed>
  205.      */
  206.     private function paramsToArguments(array $parameters, ?object $source, array $args$contextResolveInfo $info, callable $resolve): array
  207.     {
  208.         $toPassArgs = [];
  209.         $exceptions = [];
  210.         foreach ($parameters as $parameter) {
  211.             try {
  212.                 $toPassArgs[] = $parameter->resolve($source$args$context$info);
  213.             } catch (MissingArgumentException $e) {
  214.                 throw MissingArgumentException::wrapWithFieldContext($e$this->name$resolve);
  215.             } catch (ClientAware $e) {
  216.                 $exceptions[] = $e;
  217.             }
  218.         }
  219.         GraphQLAggregateException::throwExceptions($exceptions);
  220.         return $toPassArgs;
  221.     }
  222. }