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

Exception "You must freeze() a MutableObjectType before fetching its fields" gets thrown #308

Open
PhilippSchreiber opened this issue Oct 22, 2020 · 14 comments
Labels
additional info needed Additional information is needed to proceed help wanted Extra attention is needed

Comments

@PhilippSchreiber
Copy link

It gets thrown in MutableTrait in line 82. When I add a print_r there for $this->className I can see that the problem is in "Symfony\Component\Security\Core\User\UserInterface".

Does anyone have a clue what I can do? It only happens when posting to the /graphql endpoint without query / mutation. But this leads to no autocomplete since the schema doesn't get generated. Queries and mutations work as expected.

@moufmouf
Copy link
Member

Hey @PhilippSchreiber ,

Could you explain a bit more in details how I could reproduce this?

From what I understand, you are using GraphQLite with the Symfony GraphQL bundle.
When are you getting this error? In Graphiql when submitting an empty request?

@PhilippSchreiber
Copy link
Author

Hi @moufmouf ,

I try ;)

I'm using the thecodingmachine/graphqlite-bundle ^4.0

The error appears in graphiql just when it tries to generate the schema – so it should be an empty graphql request, correct. All other requests work.

I am available for testing / debugging if you need help.

@oojacoboo
Copy link
Collaborator

oojacoboo commented Nov 10, 2020

Hey @moufmouf. I'm getting this error as well - came here to discuss, actually.

Please see the following stack-trace:

RuntimeException: You must freeze() a MutableObjectType before fetching its fields.

/srv/www/vendor/thecodingmachine/graphqlite/src/Types/MutableTrait.php:83
/srv/www/vendor/webonyx/graphql-php/src/Utils/TypeInfo.php:180
/srv/www/vendor/webonyx/graphql-php/src/Utils/TypeInfo.php:200
/srv/www/vendor/webonyx/graphql-php/src/Utils/TypeInfo.php:200
/srv/www/vendor/webonyx/graphql-php/src/Utils/TypeInfo.php:200
/srv/www/vendor/webonyx/graphql-php/src/Utils/TypeInfo.php:142
/srv/www/vendor/webonyx/graphql-php/src/Utils/TypeInfo.php:200
/srv/www/vendor/webonyx/graphql-php/src/Utils/TypeInfo.php:200
/srv/www/vendor/webonyx/graphql-php/src/Utils/TypeInfo.php:200
/srv/www/vendor/webonyx/graphql-php/src/Utils/TypeInfo.php:200
/srv/www/vendor/webonyx/graphql-php/src/Utils/TypeInfo.php:142
/srv/www/vendor/webonyx/graphql-php/src/Utils/TypeInfo.php:200
/srv/www/vendor/webonyx/graphql-php/src/Type/Schema.php:213
/srv/www/vendor/webonyx/graphql-php/src/Type/Schema.php:199
/srv/www/vendor/webonyx/graphql-php/src/Type/Introspection.php:204
/srv/www/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php:632
/srv/www/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php:555
/srv/www/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php:1247
/srv/www/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php:1201
/srv/www/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php:1160
/srv/www/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php:853
/srv/www/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php:805
/srv/www/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php:722
/srv/www/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php:662
/srv/www/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php:563
/srv/www/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php:1247
/srv/www/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php:257
/srv/www/vendor/webonyx/graphql-php/src/Executor/ReferenceExecutor.php:208
/srv/www/vendor/webonyx/graphql-php/src/Executor/Executor.php:155
/srv/www/vendor/webonyx/graphql-php/src/GraphQL.php:165
/srv/www/vendor/webonyx/graphql-php/src/Server/Helper.php:295

Now, I've started digging, in order to eliminate the exact cause of the issue. I know what's causing it, but not precisely. At least in our case, the issue arose when we had an interface that was defined with an @type() annotation. A class that implemented this interface, and other classes that had @field methods that returned the class, not the interface. Somehow it seems this is causing recursion, which can clearly be seen in the call stack above.

