Skip to content

Commit 3c59b26

Browse files
committed
Build Webonyx CustomScalarType for Scalars
1 parent c0c8ebc commit 3c59b26

File tree

10 files changed

+184
-8
lines changed

10 files changed

+184
-8
lines changed

src/Parser/Ast.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Jerowork\GraphqlAttributeSchema\Parser;
66

7+
use Jerowork\GraphqlAttributeSchema\Parser\Node\AliasedNode;
78
use Jerowork\GraphqlAttributeSchema\Parser\Node\ArraySerializable;
89
use Jerowork\GraphqlAttributeSchema\Parser\Node\Node;
910

@@ -49,6 +50,19 @@ public function getNodeByClassName(string $className): ?Node
4950
return $node;
5051
}
5152

53+
// Try to retrieve node by alias
54+
foreach ($this->nodes as $node) {
55+
if (!$node instanceof AliasedNode) {
56+
continue;
57+
}
58+
59+
if ($node->getAlias() !== $className) {
60+
continue;
61+
}
62+
63+
return $node;
64+
}
65+
5266
return null;
5367
}
5468

src/SchemaBuilderFactory.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Jerowork\GraphqlAttributeSchema;
66

77
use Jerowork\GraphqlAttributeSchema\Parser\Node\Node;
8+
use Jerowork\GraphqlAttributeSchema\TypeBuilder\Object\CustomScalarObjectTypeBuilder;
89
use Jerowork\GraphqlAttributeSchema\TypeBuilder\Object\EnumObjectTypeBuilder;
910
use Jerowork\GraphqlAttributeSchema\TypeBuilder\Object\InputTypeObjectTypeBuilder;
1011
use Jerowork\GraphqlAttributeSchema\TypeBuilder\Object\ObjectTypeBuilder;
@@ -27,6 +28,7 @@ public static function create(
2728
new TypeObjectTypeBuilder(
2829
new FieldResolver(),
2930
),
31+
new CustomScalarObjectTypeBuilder(),
3032
];
3133

3234
return new SchemaBuilder(
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Jerowork\GraphqlAttributeSchema\TypeBuilder\Object;
6+
7+
use GraphQL\Language\AST\StringValueNode;
8+
use GraphQL\Type\Definition\CustomScalarType;
9+
use GraphQL\Type\Definition\Type;
10+
use Jerowork\GraphqlAttributeSchema\Parser\Ast;
11+
use Jerowork\GraphqlAttributeSchema\Parser\Node\ScalarNode;
12+
use Jerowork\GraphqlAttributeSchema\Parser\Node\Node;
13+
use Jerowork\GraphqlAttributeSchema\Type\ScalarType;
14+
use Jerowork\GraphqlAttributeSchema\TypeBuilder\TypeBuilder;
15+
16+
/**
17+
* @implements ObjectTypeBuilder<ScalarNode>
18+
*/
19+
final readonly class CustomScalarObjectTypeBuilder implements ObjectTypeBuilder
20+
{
21+
public function supports(Node $node): bool
22+
{
23+
return $node instanceof ScalarNode;
24+
}
25+
26+
public function build(Node $node, TypeBuilder $typeBuilder, Ast $ast): Type
27+
{
28+
/** @var ScalarType<mixed> $scalarType */
29+
$scalarType = $node->className;
30+
31+
// @phpstan-ignore-next-line
32+
return new CustomScalarType([
33+
'name' => $node->name,
34+
'serialize' => fn($value) => $scalarType::serialize($value),
35+
'parseValue' => fn(string $value) => $scalarType::deserialize($value),
36+
'parseLiteral' => fn(StringValueNode $valueNode) => $scalarType::deserialize($valueNode->value),
37+
'description' => $node->description,
38+
]);
39+
}
40+
}

src/TypeBuilder/TypeBuilder.php

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use GraphQL\Type\Definition\NullableType as WebonyxNullableType;
88
use GraphQL\Type\Definition\Type as WebonyxType;
99
use Jerowork\GraphqlAttributeSchema\Parser\Ast;
10+
use Jerowork\GraphqlAttributeSchema\Parser\Node\AliasedNode;
1011
use Jerowork\GraphqlAttributeSchema\Parser\Node\Node;
1112
use Jerowork\GraphqlAttributeSchema\Parser\Node\Type;
1213
use Jerowork\GraphqlAttributeSchema\TypeBuilder\Object\ObjectTypeBuilder;
@@ -82,26 +83,32 @@ private function buildScalar(string $type): WebonyxType&WebonyxNullableType
8283
*/
8384
private function buildObject(string $className, Ast $ast): WebonyxType
8485
{
85-
if (array_key_exists($className, $this->builtTypes)) {
86-
return $this->builtTypes[$className];
87-
}
88-
8986
$node = $ast->getNodeByClassName($className);
9087

9188
if ($node === null) {
9289
throw BuildException::logicError(sprintf('No node found for class: %s', $className));
9390
}
9491

92+
if ($node instanceof AliasedNode && $node->getAlias() !== null) {
93+
$nodeClassName = $node->getAlias();
94+
} else {
95+
$nodeClassName = $node->getClassName();
96+
}
97+
98+
if (array_key_exists($nodeClassName, $this->builtTypes)) {
99+
return $this->builtTypes[$nodeClassName];
100+
}
101+
95102
foreach ($this->objectTypeBuilders as $objectTypeBuilder) {
96103
if (!$objectTypeBuilder->supports($node)) {
97104
continue;
98105
}
99106

100-
$this->builtTypes[$className] = $objectTypeBuilder->build($node, $this, $ast);
107+
$this->builtTypes[$nodeClassName] = $objectTypeBuilder->build($node, $this, $ast);
101108

102-
return $this->builtTypes[$className];
109+
return $this->builtTypes[$nodeClassName];
103110
}
104111

105-
throw BuildException::logicError(sprintf('Invalid object class %s', $className));
112+
throw BuildException::logicError(sprintf('Invalid object class %s', $nodeClassName));
106113
}
107114
}

