Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How can I use a custom default resolver? #590

Closed
frthjf opened this issue Jan 29, 2019 · 10 comments · Fixed by #690
Closed

How can I use a custom default resolver? #590

frthjf opened this issue Jan 29, 2019 · 10 comments · Fixed by #690
Labels
enhancement A feature or improvement question Request for support or clarification

Comments

@frthjf
Copy link

frthjf commented Jan 29, 2019

Hi, first of all, thanks for this great package! It's amazing work.

I'm integrating Lighthouse with a CMS system and I'm looking for a way to overwrite the defaultResolver for FieldValues. Is it possible to specify a custom default resolver that returns the resolve closure?

I'm using the latest version 3.0 alpha.

@spawnia spawnia added the question Request for support or clarification label Jan 30, 2019
@spawnia
Copy link
Collaborator

spawnia commented Jan 30, 2019

That is not possible at the moment. You can however add a custom directive and place that on you fields.

@frthjf
Copy link
Author

frthjf commented Jan 30, 2019

Thanks for your quick reply. Fair enough. Would you consider adding an Event to enable extension of the default resolver?

@spawnia
Copy link
Collaborator

spawnia commented Jan 30, 2019

I am not sure it is going to be event-based, but it could be useful to have a mechanism of extending this functionality.

Out of interest, what is your specific use case and what do you plan to do in the replaced default resolver?

If we enable such a feature, we have to consider where to make the cut. There are a few "levels" of default resolvers, the priority where Lighthouse looks is something like:

  1. FieldResolver directive present on the field -> use the directive to construct the resolver
  2. Field is a subscription -> get the default resolver for subscriptions
  3. Field sits one of the root types -> get a default resolver based on the parent name
  4. Use the default field resolver from webonyx/graphql-php

At what point specifically would you like to hook in?

@frthjf
Copy link
Author

frthjf commented Jan 30, 2019

Right, to prevent an x-y-problem, here's my use-case: I have an CMS-like graphical editor for the Schema and corrresponding resolvers that creates files like this:

<?php
function resolveHello(...) {
    return 'world';
}
?>
===
type Query {
   hello: String
}

I have a parser that turns these flat-CMS files into a schema string (just like the SchemaSticher@getSchemaString) and corresponding Lighthouse class definitions, e.g.

<?php
namespace Queries;

class Hello() {
     function resolve(...) {
        return 'world';
     }
}

I basically generate the Lighthouse structure from my CMS files and then Lighthouse loads them from that structure. Now, it would be great if I could hook into the resolver construction directly and generate the resolver directly from my CMS files.

I'm able to overwrite the getSchemaString by rebinding the SchemaSourceProvider, however, I can't hook in to overwrite the default resolver.

I believe that concerns point 3 and perhabs 2 (although I currently don't support subscriptions).

Hope that clarifies my requirements.

@spawnia
Copy link
Collaborator

spawnia commented Feb 1, 2019

Interesting stuff! I am going on vacation, so i will only have a chance to look into this in a few weeks.

It seems like you found a reasonable way to resolve your issue, glad about that. Still, we might make cases like yours easier to extend.

Open to suggestions on how exactly we could do this in a way that is:

  • flexible
  • consistent
  • reasonably complex

@frthjf
Copy link
Author

frthjf commented Feb 2, 2019

Thanks for letting me know, there is no rush!

I believe a good spot to extend the default resolving mechanism is FieldFactory@handle. Currently, the handler is looking for directives and uses their resolver or falls back on the default resolver of FieldValue.

If we introduced an event here that receives $fieldValue as argument, one could customise and extend the resolver and effectively introduce a custom default resolver. For example,

if ($fieldResolver = $this->directiveFactory->createFieldResolver($fieldDefinitionNode)) 
{
    $this->fieldValue = $fieldResolver->resolveField($fieldValue);
}
event(new CreatingFieldDefinition($fieldValue));
$resolver = $this->fieldValue->getResolver();

would allow to define a custom resolver

function (&$fieldValue) {
  if (!$fieldValue->hasResolver()) {
         $fieldValue->setResolver(...);
     }
}

but also to extend the existing one returned by the directives or the default resolver.

function(&$fieldValue) {
  $fieldValue->setResolver(function ($root, array $args, $context, ResolveInfo $resolveInfo) use ($fieldValue) {
     // extend resolving behaviour 
     ...
     return $fieldValue->getDefaultResolver()($root, $args, $context, $resolveInfo);
  });
}

Note that this requires to make FieldValue::getDefaultResolver public and the introduction of some FieldValue::hasResolver method to allow to check existing resolvers.

To me that seems like a flexible solution since it is basically a 'global directive' and it works much like a custom directive implementation. In fact, the Event listener class would look like a FieldResolver where handle() would be equivalent to resolveField().

One thing I don't like about this solution is that the use of an Event seems semantically odd, particularly with an argument passed by reference.

Instead it might be cleaner and more logical to implement it directly as a class definition that the user can register in the config file.

@frthjf
Copy link
Author

frthjf commented Mar 2, 2019

@spawnia Let me know if you have some feedback on my proposal

@spawnia
Copy link
Collaborator

spawnia commented Mar 9, 2019

@frthjf as you noted yourself, the semantics of this approach do not feel quite right.

I think we could just extract the logic for getting a default resolver into an interface like this:

<?php

namespace Nuwave\Lighthouse\Support\Contracts;

use Nuwave\Lighthouse\Schema\Values\FieldValue;

interface ProvidesDefaultResolver
{
    public function defaultResolver(FieldValue $fieldValue): \Closure;
}

@frthjf
Copy link
Author

frthjf commented Mar 9, 2019

@spawnia I agree, that would be much simpler!

@spawnia spawnia added the enhancement A feature or improvement label Mar 28, 2019
@frthjf
Copy link
Author

frthjf commented Mar 31, 2019

That's great, thanks @spawnia!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement A feature or improvement question Request for support or clarification
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants