Skip to content

Commit 4e8819a

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

31 files changed

+577
-115
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

docs/usage.md

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ The following attributes can be used:
1414
- [#[EnumValue]](#enum)
1515
- [#[Field]](#field)
1616
- [#[Arg]](#arg)
17+
- [#[Autowire]](#autowire)
1718
- [#[Scalar]](#scalar)
1819

1920
See below for more information about each attribute:
@@ -235,7 +236,8 @@ In `#[Type]` and `#[InputType]`, to define fields, the `#[Field]` attribute can
235236
In order to configure any fields this can be set on constructor property (for `#[InputType]` or `#[Type]`) or
236237
on method (for `#[Type]` only).
237238

238-
The advantage to set on methods for `#[Type]` is that the method can have input arguments as well (e.g. filtering).
239+
The advantage to set on methods for `#[Type]` is that the method can have input arguments as well (e.g. filtering,
240+
injected services).
239241

240242
```php
241243
use Jerowork\GraphqlAttributeSchema\Attribute\Field;
@@ -343,8 +345,57 @@ final readonly class YourType
343345
| `description` | Set description of the argument, readable in the GraphQL schema |
344346
| `type` | Set custom return type; it can be:<br/>- A Type (FQCN)<br/>- A `ScalarType` (e.g. `ScalarType::Int`)<br/>- A `ListType` (e.g. `new ListType(ScalarType::Int)`)<br/>- A `NullableType` (e.g. `new NullableType(SomeType::class)`)<br/>- A combination of `ListType` and `NullableType` and a Type FQCN or `ScalarType` <br/>(e.g. `new NullableType(new ListType(ScalarType::String))`) |
345347

348+
### #[Autowire]
349+
350+
`#[Type]` objects are typically modeled like DTO's. They are often not defined in any DI container.
351+
Using other services inside a `#[Type]` is therefore not so easy.
352+
353+
This is where `#[Autowire]` comes into play. `#[Type]` methods defined with `#[Field]` can inject services by parameter
354+
by autowiring, with `#[Autowire]`.
355+
356+
```php
357+
use Jerowork\GraphqlAttributeSchema\Attribute\Autowire;
358+
use Jerowork\GraphqlAttributeSchema\Attribute\Type;
359+
360+
#[Type]
361+
final readonly class YourType
362+
{
363+
public function __construct(
364+
...
365+
) {}
366+
367+
public function getFoobar(
368+
int $filter,
369+
#[Autowire]
370+
SomeService $service,
371+
) {
372+
// .. use injected $service
373+
}
374+
}
375+
```
376+
377+
#### Automatic schema creation
378+
379+
Which service to inject, is automatically defined by the type of the parameter.
380+
This can be overwritten by the option `service`, see options section below.
381+
382+
#### Requirements
383+
384+
Autowired services:
385+
386+
- must be retrievable from the container (`get()`); especially for Symfony users, these should be set to public (e.g.
387+
with `#[Autoconfigure(public: true)]`),
388+
389+
#### Options
390+
391+
| Option | Description |
392+
|-----------|---------------------------------------------------------------------------------|
393+
| `service` | (optional) Set custom service identifier to retrieve from DI Container (PSR-11) |
394+
346395
### #[Scalar]
396+
347397
Webonyx/graphql-php supports 4 native scalar types:
398+
348399
- string
349400
- integer
350401
- boolean
@@ -373,21 +424,26 @@ final readonly class CustomScalar implements ScalarType
373424
}
374425
```
375426

376-
This custom scalar type can then be defined as type with option `type` within other attributes (e.g. `#[Field]`, `#[Mutation]`).
427+
This custom scalar type can then be defined as type with option `type` within other attributes (e.g. `#[Field]`,
428+
`#[Mutation]`).
377429
The `type` option can be omitted when using `alias` in `#[Scalar]`, see options section below.
378430

379431
#### Requirements
432+
380433
Custom scalar types:
434+
381435
- must implement `ScalarType`.
382436

383437
#### Options
438+
384439
| Option | Description |
385440
|---------------|-----------------------------------------------------------------------------------|
386441
| `name` | Set custom name of scalar type (instead of based on class) |
387442
| `description` | Set description of the scalar type, readable in the GraphQL schema |
388443
| `alias` | Map scalar type to another class, which removes the need to use the `type` option |
389444

390445
#### Custom ScalarType: DateTimeImmutable
446+
391447
*GraphQL Attribute Schema* already has a custom scalar type built-in: [DateTimeType](../src/Type/DateTimeType.php).
392448

393449
With this custom type, `DateTimeImmutable` can be used out-of-the-box (without any `type` option definition).

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.

0 commit comments

Comments
 (0)