src/TypeResolver/RootTypeResolver.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use BackedEnum;
88
use Jerowork\GraphqlAttributeSchema\Parser\Ast;
99
use Jerowork\GraphqlAttributeSchema\Parser\Node\Child\ArgNode;
10+
use Jerowork\GraphqlAttributeSchema\Parser\Node\ScalarNode;
1011
use Jerowork\GraphqlAttributeSchema\Parser\Node\EnumNode;
1112
use Jerowork\GraphqlAttributeSchema\Parser\Node\Child\FieldNode;
1213
use Jerowork\GraphqlAttributeSchema\Parser\Node\InputTypeNode;
@@ -57,6 +58,10 @@ public function resolveChild(ArgNode|FieldNode $child, array $args, Ast $ast): m
5758
throw ResolveException::logicError(sprintf('Node not found for typeId %s', $child->type->value));
5859
}
5960

61+
if ($node instanceof ScalarNode) {
62+
return $args[$child->name];
63+
}
64+
6065
if ($node instanceof EnumNode) {
6166
/** @var class-string<BackedEnum> $className */
6267
$className = $node->getClassName();

tests/Doubles/InputType/TestResolvableInputType.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Jerowork\GraphqlAttributeSchema\Attribute\InputType;
99
use Jerowork\GraphqlAttributeSchema\Attribute\Option\ListType;
1010
use Jerowork\GraphqlAttributeSchema\Attribute\Option\ScalarType;
11+
use DateTimeImmutable;
1112

1213
#[InputType]
1314
final readonly class TestResolvableInputType
@@ -20,5 +21,7 @@ public function __construct(
2021
public string $name,
2122
#[Field(type: new ListType(ScalarType::String))]
2223
public array $parentNames,
24+
#[Field]
25+
public DateTimeImmutable $date,
2326
) {}
2427
}

tests/Doubles/Mutation/TestResolvableMutation.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Jerowork\GraphqlAttributeSchema\Test\Doubles\Mutation;
66

