Repository navigation
Input: Cached type in registry is not the type returned by type mapper #531
Copy link
Copy link
Closed
Description
Hey,
I'm having a weird issue with input types where I get the exception in the title "Cached type in the registry is not the type returned by type mapper".
For some reason, the type stored in the cache is not the same as the one from the type mapper even if the input class they both reference is the same.
The mutation that I try to run only fails when I use variables to run the mutation, If I run the mutation without variables and provide the values directly it all works but this is not possible to do in the frontend since everything is automatically generated and requires the type to be provided.
Failing mutation
mutation CreateFacility($input: FacilityInput!) {
createFacility(input: $input) {
id
}
}Working mutation
mutation CreateFacility {
createFacility(input: {
name: "Facility"
subdomain: "facility"
}) {
id
}
}Input type
#[Input]
class FacilityInput
{
#[Field]
public string $name;
#[Field]
public string $subdomain;
}Controller/Resolver
class FacilityResolver
{
public function __construct(
private readonly CommandBus $commandBus,
) {
}
#[Mutation]
#[Logged]
public function createFacility(
#[InjectUser] Employee $employee,
FacilityInput $input
): Facility {
/** @var Facility $facility */
$facility = $this->commandBus->dispatch(new CreateFacilityCommand(
(string) $employee->organizationId,
$input->name,
$input->subdomain,
));
return $facility;
}
}The cached type
object(TheCodingMachine\GraphQLite\Types\TypeAnnotatedObjectType)[782]
public 'name' => string 'FacilityInput' (length=13)
public 'description' => null
public 'astNode' => null
public 'config' =>
array (size=3)
'name' => string 'FacilityInput' (length=13)
'fields' =>
object(Closure)[775]
virtual 'closure' 'TheCodingMachine\GraphQLite\Types\TypeAnnotatedObjectType::TheCodingMachine\GraphQLite\Types\{closure}'
public 'static' =>
array (size=5)
...
'interfaces' =>
object(Closure)[842]
virtual 'closure' 'TheCodingMachine\GraphQLite\Types\TypeAnnotatedObjectType::TheCodingMachine\GraphQLite\Types\{closure}'
public 'static' =>
array (size=3)
...
public 'extensionASTNodes' =>
array (size=0)
empty
private 'fields' (GraphQL\Type\Definition\TypeWithFields) => null
public 'resolveFieldFn' => null
private 'interfaces' (GraphQL\Type\Definition\ObjectType) => null
private 'interfaceMap' (GraphQL\Type\Definition\ObjectType) => null
private ?string 'status' (TheCodingMachine\GraphQLite\Types\MutableObjectType) => string 'frozen' (length=6)
private array 'fieldsCallables' (TheCodingMachine\GraphQLite\Types\MutableObjectType) =>
array (size=0)
empty
private ?array 'fields' (TheCodingMachine\GraphQLite\Types\MutableObjectType) => null
private ?string 'className' (TheCodingMachine\GraphQLite\Types\MutableObjectType) => string 'App\API\Dashboard\Input\FacilityInput' (length=37)
The type from the type mapper
object(TheCodingMachine\GraphQLite\Types\InputType)[1057]
public 'name' => string 'FacilityInput' (length=13)
public 'description' => null
public 'astNode' => null
public 'config' =>
array (size=3)
'name' => string 'FacilityInput' (length=13)
'description' => null
'fields' =>
object(Closure)[1063]
virtual 'closure' '$this->TheCodingMachine\GraphQLite\Types\{closure}'
public 'static' =>
array (size=4)
...
public 'this' =>
&object(TheCodingMachine\GraphQLite\Types\InputType)[1057]
public 'extensionASTNodes' => null
private 'fields' (GraphQL\Type\Definition\InputObjectType) => null
protected string 'status' => string 'pending' (length=7)
protected array 'fieldsCallables' =>
array (size=0)
empty
protected ?array 'finalFields' => null
private array 'constructorInputFields' =>
array (size=0)
empty
private array 'inputFields' =>
array (size=0)
empty
private string 'className' => string 'App\API\Dashboard\Input\FacilityInput' (length=37)
private ?TheCodingMachine\GraphQLite\Types\InputTypeValidatorInterface 'inputTypeValidator' => null
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
oojacoboo commentedon Nov 26, 2022
@NormySan what cache adapter are you using? I'm not familiar with this error. It looks like you're doing things correctly.
NormySan commentedon Nov 26, 2022
@oojacoboo I'm using a cache pool from Symfony. Only inputs causing this problem so far, all other types seems to be working properly.
oojacoboo commentedon Nov 26, 2022
From the cached type:
From the type mapper:
What's the stack-trace look like? How is the comparison being made?
NormySan commentedon Nov 27, 2022
@oojacoboo This is the stack trace:
https://gist.github.com/NormySan/d505993a1817843f7c23aa2f5a39acc0
The exception is thrown here in the recursive type mapper:
graphqlite/src/Mappers/RecursiveTypeMapper.php
Line 406 in fed9ffe
oojacoboo commentedon Nov 28, 2022
@NormySan do you use this same input type in multiple mutations? Can you confirm that the cached type and the type from the type mapper are effectively the same object? I think the issue here is that it's using
!==instead of!=. Can you test with!=and see if that resolves the issue for you?NormySan commentedon Nov 28, 2022
@oojacoboo Yeah, I'm using the input type in multiple mutations, one is for updating and one is for creating. I tried changing the comparison to just using
!=and I'm getting the same exception.oojacoboo commentedon Nov 28, 2022
@NormySan if you're certain in your testing that it was changed and executed (not cached), and that comparison is failing, can you identify what's different about the two objects and what's causing it to fail that comparison?
NormySan commentedon Nov 28, 2022
@oojacoboo I think it's failing because they are entirely different objects, I'm going to set up a basic reproduction of my codebase, maybe that will help with identifying the underlying issue.
NormySan commentedon Nov 28, 2022
Here is a simple reproduction of the exception in a simple Symfony application.
https://github.com/NormySan/graphqlite-input-error
It does not use the Symfony bundle but instead uses a custom implementation. The GraphQL request is handled by the
App\GraphQL\RequestHandlerclass, this class is created through theApp\GraphQL\RequestHandlerFactory.NormySan commentedon Nov 28, 2022
So far I've narrowed it down to the following:
FacilityInputtype.RecursiveTypeMapperin themapNameToTypemethod.mapNameToTypemethod on the type mapper.TypeAnnotatedObjectTypebeing returned and registered in the type registry.RecursiveTypeMapperto themapClassToInputTypemethod to get the input type.InputTypeobject being created instead by the type mapper which is not the same type of object that was cached earlier on.I'm not sure what the expected type in the cache should be but I'm guessing that it should be the
InputTypeobject and not theTypeAnnotatedObjectTypeobject?oojacoboo commentedon Nov 29, 2022
@NormySan so, I think the issue here is that you're using the same Input type for multiple operations. I'm not going to assume this is an incorrect design decision on your part, although it very well could be. It's mostly recommended to have a unique Input type per operation. I know this is how our API is designed and likely the reason we haven't run into this error - same for most others.
That said, looking at the code, it seems clear that it's doing a direct comparison of a cached version of the Input type and the one currently being processed by the type mapper.
I don't have enough insight or feedback on the overall use of this. But, it seems like a fresh look at this caching logic needs to be had.
I don't have any answers to these questions, but it seems this is the path forward. Then we can assess a refactor of the design and PR.
NormySan commentedon Nov 29, 2022
Tried using two different input types and I'm still getting the same error so must be something else. I did try to set up a very minimal test based on the no-framework instructions and having the same name for the input type worked well in this case so it must be something else in Symfony or my setup causing the problem.
If I can locate the underlying cause and if it's in this package I will try to make a PR.
pvlg commentedon Nov 29, 2022
Similar issue in EnumTypeMapper.php. Need to add
$enumClass = ltrim($enumClass, '\\');inmapByClassName(). Because of the slash does not find the class in the cache.Lappihuan commentedon Nov 29, 2022
@NormySan did you try ommiting the Input suffix on the PHP Class?
Its automatically added and i do remember having issues if i added it by accident.
NormySan commentedon Nov 29, 2022
@Lappihuan That solved it, I would really like my inputs to be named like that though since it creates a lot of ambiguity if the input is named the same as the entities. Going to have a look at what part of the library that might be causing that issue.
14 remaining items
oojacoboo commentedon Dec 18, 2022
#532 has been merged. Please confirm that this issue is now resolved.
apuckey commentedon Jun 15, 2026
apologies for resurrecting an old thread, but i am seeing this on the latest version, only on the very first request, subsequent requests work, i have tried using both filesystem adapter and cache adapter from synfony ( swoole application thats long lived )
when i print this to the screen from RecursiveTypeMapper::mapClassToType where this exception is thrown 'Cached type in registry is not the type returned by type mapper.':
i get this result:
I have zero #[Inputs]
Every time a new version of the application starts, the first request always fails.
oojacoboo commentedon Jun 15, 2026
@apuckey that's strange - so it's the same object, but it's creating two of them in memory on first load. Presumably it's not an issue b/c on subsequent loads, the cache is picked up from whichever one is stored.
apuckey commentedon Jun 15, 2026
Yes it works fine after the first request, but the first request is always an error.
I'll trace it when i have more time later in the day and let you know my findings :)
fix: adopt already-registered type instead of throwing on cold-regist…
test: cover cold-registry duplicate-instance adoption for class/input…
fix: reuse a reentrantly-registered type in TypeGenerator::mapAnnotat…
apuckey commentedon Jun 16, 2026
apologies this might of been incorrect information. I am just debugging exactly whats going on now.
oojacoboo commentedon Jun 16, 2026
@apuckey okay, let me know, if you could, in the PR. Thanks
apuckey commentedon Jun 16, 2026
@oojacoboo Good afternoon,
I think this may be cause by a race condition outside of this library so i apologise for that.
What i was seeing was from local development of a report generator in strict mode so it obviously did 2 introspection queries instantly, these 2 coroutines, in the process of generating types, do file io so swoole would automatically yield to the other coroutine that was also generating the types at the same time.
So in my library now when I am generating the schema on first request I am creating a lock so it only does it once.
Instead of just returning the Schema I am forcing it to build the typemap at the same time the schema gets created.
This seems to have solved both issues so this may not actually be required. I will do some more testing now that I am locking the schema / type creation
oojacoboo commentedon Jun 16, 2026
@apuckey yes, it's a race for sure. But the cache should be the source of truth. If, at the point of type creation, the cache is checked, it should prevent duplicate types. That's what the PR was attempting to resolve. Did you test it with this in mind?
apuckey commentedon Jun 16, 2026
I have tested using this PR and without after locking the schema type creation and it works as expected in both scenarios.
If I lock the schema generation including getting the typemap as above it will block any other requests whilst its creating Cache on the first request, Or I can avoid locks altogether and hook into my OnWorkerStart event to generate it when the worker starts before any requests are served.
Of course using fpm and getting 2 requests at the same time and using file cache might be a bit different, however I think it will still work and the second one should overwrite the cache as the code is not shared between requests unlike swoole.
So I think ultimately it was my fault for not locking and generating the typemap as there must be I/O operations somewhere in the typemap generation that was causing the context switching between coroutines for me.
Maybe its just a documentation thing that we can add to schema creation that in a concurrent environment we need to block whilst it generates the typemap to not cause any issues.
Reuse reentrantly-registered type in mapAnnotatedObject (fixes first-…
oojacoboo commentedon Jun 17, 2026
@apuckey thanks. I've updated the documentation around this.