overblog / GraphQLBundle

This bundle provides tools to build a complete GraphQL API server in your Symfony App.
MIT License
783 stars 221 forks source link

[RFC] resolver implementation #753

Open bartv2 opened 4 years ago

bartv2 commented 4 years ago
Q A
Bug report? no
Feature request? no
BC Break report? no
RFC? yes
Version/Branch 1.0-dev

I was looking how to use an attached query provider and get the parent value into the method. Not sure if that is even possible at the moment, but i found #538, #542 and #708 and i really liked the resolver idea.

I created the expression-function below to try this out. It's still missing a lot of functionality and this should probably be done at a level higher. But it works for simple functions, and is a start for a discussion. I think the 3rd parameter can be extracted from the ResolveInfo object.

With a method like

    public function posts(?string $after, ?int $first, ?string $before, ?int $last): Connection

and a resolver like

@=actionResolver('App\\GraphQL\\Query\\PostsProviders', 'posts', {after: "String", first: "Int", before: "String", last: "Int"})

generates code like

    return $globalVariables->get('container')->get('App\\GraphQL\\Query\\PostsProviders')->posts($args["after"], $args["first"], $args["before"], $args["last"]);

Or you could do this: With a method like below and a relay-connection args builder

    public function posts(User $parentValue, Argument $args): Connection

and a resolver like

@=actionResolver('App\\GraphQL\\Query\\PostsProviders', 'posts', {})

generates code like

    return $globalVariables->get('container')->get('App\\GraphQL\\Query\\PostsProviders')->posts($value, $args);
namespace App\GraphQL\ExpressionLanguage;

use Overblog\GraphQLBundle\ExpressionLanguage\ExpressionFunction;
use Overblog\GraphQLBundle\Definition\ArgumentInterface;

class ActionResolver extends ExpressionFunction
{
    public function __construct()
    {
        parent::__construct(
            'actionResolver',
            function (string $classString, string $methodString, string $argsString = '[]') {
                $className = stripslashes(trim($classString, '"'));
                $method = trim($methodString, '"');
                $args = eval('return '.$argsString.';');
                $reflectionClass = new \ReflectionClass($className);
                $reflectionMethod = $reflectionClass->getMethod($method);
                $arguments = [];
                foreach ($reflectionMethod->getParameters() as $index => $parameter) {
                    $argument = $this->getCallArgument($parameter, $args);
                    if ($argument === null && !$parameter->isOptional()) {
                        throw new \Exception('bla');
                    }
                    $arguments[] = $argument;
                }
                return ($this->generateServiceCall($className, $method, $arguments));
            },
            fn ($_, callable $target, array $args) => dump($target, $args)
        );
    }

    private function getCallArgument(\ReflectionParameter $parameter, array $args): ?string
    {
        if (isset($args[$parameter->getName()])) {
            return '$args["'.$parameter->getName().'"]';
        }
        if (is_a($parameter->getType()->getName(), ArgumentInterface::class, true)) {
            return '$args';
        }
        if ($parameter->getName() === 'parentValue') {
            return '$value';
        }
        return null;
    }

    private function generateServiceCall(string $class, string $method, array $args)
    {
        $serviceId = addslashes($class);
        return "$this->globalVars->get('container')->get('$serviceId')"
            . "->$method(" . implode(', ', $args) . ")";
    }
}
bartv2 commented 4 years ago

@mcg-web @Vincz @akomm @mavimo @murtukov I know you are all busy, but is this something to make a real implementation proposal for or isn't this worth pursuing?

mcg-web commented 4 years ago

Yes @bartv2 sorry for late reply, since #708 rewrite almost completely the way resolvers definitions. It will better to wait until this totally push, to see how we can implement other part depending on this. I'll try to push this before next weekend.