7+
use DateTimeImmutable;
78
use Jerowork\GraphqlAttributeSchema\Attribute\Arg;
89
use Jerowork\GraphqlAttributeSchema\Attribute\Mutation;
910
use Jerowork\GraphqlAttributeSchema\Attribute\Option\ListType;
@@ -25,6 +26,7 @@ public function __invoke(
2526
array $userIds,
2627
#[Arg(type: new ListType(TestSmallInputType::class))]
2728
array $smallInputs,
29+
DateTimeImmutable $dateTime,
2830
): string {
2931
return sprintf(
3032
'Mutation has been called with id %s and input with name %s, userIds: %s, parentNames: %s, smallInputs: %s',
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Jerowork\GraphqlAttributeSchema\Test\TypeBuilder\Object;
6+
7+
use GraphQL\Type\Definition\CustomScalarType;
8+
use Jerowork\GraphqlAttributeSchema\Parser\Ast;
9+
use Jerowork\GraphqlAttributeSchema\Parser\Node\ScalarNode;
10+
use Jerowork\GraphqlAttributeSchema\Parser\Node\TypeNode;
11+
use Jerowork\GraphqlAttributeSchema\Test\Doubles\Scalar\TestScalarType;
12+
use Jerowork\GraphqlAttributeSchema\Test\Doubles\Type\TestType;
13+
use Jerowork\GraphqlAttributeSchema\TypeBuilder\TypeBuilder;
14+
use PHPUnit\Framework\TestCase;
15+
use Jerowork\GraphqlAttributeSchema\TypeBuilder\Object\CustomScalarObjectTypeBuilder;
16+
use PHPUnit\Framework\Attributes\Test;
17+
use Override;
18+
use DateTime;
19+
20+
/**
21+
* @internal
22+
*/
23+
final class CustomScalarObjectTypeBuilderTest extends TestCase
24+
{
25+
private CustomScalarObjectTypeBuilder $builder;
26+
27+
#[Override]
28+
protected function setUp(): void
29+
{
30+
parent::setUp();
31+
32+
$this->builder = new CustomScalarObjectTypeBuilder();
33+
}
34+
35+
#[Test]
36+
public function itShouldSupportScalarNodeOnly(): void
37+
{
38+
self::assertTrue($this->builder->supports(new ScalarNode(
39+
TestScalarType::class,
40+
'enum',
41+
null,
42+
DateTime::class,
43+
)));
44+
45+
self::assertFalse($this->builder->supports(new TypeNode(
46+
TestType::class,
47+
'type',
48+
null,
49+
[],
50+
)));
51+
}
52+
53+
#[Test]
54+
public function itShouldBuildCustomScalarType(): void
55+
{
56+
$type = $this->builder->build(
57+
new ScalarNode(
58+
TestScalarType::class,
59+
'TestScalar',
60+
null,
61+
DateTime::class,
62+
),
63+
new TypeBuilder([]),
64+
new Ast(),
65+
);
66+
67+
self::assertEquals(new CustomScalarType([
68+
'name' => 'TestScalar',
69+
'serialize' => fn() => true,
70+
'parseValue' => fn() => true,
71+
'parseLiteral' => fn() => true,
72+
'description' => null,
73+
]), $type);
74+
}
75+
}

tests/TypeResolver/FieldResolverTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use PHPUnit\Framework\Attributes\Test;
2020
use PHPUnit\Framework\TestCase;
2121
use Override;
22+
use DateTimeImmutable;
2223

2324
/**
2425
* @internal
@@ -49,7 +50,7 @@ public function itShouldResolveProperty(): void
4950
null,
5051
), new Ast());
5152

52-
self::assertSame('a name', $type(new TestResolvableInputType('a name', []), []));
53+
self::assertSame('a name', $type(new TestResolvableInputType('a name', [], new DateTimeImmutable()), []));
5354
}
5455

5556
#[Test]

tests/TypeResolver/RootTypeResolverTest.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,20 @@
44

55
namespace Jerowork\GraphqlAttributeSchema\Test\TypeResolver;
66

7+
use DateTimeImmutable;
78
use Jerowork\GraphqlAttributeSchema\Parser\Ast;
89
use Jerowork\GraphqlAttributeSchema\Parser\Node\Child\ArgNode;
910
use Jerowork\GraphqlAttributeSchema\Parser\Node\Child\FieldNode;
1011
use Jerowork\GraphqlAttributeSchema\Parser\Node\Child\FieldNodeType;
1112
use Jerowork\GraphqlAttributeSchema\Parser\Node\InputTypeNode;
1213
use Jerowork\GraphqlAttributeSchema\Parser\Node\MutationNode;
14+
use Jerowork\GraphqlAttributeSchema\Parser\Node\ScalarNode;
1315
use Jerowork\GraphqlAttributeSchema\Parser\Node\Type;
1416
use Jerowork\GraphqlAttributeSchema\Test\Doubles\Container\TestContainer;
1517
use Jerowork\GraphqlAttributeSchema\Test\Doubles\InputType\TestResolvableInputType;
1618
use Jerowork\GraphqlAttributeSchema\Test\Doubles\InputType\TestSmallInputType;
1719
use Jerowork\GraphqlAttributeSchema\Test\Doubles\Mutation\TestResolvableMutation;
20+
use Jerowork\GraphqlAttributeSchema\Type\DateTimeType;
1821
use Jerowork\GraphqlAttributeSchema\TypeResolver\ResolveException;
1922
use Jerowork\GraphqlAttributeSchema\TypeResolver\RootTypeResolver;
2023
use PHPUnit\Framework\Attributes\Test;
@@ -136,6 +139,12 @@ public function itShouldResolve(): void
136139
null,
137140
'smallInputs',
138141
),
142+
new ArgNode(
143+
Type::createObject(DateTimeImmutable::class),
144+
'dateTime',
145+
null,
146+
'dateTime',
147+
),
139148
],
140149
Type::createScalar('string'),
141150
'__invoke',
@@ -167,6 +176,16 @@ public function itShouldResolve(): void
167176
'parentNames',
168177
null,
169178
),
179+
new FieldNode(
180+
Type::createObject(DateTimeImmutable::class),
181+
'date',
182+
null,
183+
[],
184+
FieldNodeType::Property,
185+
null,
186+
'date',
187+
null,
188+
),
170189
],
171190
),
172191
new InputTypeNode(
@@ -186,6 +205,12 @@ public function itShouldResolve(): void
186205
),
187206
],
188207
),
208+
new ScalarNode(
209+
DateTimeType::class,
210+
'DateTime',
211+
null,
212+
DateTimeImmutable::class,
213+
),
189214
),
190215
);
191216

@@ -196,13 +221,15 @@ public function itShouldResolve(): void
196221
'input' => [
197222
'name' => 'Foobar',
198223
'parentNames' => ['John', 'Jane'],
224+
'date' => new DateTimeImmutable('2025-01-05 12:23:00'),
199225
],
200226
'userIds' => ['1', '2', '3'],
201227
'smallInputs' => [
202228
['id' => '4'],
203229
['id' => '5'],
204230
['id' => '6'],
205231
],
232+
'dateTime' => new DateTimeImmutable('2024-12-31 12:00:12'),
206233
]),
207234
);
208235
}

0 commit comments

Comments
 (0)