Skip to content

Commit 3eed5a2

Browse files
committed
Introduce #[Autowire]
To inject services in #[Type] methods.
1 parent 74c145d commit 3eed5a2

30 files changed

+519
-113
lines changed

docs/todo.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ This library is still work in progress, and misses some valuable features:
66
- ~~Allow simple lists (array type)~~
77
- ~~Make AST serializable (cacheable)~~
88
- ~~Handle `DateTime` and `DateTimeImmutable`~~
9+
- ~~Inject autowiring services~~
910
- Connection, edge, nodes (see https://relay.dev/graphql/connections.htm)
1011
- GraphQL interfaces, inheritance
11-
- Inject autowiring services
1212
- Subscriptions

src/Attribute/Autowire.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Jerowork\GraphqlAttributeSchema\Attribute;
6+
7+
use Attribute;
8+
9+
#[Attribute(Attribute::TARGET_PARAMETER)]
10+
final readonly class Autowire
11+
{
12+
/**
13+
* @param string|class-string $service
14+
*/
15+
public function __construct(
16+
public ?string $service = null,
17+
) {}
18+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Jerowork\GraphqlAttributeSchema\Parser\Node\Child;
6+
7+
use Jerowork\GraphqlAttributeSchema\Parser\Node\ArraySerializable;
8+
9+
/**
10+
* @phpstan-type AutowireNodePayload array{
11+
* service: string|class-string,
12+
* propertyName: string,
13+
* }
14+
*
15+
* @implements ArraySerializable<AutowireNodePayload>
16+
*/
17+
final readonly class AutowireNode implements ArraySerializable
18+
{
19+
public function __construct(
20+
public string $service,
21+
public string $propertyName,
22+
) {}
23+
24+
public function toArray(): array
25+
{
26+
return [
27+
'service' => $this->service,
28+
'propertyName' => $this->propertyName,
29+
];
30+
}
31+
32+
public static function fromArray(array $payload): AutowireNode
33+
{
34+
return new self(
35+
$payload['service'],
36+
$payload['propertyName'],
37+
);
38+
}
39+
}

src/Parser/Node/Child/FieldNode.php

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,17 @@
99

1010
/**
1111
* @phpstan-import-type ArgNodePayload from ArgNode
12+
* @phpstan-import-type AutowireNodePayload from AutowireNode
1213
* @phpstan-import-type TypePayload from Type
1314
*
1415
* @phpstan-type FieldNodePayload array{
1516
* type: TypePayload,
1617
* name: string,
1718
* description: null|string,
18-
* argNodes: list<ArgNodePayload>,
19+
* argumentNodes: list<array{
20+
* node: class-string<ArgNode|AutowireNode>,
21+
* payload: ArgNodePayload|AutowireNodePayload
22+
* }>,
1923
* fieldType: string,
2024
* methodName: null|string,
2125
* propertyName: null|string,
@@ -27,13 +31,13 @@
2731
final readonly class FieldNode implements ArraySerializable
2832
{
2933
/**
30-
* @param list<ArgNode> $argNodes
34+
* @param list<ArgNode|AutowireNode> $argumentNodes
3135
*/
3236
public function __construct(
3337
public Type $type,
3438
public string $name,
3539
public ?string $description,
36-
public array $argNodes,
40+
public array $argumentNodes,
3741
public FieldNodeType $fieldType,
3842
public ?string $methodName,
3943
public ?string $propertyName,
@@ -42,11 +46,19 @@ public function __construct(
4246

4347
public function toArray(): array
4448
{
49+
$argumentNodes = [];
50+
foreach ($this->argumentNodes as $argumentNode) {
51+
$argumentNodes[] = [
52+
'node' => $argumentNode::class,
53+
'payload' => $argumentNode->toArray(),
54+
];
55+
}
56+
4557
return [
4658
'type' => $this->type->toArray(),
4759
'name' => $this->name,
4860
'description' => $this->description,
49-
'argNodes' => array_map(fn($argNode) => $argNode->toArray(), $this->argNodes),
61+
'argumentNodes' => $argumentNodes,
5062
'fieldType' => $this->fieldType->value,
5163
'methodName' => $this->methodName,
5264
'propertyName' => $this->propertyName,
@@ -56,11 +68,23 @@ public function toArray(): array
5668

5769
public static function fromArray(array $payload): FieldNode
5870
{
71+
$argumentNodes = [];
72+
foreach ($payload['argumentNodes'] as $argumentNode) {
73+
$argumentPayload = $argumentNode['payload'];
74+
if ($argumentNode['node'] === ArgNode::class) {
75+
/** @var ArgNodePayload $argumentPayload */
76+
$argumentNodes[] = ArgNode::fromArray($argumentPayload);
77+
} else {
78+
/** @var AutowireNodePayload $argumentPayload */
79+
$argumentNodes[] = AutowireNode::fromArray($argumentPayload);
80+
}
81+
}
82+
5983
return new self(
6084
Type::fromArray($payload['type']),
6185
$payload['name'],
6286
$payload['description'],
63-
array_map(fn($argNodePayload) => ArgNode::fromArray($argNodePayload), $payload['argNodes']),
87+
$argumentNodes,
6488
FieldNodeType::from($payload['fieldType']),
6589
$payload['methodName'],
6690
$payload['propertyName'],
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Jerowork\GraphqlAttributeSchema\Parser\NodeParser\Child;
6+
7+
use Jerowork\GraphqlAttributeSchema\Attribute\Arg;
8+
use Jerowork\GraphqlAttributeSchema\Parser\Node\Child\ArgNode;
9+
use Jerowork\GraphqlAttributeSchema\Parser\NodeParser\GetTypeTrait;
10+
use Jerowork\GraphqlAttributeSchema\Parser\NodeParser\ParseException;
11+
use ReflectionParameter;
12+
13+
final readonly class ArgNodeParser
14+
{
15+
use GetTypeTrait;
16+
17+
/**
18+
* @throws ParseException
19+
*/
20+
public function parse(ReflectionParameter $parameter, ?Arg $attribute): ArgNode
21+
{
22+
$type = $this->getType($parameter->getType(), $attribute);
23+
24+
if ($type === null) {
25+
throw ParseException::invalidParameterType($parameter->getName());
26+
}
27+
28+
return new ArgNode(
29+
$type,
30+
$attribute->name ?? $parameter->getName(),
31+
$attribute?->description,
32+
$parameter->getName(),
33+
);
34+
}
35+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Jerowork\GraphqlAttributeSchema\Parser\NodeParser\Child;
6+
7+
use Jerowork\GraphqlAttributeSchema\Attribute\Autowire;
8+
use Jerowork\GraphqlAttributeSchema\Parser\Node\Child\AutowireNode;
9+
use Jerowork\GraphqlAttributeSchema\Parser\NodeParser\ParseException;
10+
use ReflectionParameter;
11+
use ReflectionNamedType;
12+
13+
final readonly class AutowireNodeParser
14+
{
15+
/**
16+
* @throws ParseException
17+
*/
18+
public function parse(ReflectionParameter $parameter, Autowire $attribute): AutowireNode
19+
{
20+
if ($attribute->service !== null) {
21+
return new AutowireNode(
22+
$attribute->service,
23+
$parameter->getName(),
24+
);
25+
}
26+
27+
if (!$parameter->getType() instanceof ReflectionNamedType) {
28+
throw ParseException::invalidAutowiredParameterType($parameter->getName());
29+
}
30+
31+
return new AutowireNode(
32+
$parameter->getType()->getName(),
33+
$parameter->getName(),
34+
);
35+
}
36+
}

src/Parser/NodeParser/Child/ClassFieldNodesParser.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
private const array RESERVED_METHOD_NAMES = ['__construct'];
2323

2424
public function __construct(
25-
private MethodArgNodesParser $methodArgNodesParser,
25+
private MethodArgumentNodesParser $methodArgNodesParser,
2626
) {}
2727

2828
/**

src/Parser/NodeParser/Child/MethodArgNodesParser.php

Lines changed: 0 additions & 57 deletions
This file was deleted.
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Jerowork\GraphqlAttributeSchema\Parser\NodeParser\Child;
6+
7+
use Jerowork\GraphqlAttributeSchema\Attribute\Arg;
8+
use Jerowork\GraphqlAttributeSchema\Attribute\Autowire;
9+
use Jerowork\GraphqlAttributeSchema\Parser\Node\Child\ArgNode;
10+
use Jerowork\GraphqlAttributeSchema\Parser\Node\Child\AutowireNode;
11+
use Jerowork\GraphqlAttributeSchema\Parser\NodeParser\ParseException;
12+
use ReflectionMethod;
13+
use ReflectionParameter;
14+
15+
final readonly class MethodArgumentNodesParser
16+
{
17+
public function __construct(
18+
private AutowireNodeParser $autowireNodeParser,
19+
private ArgNodeParser $argNodeParser,
20+
) {}
21+
22+
/**
23+
* @throws ParseException
24+
*
25+
* @return list<ArgNode|AutowireNode>
26+
*/
27+
public function parse(ReflectionMethod $method, bool $includeAutowireNodes = true): array
28+
{
29+
$argumentNodes = [];
30+
31+
foreach ($method->getParameters() as $parameter) {
32+
if ($includeAutowireNodes) {
33+
$autowireAttribute = $this->getAttribute($parameter, Autowire::class);
34+
35+
if ($autowireAttribute !== null) {
36+
$argumentNodes[] = $this->autowireNodeParser->parse($parameter, $autowireAttribute);
37+
38+
continue;
39+
}
40+
}
41+
42+
$argAttribute = $this->getAttribute($parameter, Arg::class);
43+
44+
$argumentNodes[] = $this->argNodeParser->parse($parameter, $argAttribute);
45+
}
46+
47+
return $argumentNodes;
48+
}
49+
50+
/**
51+
* @template T of object
52+
*
53+
* @param class-string<T> $attributeName
54+
*
55+
* @return T
56+
*/
57+
private function getAttribute(ReflectionParameter $parameter, string $attributeName): ?object
58+
{
59+
$attributes = $parameter->getAttributes($attributeName);
60+
61+
if ($attributes === []) {
62+
return null;
63+
}
64+
65+
return array_pop($attributes)->newInstance();
66+
}
67+
}

src/Parser/NodeParser/MutationNodeParser.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
namespace Jerowork\GraphqlAttributeSchema\Parser\NodeParser;
66

77
use Jerowork\GraphqlAttributeSchema\Attribute\Mutation;
8+
use Jerowork\GraphqlAttributeSchema\Parser\Node\Child\ArgNode;
89
use Jerowork\GraphqlAttributeSchema\Parser\Node\MutationNode;
910
use Jerowork\GraphqlAttributeSchema\Parser\Node\Node;
10-
use Jerowork\GraphqlAttributeSchema\Parser\NodeParser\Child\MethodArgNodesParser;
11+
use Jerowork\GraphqlAttributeSchema\Parser\NodeParser\Child\MethodArgumentNodesParser;
1112
use ReflectionClass;
1213
use Override;
1314

@@ -21,7 +22,7 @@
2122
private const string RESOLVER_SUFFIX = 'Mutation';
2223

2324
public function __construct(
24-
private MethodArgNodesParser $methodArgNodesParser,
25+
private MethodArgumentNodesParser $methodArgumentNodesParser,
2526
) {}
2627

2728
#[Override]
@@ -42,11 +43,14 @@ public function parse(ReflectionClass $class): Node
4243
throw ParseException::invalidReturnType($class->getName(), $method->getName());
4344
}
4445

46+
/** @var list<ArgNode> $argumentNodes */
47+
$argumentNodes = $this->methodArgumentNodesParser->parse($method, false);
48+
4549
return new MutationNode(
4650
$class->getName(),
4751
$this->retrieveNameForResolver($class, $attribute, self::RESOLVER_SUFFIX),
4852
$attribute->getDescription(),
49-
$this->methodArgNodesParser->parse($method),
53+
$argumentNodes,
5054
$type,
5155
$method->getName(),
5256
$attribute->deprecationReason,

0 commit comments

Comments
 (0)