Skip to content

Commit ece94ca

Browse files
committed
Make Ast serializable
With a serializable Ast, it is possible to cache the Ast. This will save some parser time (reading attributes and classes with reflection).
1 parent 62e478c commit ece94ca

24 files changed

+861
-7
lines changed

docs/getting_started.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ $container = new YourPsr11Container();
3232
// 1. Create an AST based on your classes
3333
$ast = ParserFactory::create()->parse(__DIR__ . '/Path/To/GraphQL');
3434

35+
// with $ast->toArray(), the AST is cacheable (see Cache section)
36+
3537
// 2. Create the schema configuration
3638
$schema = SchemaBuilderFactory::create($container)->build($ast);
3739

@@ -97,3 +99,28 @@ final readonly class GraphQLServerController
9799
}
98100
}
99101
```
102+
103+
## Caching
104+
To save parsing time (involving reflection of all classes and attributes),
105+
the AST is serializable. This makes the AST cacheable.
106+
107+
```php
108+
use GraphQL\Server\StandardServer;
109+
use GraphQL\Server\ServerConfig;
110+
use Jerowork\GraphqlAttributeSchema\Parser\ParserFactory;
111+
use Jerowork\GraphqlAttributeSchema\SchemaBuilderFactory;
112+
113+
// 1. Create an AST based on your classes
114+
$ast = ParserFactory::create()->parse(__DIR__ . '/Path/To/GraphQL');
115+
116+
// Add to cache
117+
$someCache->set('graphql-attribute-schema.ast', json_encode($ast->toArray(), JSON_THROW_ON_ERROR));
118+
119+
// ...
120+
121+
// Get from cache
122+
$ast = Ast::fromArray(json_decode($someCache->get('graphql-attribute-schema.ast'), true, flags: JSON_THROW_ON_ERROR));
123+
124+
// 2. Create the schema configuration
125+
$schema = SchemaBuilderFactory::create($container)->build($ast);
126+
```

docs/todo.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ This library is still work in progress, and misses some valuable features:
44
- ~~Introduction of `#[Deprecated]` attribute~~
55
- ~~Overwrite type via attributes~~
66
- ~~Allow simple lists (array type)~~
7+
- ~~Make AST serializable (cacheable)~~
78
- Connection, edge, nodes (see https://relay.dev/graphql/connections.htm)
8-
- Make AST serializable (cacheable)
99
- Handle `DateTime` and `DateTimeImmutable`
1010
- GraphQL interfaces, inheritance
1111
- Inject autowiring services

src/Parser/Ast.php

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,18 @@
44

55
namespace Jerowork\GraphqlAttributeSchema\Parser;
66

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

9-
final readonly class Ast
10+
/**
11+
* @implements ArraySerializable<array{
12+
* nodes: list<array{
13+
* node: class-string,
14+
* payload: array<string, mixed>
15+
* }>
16+
* }>
17+
*/
18+
final readonly class Ast implements ArraySerializable
1019
{
1120
/**
1221
* @var list<Node>
@@ -42,4 +51,32 @@ public function getNodeByClassName(string $className): ?Node
4251

4352
return null;
4453
}
54+
55+
public function toArray(): array
56+
{
57+
$nodes = [];
58+
foreach ($this->nodes as $node) {
59+
$nodes[] = [
60+
'node' => $node::class,
61+
'payload' => $node->toArray(),
62+
];
63+
}
64+
65+
return [
66+
'nodes' => $nodes,
67+
];
68+
}
69+
70+
public static function fromArray(array $payload): Ast
71+
{
72+
$nodes = [];
73+
foreach ($payload['nodes'] as $nodePayload) {
74+
/** @var Node $nodeClassName */
75+
$nodeClassName = $nodePayload['node'];
76+
$nodes[] = $nodeClassName::fromArray($nodePayload['payload']);
77+
}
78+
79+
/** @var list<Node> $nodes */
80+
return new self(...$nodes);
81+
}
4582
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Jerowork\GraphqlAttributeSchema\Parser\Node;
6+
7+
/**
8+
* @template T of array
9+
*/
10+
interface ArraySerializable
11+
{
12+
/**
13+
* @return T
14+
*/
15+
public function toArray(): array;
16+
17+
/**
18+
* @param T $payload
19+
*/
20+
public static function fromArray(array $payload): self; // @phpstan-ignore-line
21+
}

