Skip to content

Fix variable validation and input coercion for per-schema scalar overrides#1876

Merged
spawnia merged 4 commits intomasterfrom
fix-built-in-scalars-comparison
Mar 16, 2026
Merged

Fix variable validation and input coercion for per-schema scalar overrides#1876
spawnia merged 4 commits intomasterfrom
fix-built-in-scalars-comparison

Conversation

@ruudk
Copy link
Collaborator

@ruudk ruudk commented Mar 16, 2026

Per-schema scalar overrides (#1869) allow type loaders to return custom instances for built-in scalars (ID, String, Int, Float, Boolean). This introduced two bugs where the custom instance and the built-in singleton diverge, causing identity checks to fail.

Bug 1: Variable validation rejects valid queries. TypeComparators uses === to compare types. When a variable type is resolved via Schema::getType() (returning the custom instance) but the argument type references the built-in singleton, the identity check fails and VariablesInAllowedPosition reports a spurious error: "Variable "$id" of type "ID!" used in position expecting type "ID!"."

Fix: Add name-based equality in isEqualType() and isTypeSubTypeOf() for built-in scalars, so two ScalarType instances with the same built-in name are considered equal.

Bug 2: Input coercion calls the wrong parseValue(). Value::coerceInputValue() calls parseValue() on the type from field definitions (the built-in singleton), not the custom override registered via the type loader. Custom parsing logic (e.g. decoding a global ID) is silently skipped.

Fix: Pass the Schema into coerceInputValue() and resolve built-in scalars from the schema before calling parseValue(), mirroring the output-side fix already in ReferenceExecutor::completeValue().

Additionally:

  • Add Type::isBuiltInScalar() to centralize the instanceof + name check used across TypeComparators, Value, and ReferenceExecutor.
  • Scope the ReferenceExecutor schema type resolution (from Support per-schema scalar overrides #1869) to built-in scalars only, matching the narrower fix elsewhere.
  • Replace silent fallbacks with assertions when the schema returns an unexpected type for a built-in scalar name.

Fixes #1874
Ref #1869

…rides

Per-schema scalar overrides (#1869) allow type loaders to return custom
instances for built-in scalars (ID, String, Int, Float, Boolean). This
introduced two bugs where the custom instance and the built-in singleton
diverge, causing identity checks to fail.

Bug 1: Variable validation rejects valid queries.
TypeComparators uses === to compare types. When a variable type is
resolved via Schema::getType() (returning the custom instance) but the
argument type references the built-in singleton, the identity check
fails and VariablesInAllowedPosition reports a spurious error:
"Variable "$id" of type "ID!" used in position expecting type "ID!"."

Fix: Add name-based equality in isEqualType() and isTypeSubTypeOf()
for built-in scalars, so two ScalarType instances with the same
built-in name are considered equal.

Bug 2: Input coercion calls the wrong parseValue().
Value::coerceInputValue() calls parseValue() on the type from field
definitions (the built-in singleton), not the custom override registered
via the type loader. Custom parsing logic (e.g. decoding a global ID)
is silently skipped.

Fix: Pass the Schema into coerceInputValue() and resolve built-in
scalars from the schema before calling parseValue(), mirroring the
output-side fix already in ReferenceExecutor::completeValue().

Additionally:
- Add Type::isBuiltInScalar() to centralize the instanceof + name
  check used across TypeComparators, Value, and ReferenceExecutor.
- Scope the ReferenceExecutor schema type resolution (from #1869) to
  built-in scalars only, matching the narrower fix elsewhere.
- Replace silent fallbacks with assertions when the schema returns an
  unexpected type for a built-in scalar name.

Fixes #1874
Ref #1869
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 8 out of 8 changed files in this pull request and generated no comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

@ruudk
Copy link
Collaborator Author

ruudk commented Mar 16, 2026

Alright, all tests pass on our project. I think this looks better now. It explicitly checks for built in scalar types

@spawnia
Copy link
Collaborator

spawnia commented Mar 16, 2026

Works in Lighthouse. Currently running tests in our large company project to see if that works too.

Have you tried any benchmarks? If you use Claude Code by chance, you can use the /bench-compare skill - or just read it to see the approach.

@ruudk
Copy link
Collaborator Author

ruudk commented Mar 16, 2026

@spawnia Please have another look.

@ruudk ruudk requested a review from spawnia March 16, 2026 14:59
@ruudk
Copy link
Collaborator Author

ruudk commented Mar 16, 2026

Everything is within noise range (±1-5% with variance of ±1%). The consistent ~3-5% difference across ALL benchmarks (including unrelated ones like Deferred and Lexer) suggests system load variance between the two runs, not a regression from our changes. The scalar override benchmarks specifically show no meaningful difference.

But feel free to try it yourself.

@spawnia
Copy link
Collaborator

spawnia commented Mar 16, 2026

Perfect. Ran well in our large project too. Want to wait on feedback from #1874 (comment), or merge and release immediately?

@ruudk
Copy link
Collaborator Author

ruudk commented Mar 16, 2026

Given that it's already broken and that we both tested it, I think best to just merge and release.

Is that already automated? 😄

@spawnia spawnia merged commit edf6ad7 into master Mar 16, 2026
22 checks passed
@spawnia spawnia deleted the fix-built-in-scalars-comparison branch March 16, 2026 15:31
@spawnia
Copy link
Collaborator

spawnia commented Mar 16, 2026

Not quite. I am working on the long list of pending mails from this project, will get to it at some point. Thanks for the fix, I released it as https://github.com/webonyx/graphql-php/releases/tag/v15.31.1.

@chrisastley
Copy link

I've just tested this on my Magento 2 store and it is still erroring.

@ruudk
Copy link
Collaborator Author

ruudk commented Mar 16, 2026

Please provide more details.

An error log line from a production system is not enough.

@Vinai
Copy link

Vinai commented Mar 17, 2026

Summary

webonyx/graphql-php v15.31.0+ changes the type resolution order in Schema::getType(), which breaks Magento's GraphQL typeLoader. Every GraphQL query or mutation that returns a scalar field (String, Int, Float, Boolean, ID) fails with:

Config element "String" is not declared in GraphQL schema

This affects all Magento GraphQL functionality (reviews, cart operations via GraphQL, etc.) on new installs or upgrades, since webonyx/graphql-php is required with a version constraint ^15.0 with allows all minor version updates.

Root Cause

In webonyx/graphql-php v15.31.0, Schema::getType($name) changed the resolution order for type lookups:

v15.30.2 (working):

// Built-in scalars checked FIRST
$standardTypes = Type::getStandardTypes();
if (isset($standardTypes[$name])) {
    return $standardTypes[$name];
}
// typeLoader only called for non-scalar types
$type = $this->loadType($name);

v15.31.1 (broken):

// typeLoader called FIRST for ALL types, including scalars
$type = $this->loadType($name);
if ($type !== null) {
    return ...;
}
// Built-in scalars only as fallback
$builtInScalars = Type::builtInScalars();
if (isset($builtInScalars[$name])) {
    return ...;
}

Magento registers a typeLoader via SchemaGenerator::generate() that delegates to TypeRegistry::get()Config::getConfigElement(). This only knows about types declared in .graphqls schema files — it does not handle built-in scalar types (String, Int, etc.). When webonyx now asks the typeLoader for String, Magento throws a LogicException.

How to Reproduce

  1. Install or update a Magento 2.4.x instance with webonyx/graphql-php v15.31.x
  2. Run any GraphQL query that returns a scalar field, e.g.:
    { storeConfig { store_name } }
  3. Observe the 500 error with Config element "String" is not declared in GraphQL schema

Possible Fix: Magento-side

The proper fix would be in Magento's TypeRegistry::get() or SchemaGenerator::generate() to handle built-in scalar types before delegating to Config::getConfigElement(). For example, checking ScalarTypes::isScalarType() early and returning the webonyx scalar instance directly, but the Magento release cycle is counted in months, not days.

Temporary Workaround in Magento

In case someone else from the Magento ecosystem comes across this, pinning the library to <15.31.0 in the root composer.json until the issue is resolved on either side is a pragmatic workaround.

composer require "webonyx/graphql-php:<15.31.0"

@ruudk
Copy link
Collaborator Author

ruudk commented Mar 17, 2026

Would be best to spend those AI tokens on creating a PR in Magento to fix it.

@Vinai
Copy link

Vinai commented Mar 17, 2026

Creating a PR isn't the problem - getting it released will take a while :)

@josefsabl
Copy link

This seems to break the thecodingmachine/graphqlite.

I keep the GraphQLite fixed to version 8.0.0 but I didn't have this sub-dependency fixed to a version. After updating, this got updated from v15.30.2 to v15.31.1 and now GraphQL stopped working and all I receive is Unknown type "String".

I use custom class finder based on https://github.com/nette/robot-loader.

Also reported here: thecodingmachine/graphqlite#785

@josefsabl
Copy link

This seems to break the thecodingmachine/graphqlite.

I keep the GraphQLite fixed to version 8.0.0 but I didn't have this sub-dependency fixed to a version. After updating, this got updated from v15.30.2 to v15.31.1 and now GraphQL stopped working and all I receive is Unknown type "String".

I use custom class finder based on https://github.com/nette/robot-loader.

Also reported here: thecodingmachine/graphqlite#785

Already fixed in the implementing library 👍

thecodingmachine/graphqlite#785 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

6 participants