The same error also occurs if you have methods annotated with @field but don't have the same fields defined in the interface.

I don't personally have a preference on how this is resolved. The spec might actually have that answer. But, at any rate, an updated error message is needed to address this situation.

@MattBred
Copy link
Contributor

MattBred commented Jan 21, 2021

@moufmouf I get this same error on 4.0.3 when I have an external User type, and an introspection is ran:

/**
 * @GraphQL\Type(class=User::class)
 * @GraphQL\SourceField(name="id")
 * @GraphQL\SourceField(name="email")
 */
class UserType
{
{"errors":[{"debugMessage":"You must freeze() a MutableObjectType before fetching its fields.",

The exception is called on line 82 in vendor/thecodingmachine/graphqlite/src/Types/MutableTrait.php

    public function getFields(): array
    {
        if ($this->finalFields === null) {
            if ($this->status === MutableInterface::STATUS_PENDING) {
                throw new RuntimeException('You must freeze() a MutableObjectType before fetching its fields.');
            }

It's checking the SymfonyUserInterface and failing.

Any ideas how to get past this?

@MattBred
Copy link
Contributor

MattBred commented Jan 21, 2021

If I comment out the exception, I get this as the next one:

Schema must contain unique named types but contains multiple types named \u0022SymfonyUserInterface\u0022 (see http://webonyx.github.io/graphql-php/type-system/#type-registry).

If I then comment out this in the graphqlite-bundle/DependencyInjection/GraphqliteCompilerPass.php it works:

// $staticTypes[] = SymfonyUserInterfaceType::class;

This is a super ugly workaround but it fixes the issue - adding this to my own Kernel's compiler pass:

// Hack to get around bug of "You must freeze() a MutableObjectType before fetching its fields."
// @see https://github.com/thecodingmachine/graphqlite/issues/308
$staticClassListTypeMapperFactoryDefinition = $container->getDefinition(StaticClassListTypeMapperFactory::class);
foreach ($staticClassListTypeMapperFactoryDefinition->getArguments() as $argumentKey => $argumentVal)
{
	if (is_array($argumentVal))
	{
		foreach ($argumentVal as $key => $val)
		{
			if ($val === SymfonyUserInterfaceType::class)
			{
				unset($argumentVal[$key]);
				$staticClassListTypeMapperFactoryDefinition->setArgument($argumentKey, $argumentVal);
			}
		}
	}
}

@oojacoboo
Copy link
Collaborator

@MattBred did you figure out what was wrong in your case? In my case it was due to a duplicate type being registered.

@oojacoboo oojacoboo added the help wanted Extra attention is needed label Mar 29, 2021
@MattBred
Copy link
Contributor

MattBred commented Mar 29, 2021

@oojacoboo I didn't dive deep enough to resolve the problem in the library - just my fix above.

From what I can remember, declaring an external User type that is based on a User class that implements UserInterface will throw this error on introspection. Something about it conflicting with the internal GraphQLite user type.

@oojacoboo oojacoboo added the additional info needed Additional information is needed to proceed label Mar 29, 2021
@oojacoboo
Copy link
Collaborator

oojacoboo commented May 19, 2021

So, I've run into this issue again. There is, without a doubt, issues with the interface implementation. I went ahead and used a union return type annotation to get around the issue - not happy about it though. I suspect that the issue is related to interfaces having a subset of fields from the type implementing them causing GraphQLite to treat them as separate types (failing comparison), generating a new type "on the fly" and then using the actual interface as well. This causes there to be duplicate types attempting to be mapped.

I'm assuming that the GraphQL spec doesn't require that the fields of an interface be the same as the fields of the type implementing it? @moufmouf any ideas on this? Also, where is this code being handled? The stack traces are virtually impossible to use as it's all webonyx internals. Where are these types being registered to take a look at this logic?

@aszenz
Copy link
Contributor

aszenz commented Jul 1, 2021

Ran into this issue as well, it occurred when I turned off enable_me query and had a manual user account type, interestingly turning the enable_me option on fixed the error even with the custom user account type defined.

@PhilippSchreiber
Copy link
Author

Ran into this issue as well, it occurred when I turned off enable_me query and had a manual user account type, interestingly turning the enable_me option on fixed the error even with the custom user account type defined.

I can confirm this. I set enable_me to on and now it's working.

@oojacoboo
Copy link
Collaborator

enable_me is just some graphqlite-bundle feature for login, logout and me mutations. It has nothing to do with the actual issue here. If that's solving the problem for you, that's great. But, it's coincidental.

@vyaaki
Copy link

vyaaki commented Oct 13, 2023

Same issue.
Some times graphqlite doesn't freeze MutableInterface object. For example, query (realty return "Rent" type, that implements PricePerUnitInterface):

{
  realty(fullId: "id"){
    ... on PricePerUnitInterface{
      price_per_unit{
        price
      }
    }
  }
}

will avoid calling method \TheCodingMachine\GraphQLite\Mappers\RecursiveTypeMapper::mapClassToType, where interface "freezing" happens. BUT! Here's pretty strange way to fix it ;D. After adding child type in query - it doesn't throw any error:

{
  realty(fullId: "id"){
    ... on Rent{
      id
    }
    ... on PricePerUnitInterface{
      price_per_unit{
        price
      }
    }
  }
}

I think, problem is that graphQl can work with interface-types only, if they are resolved as dependencies of parent types.

Another strange way to fix it: declare somewhere query with output type "PricePerUnitInterface"... Still don't understand how it works...

Here are:

  1. Trace for first query (where $this->status === MutableInterface::STATUS_PENDING condition return true and throws exception):
MutableTrait.php:119, TheCodingMachine\GraphQLite\Types\MutableInterfaceType->initializeFields()
MutableTrait.php:77, TheCodingMachine\GraphQLite\Types\MutableInterfaceType->hasField()
OverlappingFieldsCanBeMerged.php:255, GraphQL\Validator\Rules\OverlappingFieldsCanBeMerged->internalCollectFieldsAndFragmentNames()
OverlappingFieldsCanBeMerged.php:280, GraphQL\Validator\Rules\OverlappingFieldsCanBeMerged->internalCollectFieldsAndFragmentNames()
OverlappingFieldsCanBeMerged.php:165, GraphQL\Validator\Rules\OverlappingFieldsCanBeMerged->getFieldsAndFragmentNames()
OverlappingFieldsCanBeMerged.php:96, GraphQL\Validator\Rules\OverlappingFieldsCanBeMerged->findConflictsWithinSelectionSet()
OverlappingFieldsCanBeMerged.php:64, GraphQL\Validator\Rules\OverlappingFieldsCanBeMerged->GraphQL\Validator\Rules\{closure:/home/vyaaki/projects/crm/vendor/webonyx/graphql-php/src/Validator/Rules/OverlappingFieldsCanBeMerged.php:62-77}()
Visitor.php:414, GraphQL\Language\Visitor::GraphQL\Language\{closure:/home/vyaaki/projects/crm/vendor/webonyx/graphql-php/src/Language/Visitor.php:398-428}()
Visitor.php:470, GraphQL\Language\Visitor::GraphQL\Language\{closure:/home/vyaaki/projects/crm/vendor/webonyx/graphql-php/src/Language/Visitor.php:465-482}()
Visitor.php:277, GraphQL\Language\Visitor::visit()
DocumentValidator.php:224, GraphQL\Validator\DocumentValidator::visitUsingRules()
DocumentValidator.php:116, GraphQL\Validator\DocumentValidator::validate()
GraphQL.php:153, GraphQL\GraphQL::promiseToExecute()
GraphQL.php:94, GraphQL\GraphQL::executeQuery()
  1. Trace for second query (till freeze method for "PricePerUnitInterface" type calling):
MutableTrait.php:33, TheCodingMachine\GraphQLite\Types\MutableInterfaceType->freeze()
RecursiveTypeMapper.php:346, TheCodingMachine\GraphQLite\Mappers\RecursiveTypeMapper->mapClassToType()
TypeAnnotatedObjectType.php:72, TheCodingMachine\GraphQLite\Types\TypeAnnotatedObjectType::TheCodingMachine\GraphQLite\Types\{closure:/home/vyaaki/projects/crm/vendor/thecodingmachine/graphqlite/src/Types/TypeAnnotatedObjectType.php:34-85}()
FieldDefinition.php:97, GraphQL\Type\Definition\FieldDefinition::defineFieldMap()
TypeWithFields.php:26, GraphQL\Type\Definition\TypeWithFields->initializeFields()
TypeWithFields.php:77, GraphQL\Type\Definition\TypeWithFields->getFieldNames()
MutableTrait.php:133, TheCodingMachine\GraphQLite\Types\MutableObjectType->initializeFields()
MutableTrait.php:77, TheCodingMachine\GraphQLite\Types\MutableObjectType->hasField()
OverlappingFieldsCanBeMerged.php:255, GraphQL\Validator\Rules\OverlappingFieldsCanBeMerged->internalCollectFieldsAndFragmentNames()
OverlappingFieldsCanBeMerged.php:280, GraphQL\Validator\Rules\OverlappingFieldsCanBeMerged->internalCollectFieldsAndFragmentNames()
OverlappingFieldsCanBeMerged.php:165, GraphQL\Validator\Rules\OverlappingFieldsCanBeMerged->getFieldsAndFragmentNames()
OverlappingFieldsCanBeMerged.php:96, GraphQL\Validator\Rules\OverlappingFieldsCanBeMerged->findConflictsWithinSelectionSet()
OverlappingFieldsCanBeMerged.php:64, GraphQL\Validator\Rules\OverlappingFieldsCanBeMerged->GraphQL\Validator\Rules\{closure:/home/vyaaki/projects/crm/vendor/webonyx/graphql-php/src/Validator/Rules/OverlappingFieldsCanBeMerged.php:62-77}()
Visitor.php:414, GraphQL\Language\Visitor::GraphQL\Language\{closure:/home/vyaaki/projects/crm/vendor/webonyx/graphql-php/src/Language/Visitor.php:398-428}()
Visitor.php:470, GraphQL\Language\Visitor::GraphQL\Language\{closure:/home/vyaaki/projects/crm/vendor/webonyx/graphql-php/src/Language/Visitor.php:465-482}()
Visitor.php:277, GraphQL\Language\Visitor::visit()
DocumentValidator.php:224, GraphQL\Validator\DocumentValidator::visitUsingRules()
DocumentValidator.php:116, GraphQL\Validator\DocumentValidator::validate()
GraphQL.php:153, GraphQL\GraphQL::promiseToExecute()
GraphQL.php:94, GraphQL\GraphQL::executeQuery()

Hope, it will help an invastigation. Sorry for my english.

@josefsabl
Copy link

Another strange way to fix it: declare somewhere query with output type "PricePerUnitInterface"... Still don't understand how it works...

Man, you saved my day! It indeed works but there isn't enough when they are declared SOMEWHERE, rather they had to be in the same class where the problematic query is. I've given them opaque random names and they throw "not implemented" exception.

After adding child type in query - it doesn't throw any error:

This works as well, but I didn't like that too much as it means the hack has to be implemented client-side. Which is not ideal, obviously :-)

@josefsabl
Copy link

Another strange way to fix it: declare somewhere query with output type "PricePerUnitInterface"... Still don't understand how it works...

One more point. If you have a mutation that throws this "freeze" exception, you have to declare a MUTATION with output type "PricePerUnitInterface" in the same class.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
additional info needed Additional information is needed to proceed help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

7 participants