src/Parser/Node/Child/ArgNode.php

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,47 @@
44

55
namespace Jerowork\GraphqlAttributeSchema\Parser\Node\Child;
66

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

9-
final readonly class ArgNode
10+
/**
11+
* @phpstan-import-type TypePayload from Type
12+
*
13+
* @phpstan-type ArgNodePayload array{
14+
* type: TypePayload,
15+
* name: string,
16+
* description: null|string,
17+
* propertyName: string
18+
* }
19+
*
20+
* @implements ArraySerializable<ArgNodePayload>
21+
*/
22+
final readonly class ArgNode implements ArraySerializable
1023
{
1124
public function __construct(
1225
public Type $type,
1326
public string $name,
1427
public ?string $description,
1528
public string $propertyName,
1629
) {}
30+
31+
public function toArray(): array
32+
{
33+
return [
34+
'type' => $this->type->toArray(),
35+
'name' => $this->name,
36+
'description' => $this->description,
37+
'propertyName' => $this->propertyName,
38+
];
39+
}
40+
41+
public static function fromArray(array $payload): ArgNode
42+
{
43+
return new self(
44+
Type::fromArray($payload['type']),
45+
$payload['name'],
46+
$payload['description'],
47+
$payload['propertyName'],
48+
);
49+
}
1750
}

src/Parser/Node/Child/FieldNode.php

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,27 @@
44

55
namespace Jerowork\GraphqlAttributeSchema\Parser\Node\Child;
66

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

