Skip to content

Commit 17bf125

Browse files
committed
Add directives of Apollo Federation v2
1 parent b5d5e48 commit 17bf125

11 files changed

+201
-8
lines changed

src/Directives.php

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,31 @@
55
namespace Apollo\Federation;
66

77
use Apollo\Federation\Directives\ExternalDirective;
8+
use Apollo\Federation\Directives\InaccessibleDirective;
89
use Apollo\Federation\Directives\KeyDirective;
10+
use Apollo\Federation\Directives\OverrideDirective;
911
use Apollo\Federation\Directives\ProvidesDirective;
1012
use Apollo\Federation\Directives\RequiresDirective;
13+
use Apollo\Federation\Directives\ShareableDirective;
1114
use Apollo\Federation\Enum\DirectiveEnum;
12-
use GraphQL\Type\Definition\Directive;
1315

1416
/**
1517
* Helper class to get directives for annotating federated entity types.
1618
*/
1719
class Directives
1820
{
19-
/** @var array<string,Directive> */
20-
private static $directives = null;
21+
/**
22+
* @var array{
23+
* external: ExternalDirective,
24+
* inaccessible: InaccessibleDirective,
25+
* key: KeyDirective,
26+
* override: OverrideDirective,
27+
* requires: RequiresDirective,
28+
* provides: ProvidesDirective,
29+
* shareable: ShareableDirective,
30+
* }|null
31+
*/
32+
private static ?array $directives = null;
2133

2234
/**
2335
* Gets the @key directive.
@@ -35,6 +47,22 @@ public static function external(): ExternalDirective
3547
return self::getDirectives()[DirectiveEnum::EXTERNAL];
3648
}
3749

50+
/**
51+
* Gets the @inaccessible directive.
52+
*/
53+
public static function inaccessible(): InaccessibleDirective
54+
{
55+
return self::getDirectives()[DirectiveEnum::INACCESSIBLE];
56+
}
57+
58+
/**
59+
* Gets the @override directive.
60+
*/
61+
public static function override(): OverrideDirective
62+
{
63+
return self::getDirectives()[DirectiveEnum::OVERRIDE];
64+
}
65+
3866
/**
3967
* Gets the @requires directive.
4068
*/
@@ -51,19 +79,38 @@ public static function provides(): ProvidesDirective
5179
return self::getDirectives()[DirectiveEnum::PROVIDES];
5280
}
5381

