Skip to content

Commit 8306136

Browse files
committed
Add working test case and example
1 parent 82c120b commit 8306136

2 files changed

Lines changed: 145 additions & 0 deletions

File tree

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
<?php declare(strict_types=1);
2+
3+
/**
4+
* Per-schema scalar override using BuiltInDefinitions.
5+
*
6+
* Run: php examples/06-per-schema-scalar-override/example.php
7+
*
8+
* The key pattern: pass a BuiltInDefinitions instance to SchemaConfig and use
9+
* that same instance's scalar accessors in field definitions — never Type::string()
10+
* or other static singleton accessors.
11+
*/
12+
13+
require_once __DIR__ . '/../../vendor/autoload.php';
14+
15+
use GraphQL\GraphQL;
16+
use GraphQL\Type\BuiltInDefinitions;
17+
use GraphQL\Type\Definition\CustomScalarType;
18+
use GraphQL\Type\Definition\ObjectType;
19+
use GraphQL\Type\Definition\ScalarType;
20+
use GraphQL\Type\Definition\Type;
21+
use GraphQL\Type\Schema;
22+
use GraphQL\Type\SchemaConfig;
23+
24+
// A custom String scalar that trims leading/trailing whitespace on serialization.
25+
$trimmedString = new CustomScalarType([
26+
'name' => Type::STRING,
27+
'serialize' => static fn (mixed $value): string => trim((string) $value),
28+
'parseValue' => static fn (mixed $value): string => trim((string) $value),
29+
'parseLiteral' => static fn (mixed $ast): string => trim($ast->value ?? ''),
30+
]);
31+
32+
$builtInDefs = new BuiltInDefinitions([Type::STRING => $trimmedString]);
33+
34+
/**
35+
* Instance-based type registry that threads BuiltInDefinitions through all type
36+
* definitions.
37+
* Each type accessor that returns a string field must delegate to
38+
* $this->builtInDefs->string() rather than Type::string(), so every field in
39+
* the schema references the same scalar instance that was registered with the
40+
* schema.
41+
*/
42+
final class TypeRegistry
43+
{
44+
/** @var array<string, ObjectType> */
45+
private array $cache = [];
46+
47+
public function __construct(private readonly BuiltInDefinitions $builtInDefs) {}
48+
49+
public function string(): ScalarType
50+
{
51+
return $this->builtInDefs->string();
52+
}
53+
54+
public function user(): ObjectType
55+
{
56+
return $this->cache['User'] ??= new ObjectType([ // @phpstan-ignore missingType.checkedException (static configuration is known to be correct)
57+
'name' => 'User',
58+
'fields' => fn (): array => [
59+
'name' => ['type' => $this->string()],
60+
'email' => ['type' => $this->string()],
61+
],
62+
]);
63+
}
64+
}
65+
66+
$registry = new TypeRegistry($builtInDefs);
67+
68+
$queryType = new ObjectType([
69+
'name' => 'Query',
70+
'fields' => [
71+
'user' => [
72+
'type' => $registry->user(),
73+
'resolve' => static fn (): array => [
74+
'name' => ' Alice ',
75+
'email' => ' alice@example.com ',
76+
],
77+
],
78+
'greeting' => [
79+
'type' => $registry->string(),
80+
'resolve' => static fn (): string => ' hello world ',
81+
],
82+
],
83+
]);
84+
85+
$schema = new Schema(
86+
(new SchemaConfig())
87+
->setQuery($queryType)
88+
->setBuiltInDefinitions($builtInDefs)
89+
);
90+
91+
$schema->assertValid();
92+
93+
$result = GraphQL::executeQuery($schema, '{ greeting user { name email } }');
94+
95+
if ($result->errors !== []) {
96+
foreach ($result->errors as $error) {
97+
echo "Error: {$error->getMessage()}\n";
98+
}
99+
exit(1);
100+
}
101+
102+
$data = $result->toArray()['data'] ?? [];
103+
echo json_encode($data, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT) . "\n";
104+
105+
// Expected output (whitespace trimmed by the custom scalar):
106+
// {
107+
// "greeting": "hello world",
108+
// "user": {
109+
// "name": "Alice",
110+
// "email": "alice@example.com"
111+
// }
112+
// }

tests/Type/BuiltInDefinitionsTest.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,39 @@ public function testTwoSchemasWithDifferentBuiltInDefinitions(): void
9595
);
9696
}
9797

98+
public function testCorrectScalarOverrideWorksEndToEnd(): void
99+
{
100+
$upperString = new CustomScalarType([
101+
'name' => Type::STRING,
102+
'serialize' => static fn ($value) => strtoupper((string) $value),
103+
]);
104+
105+
$builtInDefs = new BuiltInDefinitions([Type::STRING => $upperString]);
106+
107+
$queryType = new ObjectType([
108+
'name' => 'Query',
109+
'fields' => [
110+
'greeting' => [
111+
'type' => $builtInDefs->string(), // use the instance, not the singleton
112+
'resolve' => static fn (): string => 'hello world',
113+
],
114+
],
115+
]);
116+
117+
$schema = new Schema(
118+
(new SchemaConfig())
119+
->setQuery($queryType)
120+
->setBuiltInDefinitions($builtInDefs)
121+
);
122+
123+
$schema->assertValid();
124+
125+
$result = GraphQL::executeQuery($schema, '{ greeting }');
126+
self::assertSame([], $result->errors);
127+
assert(is_array($result->data));
128+
self::assertSame('HELLO WORLD', $result->data['greeting']);
129+
}
130+
98131
/**
99132
* The type isolation case — no scalar overrides, just fresh BuiltInDefinitions instances
100133
* for each schema — already breaks at runtime when setAssumeValid(true) is used to skip

0 commit comments

Comments
 (0)