9-
final readonly class FieldNode
10+
/**
11+
* @phpstan-import-type ArgNodePayload from ArgNode
12+
* @phpstan-import-type TypePayload from Type
13+
*
14+
* @phpstan-type FieldNodePayload array{
15+
* type: TypePayload,
16+
* name: string,
17+
* description: null|string,
18+
* argNodes: list<ArgNodePayload>,
19+
* fieldType: string,
20+
* methodName: null|string,
21+
* propertyName: null|string,
22+
* deprecationReason: null|string
23+
* }
24+
*
25+
* @implements ArraySerializable<FieldNodePayload>
26+
*/
27+
final readonly class FieldNode implements ArraySerializable
1028
{
1129
/**
1230
* @param list<ArgNode> $argNodes
@@ -21,4 +39,32 @@ public function __construct(
2139
public ?string $propertyName,
2240
public ?string $deprecationReason,
2341
) {}
42+
43+
public function toArray(): array
44+
{
45+
return [
46+
'type' => $this->type->toArray(),
47+
'name' => $this->name,
48+
'description' => $this->description,
49+
'argNodes' => array_map(fn($argNode) => $argNode->toArray(), $this->argNodes),
50+
'fieldType' => $this->fieldType->value,
51+
'methodName' => $this->methodName,
52+
'propertyName' => $this->propertyName,
53+
'deprecationReason' => $this->deprecationReason,
54+
];
55+
}
56+
57+
public static function fromArray(array $payload): FieldNode
58+
{
59+
return new self(
60+
Type::fromArray($payload['type']),
61+
$payload['name'],
62+
$payload['description'],
63+
array_map(fn($argNodePayload) => ArgNode::fromArray($argNodePayload), $payload['argNodes']),
64+
FieldNodeType::from($payload['fieldType']),
65+
$payload['methodName'],
66+
$payload['propertyName'],
67+
$payload['deprecationReason'],
68+
);
69+
}
2470
}

src/Parser/Node/EnumNode.php

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

55
namespace Jerowork\GraphqlAttributeSchema\Parser\Node;
66

7+
/**
8+
* @phpstan-import-type EnumValueNodePayload from EnumValueNode
9+
*
10+
* @phpstan-type EnumNodePayload array{
11+
* className: class-string,
12+
* name: string,
13+
* description: null|string,
14+
* cases: list<EnumValueNodePayload>
15+
* }
16+
*/
717
final readonly class EnumNode implements Node
818
{
919
/**
@@ -21,4 +31,30 @@ public function getClassName(): string
2131
{
2232
return $this->className;
2333
}
34+
35+
/**
36+
* @return EnumNodePayload
37+
*/
38+
public function toArray(): array
39+
{
40+
return [
41+
'className' => $this->className,
42+
'name' => $this->name,
43+
'description' => $this->description,
44+
'cases' => array_map(fn($case) => $case->toArray(), $this->cases),
45+
];
46+
}
47+
48+
/**
49+
* @param EnumNodePayload $payload
50+
*/
51+
public static function fromArray(array $payload): EnumNode
52+
{
53+
return new self(
54+
$payload['className'],
55+
$payload['name'],
56+
$payload['description'],
57+
array_map(fn($casePayload) => EnumValueNode::fromArray($casePayload), $payload['cases']),
58+
);
59+
}
2460
}

src/Parser/Node/EnumValueNode.php

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,38 @@
44

55
namespace Jerowork\GraphqlAttributeSchema\Parser\Node;
66

7-
final readonly class EnumValueNode
7+
/**
8+
* @phpstan-type EnumValueNodePayload array{
9+
* value: string,
10+
* description: null|string,
11+
* deprecationReason: null|string
12+
* }
13+
*
14+
* @implements ArraySerializable<EnumValueNodePayload>
15+
*/
16+
final readonly class EnumValueNode implements ArraySerializable
817
{
918
public function __construct(
1019
public string $value,
1120
public ?string $description,
1221
public ?string $deprecationReason,
1322
) {}
23+
24+
public function toArray(): array
25+
{
26+
return [
27+
'value' => $this->value,
28+
'description' => $this->description,
29+
'deprecationReason' => $this->deprecationReason,
30+
];
31+
}
32+
33+
public static function fromArray(array $payload): EnumValueNode
34+
{
35+
return new self(
36+
$payload['value'],
37+
$payload['description'],
38+
$payload['deprecationReason'],
39+
);
40+
}
1441
}

src/Parser/Node/InputTypeNode.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@
66

77
use Jerowork\GraphqlAttributeSchema\Parser\Node\Child\FieldNode;
88

9+
/**
10+
* @phpstan-import-type FieldNodePayload from FieldNode
11+
*
12+
* @phpstan-type InputTypeNodePayload array{
13+
* className: class-string,
14+
* name: string,
15+
* description: null|string,
16+
* fieldNodes: list<FieldNodePayload>
17+
* }
18+
*/
919
final readonly class InputTypeNode implements Node
1020
{
1121
/**
@@ -23,4 +33,30 @@ public function getClassName(): string
2333
{
2434
return $this->className;
2535
}
36+
37+
/**
38+
* @return InputTypeNodePayload
39+
*/
40+
public function toArray(): array
41+
{
42+
return [
43+
'className' => $this->className,
44+
'name' => $this->name,
45+
'description' => $this->description,
46+
'fieldNodes' => array_map(fn($fieldNode) => $fieldNode->toArray(), $this->fieldNodes),
47+
];
48+
}
49+
50+
/**
51+
* @param InputTypeNodePayload $payload
52+
*/
53+
public static function fromArray(array $payload): InputTypeNode
54+
{
55+
return new self(
56+
$payload['className'],
57+
$payload['name'],
58+
$payload['description'],
59+
array_map(fn($fieldNodePayload) => FieldNode::fromArray($fieldNodePayload), $payload['fieldNodes']),
60+
);
61+
}
2662
}

0 commit comments

Comments
 (0)