-
-
Notifications
You must be signed in to change notification settings - Fork 564
nested mutation resolvers #189
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
Comments
I don't think I understand the question. Can you provide some example of what you are trying to achieve? |
Well, I create a mutation with complex types inside, not a flat field set under root types. |
GraphQL is designed with flat mutations at the root. There is some discussion in reference repo and specs about something similar, but nothing like this is supported yet. You may check these repositories for similar questions, maybe you'll find something relevant. But I may be still missing something from the textual description. Can you post GraphQL mutation example of what you are trying to achieve? E.g. mutation {
field1 {
id # I want this to be passed to field2 args
}
field2(id: $id) {
id
}
} |
mutation {
createUser {
name: "user1"
createCategory {
name: "category1"
}
}
} In the end, it will first execute createCategory since I have resolver in CategoryType saving new category, wich return ID:1, then data is passed to createUser resolver with $args['createCategory'] = 1. Ok, I already resolved all this by splittling the root resolver in calls to resolvers I can find for each type, but would be nice if graphql itself can recursively go deep into each type for a resolver and process the data received for it if any. |
Well, technically you can do something similar by utilizing context (e.g. setting some value in the top resolver and then reading from context in the nested resolver). But this is against GraphQL design (again, right now specs declare mutations as flat list, no nesting) - this is reflected in the fact that root-level fields of mutation type are resolved sequentially, but all other fields can be resolved by the server in parallel. See also graphql/graphql-spec#252 and graphql/graphql-js#672 |
This is how I resolved it. //Mutation resolver
public function resolveMutation($val, $args, $context, ResolveInfo $info){
DebugBreak('1@localhost');
foreach ($args as $mutationArgName => $mutationData) {//loop in mutations data
//get the argument object; this data suppoe to represent full object to be saved
$mutationArg = $this->getField($info->fieldName)->getArg($mutationArgName);
$mutationArgType = $mutationArg->getType();
//particular call to final execution code, eg. save, update, etc. Throws error and halt if not callable found
$call = $this->getCallableMutator($info->fieldName, $mutationArgType);
//process inner types first, saving as necessary, then save mutation data altered (eg ID instead full inner object data)
if ($mutationArgType instanceOf BaseInputType){
$args[$mutationArgName] = $mutationArgType->resolveInnerMutation($mutationData);
//call final outer object save here
}else{
//with simple input types, just pass them along to save
}
call_user_func($call, $args[$mutationArgName]);
//return output mutation object
}
} ... in OfferInputType class, extending BaseInputType used to define all mutations arguments like 'createOffer' => [
'name' => 'createOffer',
'type' => Types::get('OfferOutput'),
'args' => [
'offer' => Types::get('OfferInput')
], public function resolveInnerMutation($inputData){
$inputDataResolved = [];
$fields = $this->getFields();
foreach ($fields as $field) {
if(!isset($inputData[$field->name])) continue;//skip where we have no data received
$fieldType = $field->getType();
if($fieldType instanceOf BaseInputType){//loop into objects and execute the nested resolvers
if(!empty($inputData['id'])){
$inputDataResolved[$field->name] = $inputData['id'];//no creation, just use provided id
}else{
if(is_callable($fieldType->config['resolveField'])){
$inputDataResolved[$field->name] = call_user_func($fieldType->config['resolveField'], $inputData[$field->name], $field);
}else{
$inputDataResolved[$field->name] = $inputData[$field->name]; //Don't lose data where there are no resolvers defined
}
}
}elseif($fieldType instanceOf ListOfType){
$fieldType = Type::getNamedType($fieldType);
if($fieldType instanceOf BaseInputType){
foreach ($inputData[$field->name] as $key => $inputItem) {
if(!empty($inputItem['id'])){
$inputDataResolved[$field->name][] = $inputItem['id'];//no creation, just use provided id
}else{
if(is_callable($fieldType->config['resolveField'])){
$inputDataResolved[$field->name][] = call_user_func($fieldType->config['resolveField'], $inputItem, $field);
}else{
$inputDataResolved[$field->name][] = $inputItem;//Don't lose data where there are no resolvers defined
}
}
if(!isset($inputDataResolved[$field->name][$key])) throw new \Exception('Something went wrong processing sub-resolvers; Lost data?');
}
}else{
$inputDataResolved[$field->name] = $inputData[$field->name];
}
}elseif($fieldType instanceOf NonNull){
$fieldType = Type::getNamedType($fieldType);
$inputItem = $inputData[$field->name];
if($fieldType instanceOf BaseInputType){
if(!empty($inputItem['id'])){
$inputDataResolved[$field->name] = $inputItem['id'];//no creation, just use provided id
}else{
if(is_callable($fieldType->config['resolveField'])){
$inputDataResolved[$field->name] = call_user_func($fieldType->config['resolveField'], $inputItem, $field);
}else{
$inputDataResolved[$field->name] = $inputItem;//Don't lose data where there are no resolvers defined
}
}
}else{
//Set alias where available
$inputDataResolved[$field->name] = $inputItem;
}
}else{
//field is basic scalar, doesn't need a mutation
$inputDataResolved[$field->name] = $inputData[$field->name];
}
if(!isset($inputDataResolved[$field->name])) throw new \Exception('Somethingwent wrong processing sub-resolvers; Lost data?');
}
return $inputDataResolved;//Done with this mutation InnerResolvers
} |
So I guess we can close it until some solution emerges in the spec (until then anyone interested can use workarounds listed above). |
Is that part of the specs meanwhile? I can't find anything related but this is a pressing issue for me. |
Hello,
Is there any way to build input objects with own resolvers, so they get executed first then arguments for them get replaced with the resolve return?
Thanks
The text was updated successfully, but these errors were encountered: