webonyx/graphql-php
A PHP implementation of the GraphQL specification, based on the JavaScript reference implementation. It's fully compliant with the October 2021 spec, with September 2025 compliance in progress, and supports schema building, query execution, and validation.
config/graphql.php).Printer::stripIgnoredCharacters() removes characters that do not change a document’s meaning. Architecturally this is a canonicalization primitive — it enables stable document fingerprints for persisted queries / APQ, edge caching, and allow-listing without pulling in an external parser. It is a small, additive utility and does not alter existing schema or execution behavior.["x"] or {"query":"{ a }","0":"x"}) now raise a RequestError instead of a TypeError. This is an architectural correctness fix: TypeError is a PHP-level fault that escapes the GraphQL error envelope, whereas RequestError is a first-class client-error type that flows through the standard error formatter. It aligns batch handling with the rest of the request-error taxonomy — no schema or execution change.StandardServer or custom middleware.UniqueOperationTypes, QueryComplexity) aligns with Laravel’s validation ecosystem (e.g., Form Requests).FormattedError) integrates with Laravel’s exception handling (App\Exceptions\Handler).stripIgnoredCharacters() can be called once at the point where a query document is ingested (typically a thin controller or middleware) to produce a stable hash key before hitting Redis. No new runtime dependency, no configuration surface — integration cost is a few lines in existing request handling.RequestError response instead of a TypeError that would typically surface as a 500. No integration work required — it simply makes existing batch handling behave correctly.graphql-php's API (e.g., FieldDefinition vs. Lighthouse’s Type).RequestError (client error) instead of a TypeError (server fault).stripIgnoredCharacters() changes whitespace/insignificant-character handling. If hashes generated by a different tool (or a prior ad-hoc preg_replace) are already stored, hashes will not match on cutover. Persisted-query allow-lists require a one-time regeneration or dual-lookup window.Schema Compatibility:
graphql-php’s schema definition?Performance Benchmarks:
graphql-php compare to Lighthouse or GraphQLite in query execution speed and memory usage for Laravel’s typical workloads (e.g., 100+ resolvers)?Tooling Ecosystem:
Long-Term Maintenance:
Security:
>= 15.38.0 to guarantee this hardening rather than backporting it?Batch Support:
StandardServer to Laravel’s HTTP kernel (e.g., app/Http/Kernel.php).GraphQLServiceProvider) to bootstrap the schema, resolvers, and middleware.resolve(fn() => new UserResolver())).Route::graphql()) using middleware for auth/validation.Route::post('/graphql', [GraphQLController::class, 'handle'])
->middleware(['auth:sanctum', 'throttle:60']);
Printer::stripIgnoredCharacters() at document intake to produce the cache key, so cosmetic whitespace differences between clients collapse onto a single entry:
$normalized = \GraphQL\Language\Printer::stripIgnoredCharacters($document);
$key = 'gql:pq:' . hash('xxh128', $normalized);
\TypeError and re-wrap it into a 500 before the server’s error formatter sees it — that would negate the v15.38.1 benefit.TypeError → RequestError shift.graphql-php alongside Lighthouse using different endpoints (e.g., /graphql, /graphql-old).Type definitions to graphql-php’s Schema.$schema = Schema::create([
'query' => new ObjectType([
'name' => 'Query',
'fields' => [
'user' => $userField, // Converted from Lighthouse
],
]),
]);
@resolver directives with graphql-php’s class-based resolvers or callables.$schema->mutate([
'directives' => [
new DirectiveType([
'name' => 'auth',
'locations' => [DirectiveLocation::FIELD_DEFINITION],
'resolve' => fn($next) => $next(), // Custom logic
]),
],
]);
__schema) match between old and new implementations.["x"], {"query":"{ a }","0":"x"}) asserting a RequestError envelope and an appropriate 4xx status.graphql-php's subscription support).^15.38 (rather than the previously implied ^15.12) so the UTF-8 hardening, normalization helper, and batch error fix are guaranteed present across all environments.graphql-php format.graphql-php’s API.QueryPlan and caching.stripIgnoredCharacters() for cache keys; regenerate existing persisted-query hashes during a dual-lookup window.Sentry or Log), watching for the new 4xx batch-error shape.# Generate a migration for schema changes
php artisan graphql:schema-migrate
tests/Feature/GraphQL/UserResolverTest.php).graphql-php for breaking changes (e.g., via GitHub releases or composer update --dry-run).^15.38) to avoid surprises and to hold the current security floor.graphql-php's debug extensions (e.g., extensions.debug) to log query execution.FormattedError to include Laravel-specific context (e.g., user ID, request ID).$server->setErrorFormatter(function ($e, array $context) {
return (new FormattedError($e))
->setExtensions([
'laravel' => [
'user_id' => auth()->id(),
'request_id' => request()->header('X-Request-Id'),
],
]);
});
TypeError from escaping into the 500 path — confirm no controller-level catch-all re-introduces it.QueryComplexity/depth limits and resolver-level N+1 control (e.g., batched loaders).stripIgnoredCharacters() materially improves hit rate for any CDN or Redis layer keyed on query text, since whitespace and comment differences no longer fragment the cache. Expect a measurable drop in cache-key cardinality if clients currently format queries inconsistently.TypeError/500 will now produce a RequestError/4xx. Risk is not correctness but observability and client behavior: alert rules keyed to 5xx will go quiet (good, but expect a monitoring gap), and any client that special-cased the old failure may behave oddly. Low likelihood, but worth a one-line comms note.stripIgnoredCharacters() operates on documents; applying it to non-document strings (e.g., raw JSON variables) is undefined usage. Guard at the call site.^15.12 → ^15.38) and a one-paragraph release note to client teams covering UTF-8 rejection and, if batched requests are in use, the batch error-type change. No architecture review, no stakeholder sign-off, no training required.How can I help you explore Laravel packages today?