82+
/**
83+
* Gets the @shareable directive.
84+
*/
85+
public static function shareable(): ShareableDirective
86+
{
87+
return self::getDirectives()[DirectiveEnum::SHAREABLE];
88+
}
89+
5490
/**
5591
* Gets the directives that can be used on federated entity types.
5692
*
57-
* @return array<string,Directive>
93+
* @return array{
94+
* external: ExternalDirective,
95+
* inaccessible: InaccessibleDirective,
96+
* key: KeyDirective,
97+
* override: OverrideDirective,
98+
* requires: RequiresDirective,
99+
* provides: ProvidesDirective,
100+
* shareable: ShareableDirective,
101+
* }
58102
*/
59103
public static function getDirectives(): array
60104
{
61105
if (!self::$directives) {
62106
self::$directives = [
63107
DirectiveEnum::EXTERNAL => new ExternalDirective(),
108+
DirectiveEnum::INACCESSIBLE => new InaccessibleDirective(),
64109
DirectiveEnum::KEY => new KeyDirective(),
110+
DirectiveEnum::OVERRIDE => new OverrideDirective(),
65111
DirectiveEnum::REQUIRES => new RequiresDirective(),
66112
DirectiveEnum::PROVIDES => new ProvidesDirective(),
113+
DirectiveEnum::SHAREABLE => new ShareableDirective(),
67114
];
68115
}
69116

src/Directives/ExternalDirective.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
* The `@external` directive is used to mark a field as owned by another service. This
1111
* allows service A to use fields from service B while also knowing at runtime the
1212
* types of that field.
13+
*
14+
* @see https://www.apollographql.com/docs/federation/federated-types/federated-directives/#external
1315
*/
1416
class ExternalDirective extends Directive
1517
{
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Apollo\Federation\Directives;
6+
7+
use Apollo\Federation\Enum\DirectiveEnum;
8+
use GraphQL\Language\DirectiveLocation;
9+
use GraphQL\Type\Definition\Directive;
10+
11+
/**
12+
* The `@inaccessible` directive indicates that a field or type should be omitted from the gateway's API schema,
13+
* even if it's also defined in other subgraphs.
14+
*
15+
* @see https://www.apollographql.com/docs/federation/federated-types/federated-directives/#inaccessible
16+
*/
17+
class InaccessibleDirective extends Directive
18+
{
19+
public function __construct()
20+
{
21+
parent::__construct([
22+
'name' => DirectiveEnum::INACCESSIBLE,
23+
'locations' => [
24+
DirectiveLocation::FIELD_DEFINITION,
25+
DirectiveLocation::IFACE,
26+
DirectiveLocation::OBJECT,
27+
DirectiveLocation::UNION,
28+
],
29+
]);
30+
}
31+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Apollo\Federation\Directives;
6+
7+
use Apollo\Federation\Enum\DirectiveEnum;
8+
use GraphQL\Language\DirectiveLocation;
9+
use GraphQL\Type\Definition\Directive;
10+
use GraphQL\Type\Definition\FieldArgument;
11+
use GraphQL\Type\Definition\Type;
12+
13+
/**
14+
* The `@override` directive indicates that a field is now resolved by this subgraph
15+
* instead of another subgraph where it's also defined.
16+
*
17+
* @see https://www.apollographql.com/docs/federation/federated-types/federated-directives/#override
18+
*/
19+
class OverrideDirective extends Directive
20+
{
21+
public function __construct()
22+
{
23+
parent::__construct([
24+
'name' => DirectiveEnum::OVERRIDE,
25+
'locations' => [DirectiveLocation::FIELD_DEFINITION],
26+
'args' => [
27+
new FieldArgument([
28+
'name' => 'from',
29+
'type' => Type::nonNull(Type::string()),
30+
]),
31+
],
32+
]);
33+
}
34+
}

src/Directives/ProvidesDirective.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
/**
1212
* The `@provides` directive is used to annotate the expected returned fieldset from a field
1313
* on a base type that is guaranteed to be selectable by the gateway.
14+
*
15+
* @see https://www.apollographql.com/docs/federation/federated-types/federated-directives/#provides
1416
*/
1517
class ProvidesDirective extends Directive
1618
{

src/Directives/RequiresDirective.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
* The `@requires` directive is used to annotate the required input fieldset from a base type
1313
* for a resolver. It is used to develop a query plan where the required fields may not be
1414
* needed by the client, but the service may need additional information from other services.
15+
*
16+
* @see https://www.apollographql.com/docs/federation/federated-types/federated-directives/#requires
1517
*/
1618
class RequiresDirective extends Directive
1719
{
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Apollo\Federation\Directives;
6+
7+
use Apollo\Federation\Enum\DirectiveEnum;
8+
use GraphQL\Language\DirectiveLocation;
9+
use GraphQL\Type\Definition\Directive;
10+
11+
/**
12+
* The `@shareable` directive indicates that an object type's field is allowed to be resolved
13+
* by multiple subgraphs (by default, each field can be resolved by only one subgraph).
14+
*
15+
* @see https://www.apollographql.com/docs/federation/federated-types/federated-directives/#shareable
16+
*/
17+
class ShareableDirective extends Directive
18+
{
19+
public function __construct()
20+
{
21+
parent::__construct([
22+
'name' => DirectiveEnum::SHAREABLE,
23+
'locations' => [DirectiveLocation::FIELD_DEFINITION, DirectiveLocation::OBJECT],
24+
]);
25+
}
26+
}

src/Enum/DirectiveEnum.php

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,31 @@
77
class DirectiveEnum
88
{
99
public const EXTERNAL = 'external';
10+
public const INACCESSIBLE = 'inaccessible';
1011
public const KEY = 'key';
12+
public const OVERRIDE = 'override';
1113
public const PROVIDES = 'provides';
1214
public const REQUIRES = 'requires';
15+
public const SHAREABLE = 'shareable';
16+
17+
/**
18+
* @var string[]|null
19+
*/
20+
protected static ?array $constants = null;
1321

1422
/**
1523
* @return string[]
1624
*/
1725
public static function getAll(): array
1826
{
19-
return [self::EXTERNAL, self::KEY, self::PROVIDES, self::REQUIRES];
27+
if (null === static::$constants) {
28+
static::$constants = (new \ReflectionClass(static::class))->getConstants();
29+
}
30+
31+
return static::$constants;
2032
}
2133

22-
private function __construct()
34+
protected function __construct()
2335
{
2436
// forbid creation of an object
2537
}

test/DirectivesTest.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,21 @@ public function testExternalDirective(): void
3737
$this->assertEqualsCanonicalizing($expectedLocations, $config['locations']);
3838
}
3939

40+
public function testInaccessibleDirective(): void
41+
{
42+
$config = Directives::inaccessible()->config;
43+
44+
$expectedLocations = [
45+
DirectiveLocation::FIELD_DEFINITION,
46+
DirectiveLocation::IFACE,
47+
DirectiveLocation::OBJECT,
48+
DirectiveLocation::UNION,
49+
];
50+
51+
$this->assertEquals('inaccessible', $config['name']);
52+
$this->assertEqualsCanonicalizing($expectedLocations, $config['locations']);
53+
}
54+
4055
public function testRequiresDirective(): void
4156
{
4257
$config = Directives::requires()->config;
@@ -57,6 +72,16 @@ public function testProvidesDirective(): void
5772
$this->assertEqualsCanonicalizing($expectedLocations, $config['locations']);
5873
}
5974

75+
public function testShareableDirective(): void
76+
{
77+
$config = Directives::shareable()->config;
78+
79+
$expectedLocations = [DirectiveLocation::FIELD_DEFINITION, DirectiveLocation::OBJECT];
80+
81+
$this->assertEquals('shareable', $config['name']);
82+
$this->assertEqualsCanonicalizing($expectedLocations, $config['locations']);
83+
}
84+
6085
public function testItAddsDirectivesToSchema(): void
6186
{
6287
$schema = new Schema([
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
1+
directive @external on FIELD_DEFINITION
2+
3+
directive @inaccessible on FIELD_DEFINITION | INTERFACE | OBJECT | UNION
4+
15
directive @key(fields: String!) on OBJECT | INTERFACE
26

3-
directive @external on FIELD_DEFINITION
7+
directive @override(from: String!) on FIELD_DEFINITION
48

59
directive @requires(fields: String!) on FIELD_DEFINITION
610

711
directive @provides(fields: String!) on FIELD_DEFINITION
812

13+
directive @shareable on FIELD_DEFINITION | OBJECT
14+
915
type Query {
1016
_: String
1117
}

0 commit comments

Comments
 (0)