Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 31b2dc1

Browse files
committedJan 8, 2020
Versioning docs for the 4.0 release
1 parent d003c00 commit 31b2dc1

35 files changed

+4601
-0
lines changed
 
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
---
2+
id: version-4.0-changelog
3+
title: Changelog
4+
sidebar_label: Changelog
5+
original_id: changelog
6+
---
7+
8+
## 4.0
9+
10+
This is a complete refactoring from 3.x. While existing annotations are kept compatible, the internals have completely
11+
changed.
12+
13+
New features:
14+
15+
- You can directly [annotate a PHP interface with `@Type` to make it a GraphQL interface](inheritance-interfaces.md#mapping-interfaces)
16+
- You can autowire services in resolvers, thanks to the new `@Autowire` annotation
17+
- Added [user input validation](validation.md) (using the Symfony Validator or the Laravel validator or a custom `@Assertion` annotation
18+
- Improved security handling:
19+
- Unauthorized access to fields can now generate GraphQL errors (rather that schema errors in GraphQLite v3)
20+
- Added fine-grained security using the `@Security` annotation. A field can now be [marked accessible or not depending on the context](fine-grained-security.md).
21+
For instance, you can restrict access to the field "viewsCount" of the type `BlogPost` only for post that the current user wrote.
22+
- You can now inject the current logged user in any query / mutation / field using the `@InjectUser` annotation
23+
- Performance:
24+
- You can inject the [Webonyx query plan in a parameter from a resolver](query_plan.md)
25+
- You can use the [dataloader pattern to improve performance drastically via the "prefetchMethod" attribute](prefetch_method.md)
26+
- Customizable error handling has been added:
27+
- You can throw [GraphQL errors](error_handling.md) with `TheCodingMachine\GraphQLite\Exceptions\GraphQLException`
28+
- You can specify the HTTP response code to send with a given error, and the errors "extensions" section
29+
- You can throw [many errors in one exception](error_handling.md#many-errors-for-one-exception) with `TheCodingMachine\GraphQLite\Exceptions\GraphQLAggregateException`
30+
- You can map [a given PHP class to several PHP input types](input_types.md#declaring-several-input-types-for-the-same-php-class) (a PHP class can have several `@Factory` annotations)
31+
- You can force input types using `@UseInputType(for="$id", inputType="ID!")`
32+
- You can extend an input types (just like you could extend an output type in v3) using [the new `@Decorate` annotation](extend_input_type.md)
33+
- In a factory, you can [exclude some optional parameters from the GraphQL schema](input_types#ignoring-some-parameters)
34+
35+
36+
Many extension points have been added
37+
38+
- Added a "root type mapper" (useful to map scalar types to PHP types or to add custom annotations related to resolvers)
39+
- Added ["field middlewares"](field_middlewares.md) (useful to add middleware that modify the way GraphQL fields are handled)
40+
- Added a ["parameter type mapper"](argument_resolving.md) (useful to add customize parameter resolution or add custom annotations related to parameters)
41+
42+
New framework specific features:
43+
44+
Symfony:
45+
46+
- The Symfony bundle now provides a "login" and a "logout" mutation (and also a "me" query)
47+
48+
Laravel:
49+
50+
- [Native integration with the Laravel paginator](laravel-package-advanced.md#support-for-pagination) has been added
51+
52+
Internals:
53+
54+
- The `FieldsBuilder` class has been split in many different services (`FieldsBuilder`, `TypeHandler`, and a
55+
chain of *root type mappers*)
56+
- The `FieldsBuilderFactory` class has been completely removed.
57+
- Overall, there is not much in common internally between 4.x and 3.x. 4.x is much more flexible with many more hook points
58+
than 3.x. Try it out!
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
---
2+
id: version-4.0-annotations_reference
3+
title: Annotations reference
4+
sidebar_label: Annotations reference
5+
original_id: annotations_reference
6+
---
7+
8+
## @Query annotation
9+
10+
The `@Query` annotation is used to declare a GraphQL query.
11+
12+
**Applies on**: controller methods.
13+
14+
Attribute | Compulsory | Type | Definition
15+
---------------|------------|------|--------
16+
name | *no* | string | The name of the query. If skipped, the name of the method is used instead.
17+
[outputType](custom_output_types.md) | *no* | string | Forces the GraphQL output type of a query.
18+
19+
## @Mutation annotation
20+
21+
The `@Mutation` annotation is used to declare a GraphQL mutation.
22+
23+
**Applies on**: controller methods.
24+
25+
Attribute | Compulsory | Type | Definition
26+
---------------|------------|------|--------
27+
name | *no* | string | The name of the mutation. If skipped, the name of the method is used instead.
28+
[outputType](custom_output_types.md) | *no* | string | Forces the GraphQL output type of a query.
29+
30+
## @Type annotation
31+
32+
The `@Type` annotation is used to declare a GraphQL object type.
33+
34+
**Applies on**: classes.
35+
36+
Attribute | Compulsory | Type | Definition
37+
---------------|------------|------|--------
38+
class | *no* | string | The targeted class. If no class is passed, the type applies to the current class. The current class is assumed to be an entity. If the "class" attribute is passed, [the class annotated with `@Type` is a service](external_type_declaration.md).
39+
name | *no* | string | The name of the GraphQL type generated. If not passed, the name of the class is used. If the class ends with "Type", the "Type" suffix is removed
40+
default | *no* | bool | Defaults to *true*. Whether the targeted PHP class should be mapped by default to this type.
41+
external | *no* | bool | Whether this is an [external type declaration](external_type_declaration.md) or not. You usually do not need to use this attribute since this value defaults to true if a "class" attribute is set. This is only useful if you are declaring a type with no PHP class mapping using the "name" attribute.
42+
43+
## @ExtendType annotation
44+
45+
The `@ExtendType` annotation is used to add fields to an existing GraphQL object type.
46+
47+
**Applies on**: classes.
48+
49+
Attribute | Compulsory | Type | Definition
50+
---------------|------------|------|--------
51+
class | see below | string | The targeted class. [The class annotated with `@ExtendType` is a service](extend_type.md).
52+
name | see below | string | The targeted GraphQL output type.
53+
54+
One and only one of "class" and "name" parameter can be passed at the same time.
55+
56+
## @Field annotation
57+
58+
The `@Field` annotation is used to declare a GraphQL field.
59+
60+
**Applies on**: methods of classes annotated with `@Type` or `@ExtendType`.
61+
62+
Attribute | Compulsory | Type | Definition
63+
---------------|------------|------|--------
64+
name | *no* | string | The name of the field. If skipped, the name of the method is used instead.
65+
[outputType](custom_output_types.md) | *no* | string | Forces the GraphQL output type of a query.
66+
67+
## @SourceField annotation
68+
69+
The `@SourceField` annotation is used to declare a GraphQL field.
70+
71+
**Applies on**: classes annotated with `@Type` or `@ExtendType`.
72+
73+
Attribute | Compulsory | Type | Definition
74+
---------------|------------|------|--------
75+
name | *yes* | string | The name of the field.
76+
[outputType](custom_output_types.md) | *no* | string | Forces the GraphQL output type of the field. Otherwise, return type is used.
77+
phpType | *no* | string | The PHP type of the field (as you would write it in a Docblock)
78+
annotations | *no* | array<Annotations> | A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here.
79+
80+
**Note**: `outputType` and `phpType` are mutually exclusive.
81+
82+
## @MagicField annotation
83+
84+
The `@MagicField` annotation is used to declare a GraphQL field that originates from a PHP magic property (using `__get` magic method).
85+
86+
**Applies on**: classes annotated with `@Type` or `@ExtendType`.
87+
88+
Attribute | Compulsory | Type | Definition
89+
---------------|------------|------|--------
90+
name | *yes* | string | The name of the field.
91+
[outputType](custom_output_types.md) | *no*(*) | string | The GraphQL output type of the field.
92+
phpType | *no*(*) | string | The PHP type of the field (as you would write it in a Docblock)
93+
annotations | *no* | array<Annotations> | A set of annotations that apply to this field. You would typically used a "@Logged" or "@Right" annotation here.
94+
95+
(*) **Note**: `outputType` and `phpType` are mutually exclusive. You MUST provide one of them.
96+
97+
## @Logged annotation
98+
99+
The `@Logged` annotation is used to declare a Query/Mutation/Field is only visible to logged users.
100+
101+
**Applies on**: methods annotated with `@Query`, `@Mutation` or `@Field`.
102+
103+
This annotation allows no attributes.
104+
105+
## @Right annotation
106+
107+
The `@Right` annotation is used to declare a Query/Mutation/Field is only visible to users with a specific right.
108+
109+
**Applies on**: methods annotated with `@Query`, `@Mutation` or `@Field`.
110+
111+
Attribute | Compulsory | Type | Definition
112+
---------------|------------|------|--------
113+
name | *yes* | string | The name of the right.
114+
115+
## @FailWith annotation
116+
117+
The `@FailWith` annotation is used to declare a default value to return in the user is not authorized to see a specific
118+
query / mutation / field (according to the `@Logged` and `@Right` annotations).
119+
120+
**Applies on**: methods annotated with `@Query`, `@Mutation` or `@Field` and one of `@Logged` or `@Right` annotations.
121+
122+
Attribute | Compulsory | Type | Definition
123+
---------------|------------|------|--------
124+
*default* | *yes* | mixed | The value to return if the user is not authorized.
125+
126+
## @HideIfUnauthorized annotation
127+
128+
The `@HideIfUnauthorized` annotation is used to completely hide the query / mutation / field if the user is not authorized
129+
to access it (according to the `@Logged` and `@Right` annotations).
130+
131+
**Applies on**: methods annotated with `@Query`, `@Mutation` or `@Field` and one of `@Logged` or `@Right` annotations.
132+
133+
`@HideIfUnauthorized` and `@FailWith` are mutually exclusive.
134+
135+
## @InjectUser annotation
136+
137+
Use the `@InjectUser` annotation to inject an instance of the current user logged in into a parameter of your
138+
query / mutation / field.
139+
140+
**Applies on**: methods annotated with `@Query`, `@Mutation` or `@Field`.
141+
142+
Attribute | Compulsory | Type | Definition
143+
---------------|------------|--------|--------
144+
*for* | *yes* | string | The name of the PHP parameter
145+
146+
## @Security annotation
147+
148+
The `@Security` annotation can be used to check fin-grained access rights.
149+
It is very flexible: it allows you to pass an expression that can contains custom logic.
150+
151+
See [the fine grained security page](fine-grained-security.md) for more details.
152+
153+
**Applies on**: methods annotated with `@Query`, `@Mutation` or `@Field`.
154+
155+
Attribute | Compulsory | Type | Definition
156+
---------------|------------|--------|--------
157+
*default* | *yes* | string | The security expression
158+
159+
## @Factory annotation
160+
161+
The `@Factory` annotation is used to declare a factory that turns GraphQL input types into objects.
162+
163+
**Applies on**: methods from classes in the "types" namespace.
164+
165+
Attribute | Compulsory | Type | Definition
166+
---------------|------------|------|--------
167+
name | *no* | string | The name of the input type. If skipped, the name of class returned by the factory is used instead.
168+
default | *no* | bool | If `true`, this factory will be used by default for its PHP return type. If set to `false`, you must explicitly [reference this factory using the `@Parameter` annotation](http://localhost:3000/docs/input-types#declaring-several-input-types-for-the-same-php-class).
169+
170+
## @UseInputType annotation
171+
172+
Used to override the GraphQL input type of a PHP parameter.
173+
174+
**Applies on**: methods annotated with `@Query`, `@Mutation` or `@Field` annotation.
175+
176+
Attribute | Compulsory | Type | Definition
177+
---------------|------------|------|--------
178+
*for* | *yes* | string | The name of the PHP parameter
179+
*inputType* | *yes* | string | The GraphQL input type to force for this input field
180+
181+
## @Decorate annotation
182+
183+
The `@Decorate` annotation is used [to extend/modify/decorate an input type declared with the `@Factory` annotation](extend_input_type.md).
184+
185+
**Applies on**: methods from classes in the "types" namespace.
186+
187+
Attribute | Compulsory | Type | Definition
188+
---------------|------------|------|--------
189+
name | *yes* | string | The GraphQL input type name extended by this decorator.
190+
191+
## @Autowire annotation
192+
193+
[Resolves a PHP parameter from the container](autowiring.md).
194+
195+
Useful to inject services directly into `@Field` method arguments.
196+
197+
**Applies on**: methods annotated with `@Query`, `@Mutation` or `@Field` annotation.
198+
199+
Attribute | Compulsory | Type | Definition
200+
---------------|------------|------|--------
201+
*for* | *yes* | string | The name of the PHP parameter
202+
*identifier* | *no* | string | The identifier of the service to fetch. This is optional. Please avoid using this attribute as this leads to a "service locator" anti-pattern.
203+
204+
## @HideParameter annotation
205+
206+
Removes [an argument from the GraphQL schema](input_types.md#ignoring_some_parameters).
207+
208+
Attribute | Compulsory | Type | Definition
209+
---------------|------------|------|--------
210+
*for* | *yes* | string | The name of the PHP parameter to hide
211+
212+
213+
## @Validate annotation
214+
215+
<div class="alert alert-info">This annotation is only available in the GraphQLite Laravel package</div>
216+
217+
[Validates a user input in Laravel](laravel-package-advanced.md).
218+
219+
**Applies on**: methods annotated with `@Query`, `@Mutation`, `@Field`, `@Factory` or `@Decorator` annotation.
220+
221+
Attribute | Compulsory | Type | Definition
222+
---------------|------------|------|--------
223+
*for* | *yes* | string | The name of the PHP parameter
224+
*rule* | *yes | string | Laravel validation rules
225+
226+
Sample:
227+
228+
```
229+
@Validate(for="$email", rule="email|unique:users")
230+
```
231+
232+
## @Assertion annotation
233+
234+
[Validates a user input](validation.md).
235+
236+
The `@Assertion` annotation is available in the *thecodingmachine/graphqlite-symfony-validator-bridge* third party package.
237+
It is available out of the box if you use the Symfony bundle.
238+
239+
**Applies on**: methods annotated with `@Query`, `@Mutation`, `@Field`, `@Factory` or `@Decorator` annotation.
240+
241+
Attribute | Compulsory | Type | Definition
242+
---------------|------------|------|--------
243+
*for* | *yes* | string | The name of the PHP parameter
244+
*constraint* | *yes | annotation | One (or many) Symfony validation annotations.
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
---
2+
id: version-4.0-argument-resolving
3+
title: Extending argument resolving
4+
sidebar_label: Custom argument resolving
5+
original_id: argument-resolving
6+
---
7+
<small>Available in GraphQLite 4.0+</small>
8+
9+
Using a **parameter middleware**, you can hook into the argument resolution of field/query/mutation/factory.
10+
11+
<div class="alert alert-info">Use a parameter middleware if you want to alter the way arguments are injected in a method
12+
or if you want to alter the way input types are imported (for instance if you want to add a validation step)</div>
13+
14+
As an example, GraphQLite uses *parameter middlewares* internally to:
15+
16+
- Inject the Webonyx GraphQL resolution object when you type-hint on the `ResolveInfo` object. For instance:
17+
```php
18+
/**
19+
* @Query
20+
* @return Product[]
21+
*/
22+
public function products(ResolveInfo $info): array
23+
```
24+
In the query above, the `$info` argument is filled with the Webonyx `ResolveInfo` class thanks to the
25+
[`ResolveInfoParameterHandler parameter middleware`](https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php)
26+
- Inject a service from the container when you use the `@Autowire` annotation
27+
- Perform validation with the `@Validate` annotation (in Laravel package)
28+
29+
<!-- https://docs.google.com/drawings/d/10zHfWdbvEab6_dyQBcM68_I_bXQkZtO5ePqt4jdDlk8/edit?usp=sharing -->
30+
31+
**Parameter middlewares**
32+
33+
![](assets/parameter_middleware.svg)
34+
35+
Each middleware is passed number of objects describing the parameter:
36+
37+
- a PHP `ReflectionParameter` object representing the parameter being manipulated
38+
- a `phpDocumentor\Reflection\DocBlock` instance (useful to analyze the `@param` comment if any)
39+
- a `phpDocumentor\Reflection\Type` instance (useful to analyze the type if the argument)
40+
- a `TheCodingMachine\GraphQLite\Annotations\ParameterAnnotations` instance. This is a collection of all custom annotations that apply to this specific argument (more on that later)
41+
- a `$next` handler to pass the argument resolving to the next middleware.
42+
43+
Parameter resolution is done in 2 passes.
44+
45+
On the first pass, middlewares are traversed. They must return a `TheCodingMachine\GraphQLite\Parameters\ParameterInterface` (an object that does the actual resolving).
46+
47+
```php
48+
interface ParameterMiddlewareInterface
49+
{
50+
public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface;
51+
}
52+
```
53+
54+
Then, resolution actually happen by executing the resolver (this is the second pass).
55+
56+
## Annotations parsing
57+
58+
If you plan to use annotations while resolving arguments, your annotation should extend the [`ParameterAnnotationInterface`](https://github.com/thecodingmachine/graphqlite/blob/master/src/Annotations/ParameterAnnotationInterface.php)
59+
60+
For instance, if we want GraphQLite to inject a service in an argument, we can use `@Autowire(for="myService")`.
61+
62+
The annotation looks like this:
63+
64+
```php
65+
/**
66+
* Use this annotation to autowire a service from the container into a given parameter of a field/query/mutation.
67+
*
68+
* @Annotation
69+
*/
70+
class Autowire implements ParameterAnnotationInterface
71+
{
72+
/**
73+
* @var string
74+
*/
75+
public $for;
76+
77+
/**
78+
* The getTarget method must return the name of the argument
79+
*/
80+
public function getTarget(): string
81+
{
82+
return $this->for;
83+
}
84+
}
85+
```
86+
87+
## Writing the parameter middleware
88+
89+
The middleware purpose is to analyze a parameter and decide whether or not it can handle it.
90+
91+
**Parameter middleware class**
92+
```php
93+
class ContainerParameterHandler implements ParameterMiddlewareInterface
94+
{
95+
/** @var ContainerInterface */
96+
private $container;
97+
98+
public function __construct(ContainerInterface $container)
99+
{
100+
$this->container = $container;
101+
}
102+
103+
public function mapParameter(ReflectionParameter $parameter, DocBlock $docBlock, ?Type $paramTagType, ParameterAnnotations $parameterAnnotations, ParameterHandlerInterface $next): ParameterInterface
104+
{
105+
// The $parameterAnnotations object can be used to fetch any annotation implementing ParameterAnnotationInterface
106+
$autowire = $parameterAnnotations->getAnnotationByType(Autowire::class);
107+
108+
if ($autowire === null) {
109+
// If there are no annotation, this middleware cannot handle the parameter. Let's ask
110+
// the next middleware in the chain (using the $next object)
111+
return $next->mapParameter($parameter, $docBlock, $paramTagType, $parameterAnnotations);
112+
}
113+
114+
// We found a @Autowire annotation, let's return a parameter resolver.
115+
return new ContainerParameter($this->container, $parameter->getType());
116+
}
117+
}
118+
```
119+
120+
The last step is to write the actual parameter resolver.
121+
122+
**Parameter resolver class**
123+
```php
124+
/**
125+
* A parameter filled from the container.
126+
*/
127+
class ContainerParameter implements ParameterInterface
128+
{
129+
/** @var ContainerInterface */
130+
private $container;
131+
/** @var string */
132+
private $identifier;
133+
134+
public function __construct(ContainerInterface $container, string $identifier)
135+
{
136+
$this->container = $container;
137+
$this->identifier = $identifier;
138+
}
139+
140+
/**
141+
* The "resolver" returns the actual value that will be fed to the function.
142+
*/
143+
public function resolve(?object $source, array $args, $context, ResolveInfo $info)
144+
{
145+
return $this->container->get($this->identifier);
146+
}
147+
}
148+
```
149+
150+
## Registering a parameter middleware
151+
152+
The last step is to register the parameter middleware we just wrote:
153+
154+
You can register your own parameter middlewares using the `SchemaFactory::addParameterMiddleware()` method.
155+
156+
```php
157+
$schemaFactory->addParameterMiddleware(new ContainerParameterHandler($container));
158+
```
159+
160+
If you are using the Symfony bundle, you can tag the service as "graphql.parameter_middleware".
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
---
2+
id: version-4.0-authentication_authorization
3+
title: Authentication and authorization
4+
sidebar_label: Authentication and authorization
5+
original_id: authentication_authorization
6+
---
7+
8+
You might not want to expose your GraphQL API to anyone. Or you might want to keep some queries/mutations or fields
9+
reserved to some users.
10+
11+
GraphQLite offers some control over what a user can do with your API. You can restrict access to resources:
12+
13+
- based on authentication using the [`@Logged` annotation](#logged-and-right-annotations) (restrict access to logged users)
14+
- based on authorization using the [`@Right` annotation](#logged-and-right-annotations) (restrict access to logged users with certain rights).
15+
- based on fine-grained authorization using the [`@Security` annotation](fine-grained-security.md) (restrict access for some given resources to some users).
16+
17+
<div class="alert alert-info">
18+
GraphQLite does not have its own security mechanism.
19+
Unless you're using our Symfony Bundle or our Laravel package, it is up to you to connect this feature to your framework's security mechanism.<br>
20+
See <a href="implementing-security.md">Connecting GraphQLite to your framework's security module</a>.
21+
</div>
22+
23+
## `@Logged` and `@Right` annotations
24+
25+
GraphQLite exposes two annotations (`@Logged` and `@Right`) that you can use to restrict access to a resource.
26+
27+
```php
28+
namespace App\Controller;
29+
30+
use TheCodingMachine\GraphQLite\Annotations\Query;
31+
use TheCodingMachine\GraphQLite\Annotations\Logged;
32+
use TheCodingMachine\GraphQLite\Annotations\Right;
33+
34+
class UserController
35+
{
36+
/**
37+
* @Query
38+
* @Logged
39+
* @Right("CAN_VIEW_USER_LIST")
40+
* @return User[]
41+
*/
42+
public function users(int $limit, int $offset): array
43+
{
44+
// ...
45+
}
46+
}
47+
```
48+
49+
In the example above, the query `users` will only be available if the user making the query is logged AND if he
50+
has the `CAN_VIEW_USER_LIST` right.
51+
52+
`@Logged` and `@Right` annotations can be used next to:
53+
54+
* `@Query` annotations
55+
* `@Mutation` annotations
56+
* `@Field` annotations
57+
58+
<div class="alert alert-info">By default, if a user tries to access an unauthorized query/mutation/field, an error is raised and the query fails.</div>
59+
60+
## Not throwing errors
61+
62+
If you do not want an error to be thrown when a user attempts to query a field/query/mutation he has no access to, you can use the `@FailWith` annotation.
63+
64+
The `@FailWith` annotation contains the value that will be returned for users with insufficient rights.
65+
66+
```php
67+
class UserController
68+
{
69+
/**
70+
* If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",
71+
* the value returned will be "null".
72+
*
73+
* @Query
74+
* @Logged
75+
* @Right("CAN_VIEW_USER_LIST")
76+
* @FailWith(null)
77+
* @return User[]
78+
*/
79+
public function users(int $limit, int $offset): array
80+
{
81+
// ...
82+
}
83+
}
84+
```
85+
86+
## Injecting the current user as a parameter
87+
88+
Use the `@InjectUser` annotation to get an instance of the current user logged in.
89+
90+
```php
91+
namespace App\Controller;
92+
93+
use TheCodingMachine\GraphQLite\Annotations\Query;
94+
use TheCodingMachine\GraphQLite\Annotations\InjectUser;
95+
96+
class ProductController
97+
{
98+
/**
99+
* @Query
100+
* @InjectUser(for="$user")
101+
* @return Product
102+
*/
103+
public function product(int $id, User $user): Product
104+
{
105+
// ...
106+
}
107+
}
108+
```
109+
110+
The `@InjectUser` annotation can be used next to:
111+
112+
* `@Query` annotations
113+
* `@Mutation` annotations
114+
* `@Field` annotations
115+
116+
The object injected as the current user depends on your framework. It is in fact the object returned by the
117+
["authentication service" configured in GraphQLite](implementing-security.md).
118+
119+
## Hiding fields / queries / mutations
120+
121+
By default, a user analysing the GraphQL schema can see all queries/mutations/types available.
122+
Some will be available to him and some won't.
123+
124+
If you want to add an extra level of security (or if you want your schema to be kept secret to unauthorized users),
125+
you can use the `@HideIfUnauthorized` annotation.
126+
127+
```php
128+
class UserController
129+
{
130+
/**
131+
* If a user is not logged or if the user has not the right "CAN_VIEW_USER_LIST",
132+
* the schema will NOT contain the "users" query at all (so trying to call the
133+
* "users" query will result in a GraphQL "query not found" error.
134+
*
135+
* @Query
136+
* @Logged
137+
* @Right("CAN_VIEW_USER_LIST")
138+
* @HideIfUnauthorized()
139+
* @return User[]
140+
*/
141+
public function users(int $limit, int $offset): array
142+
{
143+
// ...
144+
}
145+
}
146+
```
147+
148+
While this is the most secured mode, it can have drawbacks when working with development tools
149+
(you need to be logged as admin to fetch the complete schema).
150+
151+
<div class="alert alert-info">The "HideIfUnauthorized" mode was the default mode in GraphQLite 3 and is optionnal from GraphQLite 4+.</div>
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
---
2+
id: version-4.0-autowiring
3+
title: Autowiring services
4+
sidebar_label: Autowiring services
5+
original_id: autowiring
6+
---
7+
8+
GraphQLite can automatically inject services in your fields/queries/mutations signatures.
9+
10+
Some of your fields may be computed. In order to compute these fields, you might need to call a service.
11+
12+
Most of the time, your `@Type` annotation will be put on a model. And models do not have access to services.
13+
Hopefully, if you add a type-hinted service in your field's declaration, GraphQLite will automatically fill it with
14+
the service instance.
15+
16+
## Sample
17+
18+
Let's assume you are running an international store. You have a `Product` class. Each product has many names (depending
19+
on the language of the user).
20+
21+
```php
22+
namespace App\Entities;
23+
24+
use TheCodingMachine\GraphQLite\Annotations\Autowire;
25+
use TheCodingMachine\GraphQLite\Annotations\Field;
26+
use TheCodingMachine\GraphQLite\Annotations\Type;
27+
28+
use Symfony\Component\Translation\TranslatorInterface;
29+
30+
/**
31+
* @Type()
32+
*/
33+
class Product
34+
{
35+
// ...
36+
37+
/**
38+
* @Field()
39+
* @Autowire(for="$translator")
40+
*/
41+
public function getName(TranslatorInterface $translator): string
42+
{
43+
return $translator->trans('product_name_'.$this->id);
44+
}
45+
}
46+
```
47+
48+
When GraphQLite queries the name, it will automatically fetch the translator service.
49+
50+
<div class="alert alert-warning">As with most autowiring solutions, GraphQLite assumes that the service identifier
51+
in the container is the fully qualified class name of the type-hint. So in the example above, GraphQLite will
52+
look for a service whose name is <code>Symfony\Component\Translation\TranslatorInterface</code>.</div>
53+
54+
## Best practices
55+
56+
It is a good idea to refrain from type-hinting on concrete implementations.
57+
Most often, your field declaration will be in your model. If you add a type-hint on a service, you are binding your domain
58+
with a particular service implementation. This makes your code tightly coupled and less testable.
59+
60+
<div class="alert alert-error">
61+
Please don't do that:
62+
63+
<pre><code> /**
64+
* @Field()
65+
*/
66+
public function getName(MyTranslator $translator): string
67+
{
68+
// Your domain is suddenly tightly coupled to the MyTranslator class.
69+
}
70+
</code></pre>
71+
</div>
72+
73+
Instead, be sure to type-hint against an interface.
74+
75+
<div class="alert alert-success">
76+
Do this instead:
77+
78+
<pre><code> /**
79+
* @Field()
80+
*/
81+
public function getName(TranslatorInterface $translator): string
82+
{
83+
// Good. You can switch translator implementation any time.
84+
}
85+
</code></pre>
86+
</div>
87+
88+
By type-hinting against an interface, your code remains testable and is decoupled from the service implementation.
89+
90+
## Fetching a service by name (discouraged!)
91+
92+
Optionally, you can specify the identifier of the service you want to fetch from the controller:
93+
94+
```php
95+
/**
96+
* @Autowire(for="$translator", identifier="translator")
97+
*/
98+
```
99+
100+
<div class="alert alert-error">While GraphQLite offers the possibility to specify the name of the service to be
101+
autowired, we would like to emphasize that this is <strong>highly discouraged</strong>. Hard-coding a container
102+
identifier in the code of your class is akin to using the "service locator" pattern, which is known to be an
103+
anti-pattern. Please refrain from doing this as much as possible.</div>
104+
105+
## Alternative solution
106+
107+
You may find yourself uncomfortable with the autowiring mechanism of GraphQLite. For instance maybe:
108+
109+
- Your service identifier in the container is not the fully qualified class name of the service (this is often true if you are not using a container supporting autowiring)
110+
- You do not want to inject a service in a domain object
111+
- You simply do not like the magic of injecting services in a method signature
112+
113+
If you do not want to use autowiring and if you still need to access services to compute a field, please read on
114+
the next chapter to learn [how to extend a type](extend_type).
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
---
2+
id: version-4.0-custom-types
3+
title: Custom types
4+
sidebar_label: Custom types
5+
original_id: custom-types
6+
---
7+
8+
In some special cases, you want to override the GraphQL return type that is attributed by default by GraphQLite.
9+
10+
For instance:
11+
12+
```php
13+
/**
14+
* @Type(class=Product::class)
15+
*/
16+
class ProductType
17+
{
18+
/**
19+
* @Field(name="id")
20+
*/
21+
public function getId(Product $source): string
22+
{
23+
return $source->getId();
24+
}
25+
}
26+
```
27+
28+
In the example above, GraphQLite will generate a GraphQL schema with a field `id` of type `string`:
29+
30+
```graphql
31+
type Product {
32+
id: String!
33+
}
34+
```
35+
36+
GraphQL comes with an `ID` scalar type. But PHP has no such type. So GraphQLite does not know when a variable
37+
is an `ID` or not.
38+
39+
You can help GraphQLite by manually specifying the output type to use:
40+
41+
```php
42+
/**
43+
* @Field(name="id", outputType="ID")
44+
*/
45+
```
46+
47+
## Usage
48+
49+
The `outputType` attribute will map the return value of the method to the output type passed in parameter.
50+
51+
You can use the `outputType` attribute in the following annotations:
52+
53+
* `@Query`
54+
* `@Mutation`
55+
* `@Field`
56+
* `@SourceField`
57+
* `@MagicField`
58+
59+
## Registering a custom output type (advanced)
60+
61+
In order to create a custom output type, you need to:
62+
63+
1. Design a class that extends `GraphQL\Type\Definition\ObjectType`.
64+
2. Register this class in the GraphQL schema.
65+
66+
You'll find more details on the [Webonyx documentation](https://webonyx.github.io/graphql-php/type-system/object-types/).
67+
68+
---
69+
70+
In order to find existing types, the schema is using *type mappers* (classes implementing the `TypeMapperInterface` interface).
71+
72+
You need to make sure that one of these type mappers can return an instance of your type. The way you do this will depend on the framework
73+
you use.
74+
75+
### Symfony users
76+
77+
Any class extending `GraphQL\Type\Definition\ObjectType` (and available in the container) will be automatically detected
78+
by Symfony and added to the schema.
79+
80+
If you want to automatically map the output type to a given PHP class, you will have to explicitly declare the output type
81+
as a service and use the `graphql.output_type` tag:
82+
83+
```yaml
84+
# config/services.yaml
85+
services:
86+
App\MyOutputType:
87+
tags:
88+
- { name: 'graphql.output_type', class: 'App\MyPhpClass' }
89+
```
90+
91+
### Other frameworks
92+
93+
The easiest way is to use a `StaticTypeMapper`. This class is used to register custom output types.
94+
95+
```php
96+
// Sample code:
97+
$staticTypeMapper = new StaticTypeMapper();
98+
99+
// Let's register a type that maps by default to the "MyClass" PHP class
100+
$staticTypeMapper->setTypes([
101+
MyClass::class => new MyCustomOutputType()
102+
]);
103+
104+
// If you don't want your output type to map to any PHP class by default, use:
105+
$staticTypeMapper->setNotMappedTypes([
106+
new MyCustomOutputType()
107+
]);
108+
109+
```
110+
111+
The `StaticTypeMapper` instance MUST be registered in your container and linked to a `CompositeTypeMapper`
112+
that will aggregate all the type mappers of the application.
113+
114+
## Registering a custom scalar type (advanced)
115+
116+
If you need to add custom scalar types, first, check the [GraphQLite Misc. Types library](https://github.com/thecodingmachine/graphqlite-misc-types).
117+
It contains a number of "out-of-the-box" scalar types ready to use and you might find what you need there.
118+
119+
You still need to develop your custom scalar type? Ok, let's get started.
120+
121+
In order to add a scalar type in GraphQLite, you need to:
122+
123+
- create a [Webonyx custom scalar type](https://webonyx.github.io/graphql-php/type-system/scalar-types/#writing-custom-scalar-types).
124+
You do this by creating a class that extends `GraphQL\Type\Definition\ScalarType`.
125+
- create a "type mapper" that will map PHP types to the GraphQL scalar type. You do this by writing a class implementing the `RootTypeMapperInterface`.
126+
- create a "type mapper factory" that will be in charge of creating your "type mapper".
127+
128+
```php
129+
interface RootTypeMapperInterface
130+
{
131+
public function toGraphQLOutputType(Type $type, ?OutputType $subType, ReflectionMethod $refMethod, DocBlock $docBlockObj): OutputType;
132+
133+
public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, ReflectionMethod $refMethod, DocBlock $docBlockObj): InputType;
134+
135+
public function mapNameToType(string $typeName): NamedType;
136+
}
137+
```
138+
139+
The `toGraphQLOutputType` and `toGraphQLInputType` are meant to map a return type (for output types) or a parameter type (for input types)
140+
to your GraphQL scalar type. Return your scalar type if there is a match or `null` if there no match.
141+
142+
The `mapNameToType` should return your GraphQL scalar type if `$typeName` is the name of your scalar type.
143+
144+
RootTypeMapper are organized **in a chain** (they are actually middlewares).
145+
Each instance of a `RootTypeMapper` holds a reference on the next root type mapper to be called in the chain.
146+
147+
For instance:
148+
149+
```php
150+
class AnyScalarTypeMapper implements RootTypeMapperInterface
151+
{
152+
/** @var RootTypeMapperInterface */
153+
private $next;
154+
155+
public function __construct(RootTypeMapperInterface $next)
156+
{
157+
$this->next = $next;
158+
}
159+
160+
public function toGraphQLOutputType(Type $type, ?OutputType $subType, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?OutputType
161+
{
162+
if ($type instanceof Scalar) {
163+
// AnyScalarType is a class implementing the Webonyx ScalarType type.
164+
return AnyScalarType::getInstance();
165+
}
166+
// If the PHPDoc type is not "Scalar", let's pass the control to the next type mapper in the chain
167+
return $this->next->toGraphQLOutputType($type, $subType, $refMethod, $docBlockObj);
168+
}
169+
170+
public function toGraphQLInputType(Type $type, ?InputType $subType, string $argumentName, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?InputType
171+
{
172+
if ($type instanceof Scalar) {
173+
// AnyScalarType is a class implementing the Webonyx ScalarType type.
174+
return AnyScalarType::getInstance();
175+
}
176+
// If the PHPDoc type is not "Scalar", let's pass the control to the next type mapper in the chain
177+
return $this->next->toGraphQLInputType($type, $subType, $argumentName, $refMethod, $docBlockObj);
178+
}
179+
180+
/**
181+
* Returns a GraphQL type by name.
182+
* If this root type mapper can return this type in "toGraphQLOutputType" or "toGraphQLInputType", it should
183+
* also map these types by name in the "mapNameToType" method.
184+
*
185+
* @param string $typeName The name of the GraphQL type
186+
* @return NamedType|null
187+
*/
188+
public function mapNameToType(string $typeName): ?NamedType
189+
{
190+
if ($typeName === AnyScalarType::NAME) {
191+
return AnyScalarType::getInstance();
192+
}
193+
return null;
194+
}
195+
}
196+
```
197+
198+
Now, in order to create an instance of your `AnyScalarTypeMapper` class, you need an instance of the `$next` type mapper in the chain.
199+
How do you get the `$next` type mapper? Through a factory:
200+
201+
```php
202+
class AnyScalarTypeMapperFactory implements RootTypeMapperFactoryInterface
203+
{
204+
public function create(RootTypeMapperInterface $next, RootTypeMapperFactoryContext $context): RootTypeMapperInterface
205+
{
206+
return new AnyScalarTypeMapper($next);
207+
}
208+
}
209+
```
210+
211+
Now, you need to register this factory in your application, and we are done.
212+
213+
You can register your own root mapper factories using the `SchemaFactory::addRootTypeMapperFactory()` method.
214+
215+
```php
216+
$schemaFactory->addRootTypeMapperFactory(new AnyScalarTypeMapperFactory());
217+
```
218+
219+
If you are using the Symfony bundle, the factory will be automatically registered, you have nothing to do (the service
220+
is automatically tagged with the "graphql.root_type_mapper_factory" tag).
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
---
2+
id: version-4.0-error-handling
3+
title: Error handling
4+
sidebar_label: Error handling
5+
original_id: error-handling
6+
---
7+
8+
In GraphQL, when an error occurs, the server must add an "error" entry in the response.
9+
10+
```json
11+
{
12+
"errors": [
13+
{
14+
"message": "Name for character with ID 1002 could not be fetched.",
15+
"locations": [ { "line": 6, "column": 7 } ],
16+
"path": [ "hero", "heroFriends", 1, "name" ],
17+
"extensions": {
18+
"category": "Exception"
19+
}
20+
}
21+
]
22+
}
23+
```
24+
25+
You can generate such errors with GraphQLite by throwing a `GraphQLException`.
26+
27+
```php
28+
use TheCodingMachine\GraphQLite\Exceptions\GraphQLException;
29+
30+
throw new GraphQLException("Exception message");
31+
```
32+
33+
## HTTP response code
34+
35+
By default, when you throw a `GraphQLException`, the HTTP status code will be 500.
36+
37+
If your exception code is in the 4xx - 5xx range, the exception code will be used as an HTTP status code.
38+
39+
```php
40+
// This exception will generate a HTTP 404 status code
41+
throw new GraphQLException("Not found", 404);
42+
```
43+
44+
<div class="alert alert-info">GraphQL allows to have several errors for one request. If you have several
45+
<code>GraphQLException</code> thrown for the same request, the HTTP status code used will be the highest one.</div>
46+
47+
## Customizing the category
48+
49+
By default, GraphQLite adds a "category" entry in the "extensions section". You can customize the category with the
50+
4th parameter of the constructor:
51+
52+
```php
53+
throw new GraphQLException("Not found", 404, null, "NOT_FOUND");
54+
```
55+
56+
will generate:
57+
58+
```json
59+
{
60+
"errors": [
61+
{
62+
"message": "Not found",
63+
"extensions": {
64+
"category": "NOT_FOUND"
65+
}
66+
}
67+
]
68+
}
69+
```
70+
71+
## Customizing the extensions section
72+
73+
You can customize the whole "extensions" section with the 5th parameter of the constructor:
74+
75+
```php
76+
throw new GraphQLException("Field required", 400, null, "VALIDATION", ['field' => 'name']);
77+
```
78+
79+
will generate:
80+
81+
```json
82+
{
83+
"errors": [
84+
{
85+
"message": "Field required",
86+
"extensions": {
87+
"category": "VALIDATION",
88+
"field": "name"
89+
}
90+
}
91+
]
92+
}
93+
```
94+
95+
## Writing your own exceptions
96+
97+
Rather that throwing the base `GraphQLException`, you should consider writing your own exception.
98+
99+
Any exception that implements interface `TheCodingMachine\GraphQLite\Exceptions\GraphQLExceptionInterface` will be displayed
100+
in the GraphQL "errors" section.
101+
102+
```php
103+
class ValidationException extends Exception implements GraphQLExceptionInterface
104+
{
105+
/**
106+
* Returns true when exception message is safe to be displayed to a client.
107+
*/
108+
public function isClientSafe(): bool
109+
{
110+
return true;
111+
}
112+
113+
/**
114+
* Returns string describing a category of the error.
115+
*
116+
* Value "graphql" is reserved for errors produced by query parsing or validation, do not use it.
117+
*/
118+
public function getCategory(): string
119+
{
120+
return 'VALIDATION';
121+
}
122+
123+
/**
124+
* Returns the "extensions" object attached to the GraphQL error.
125+
*
126+
* @return array<string, mixed>
127+
*/
128+
public function getExtensions(): array
129+
{
130+
return [];
131+
}
132+
}
133+
```
134+
135+
## Many errors for one exception
136+
137+
Sometimes, you need to display several errors in the response. But of course, at any given point in your code, you can
138+
throw only one exception.
139+
140+
If you want to display several exceptions, you can bundle these exceptions in a `GraphQLAggregateException` that you can
141+
throw.
142+
143+
```php
144+
use TheCodingMachine\GraphQLite\Exceptions\GraphQLAggregateException;
145+
146+
/**
147+
* @Query
148+
*/
149+
public function createProduct(string $name, float $price): Product
150+
{
151+
$exceptions = new GraphQLAggregateException();
152+
153+
if ($name === '') {
154+
$exceptions->add(new GraphQLException('Name cannot be empty', 400, null, 'VALIDATION'));
155+
}
156+
if ($price <= 0) {
157+
$exceptions->add(new GraphQLException('Price must be positive', 400, null, 'VALIDATION'));
158+
}
159+
160+
if ($exceptions->hasExceptions()) {
161+
throw $exceptions;
162+
}
163+
}
164+
```
165+
166+
## Webonyx exceptions
167+
168+
GraphQLite is based on the wonderful webonyx/GraphQL-PHP library. Therefore, the Webonyx exception mechanism can
169+
also be used in GraphQLite. This means you can throw a `GraphQL\Error\Error` exception or any exception implementing
170+
[`GraphQL\Error\ClientAware` interface](http://webonyx.github.io/graphql-php/error-handling/#errors-in-graphql)
171+
172+
Actually, the `TheCodingMachine\GraphQLite\Exceptions\GraphQLExceptionInterface` extends Webonyx's `ClientAware` interface.
173+
174+
## Behaviour of exceptions that do not implement ClientAware
175+
176+
If an exception that does not implement `ClientAware` is thrown, by default, GraphQLite will not catch it.
177+
178+
The exception will propagate to your framework error handler/middleware that is in charge of displaying the classical error page.
179+
180+
You can [change the underlying behaviour of Webonyx to catch any exception and turn them into GraphQL errors](http://webonyx.github.io/graphql-php/error-handling/#debugging-tools).
181+
The way you adjust the error settings depends on the framework you are using ([Symfony](symfony-bundle.md), [Laravel](laravel-package.md)).
182+
183+
<div class="alert alert-info">To be clear: we strongly discourage changing this setting. We strongly believe that the
184+
default "RETHROW_UNSAFE_EXCEPTIONS" setting of Webonyx is the only sane setting (only putting in "errors" section exceptions
185+
designed for GraphQL).</div>
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
---
2+
id: version-4.0-extend_input_type
3+
title: Extending an input type
4+
sidebar_label: Extending an input type
5+
original_id: extend_input_type
6+
---
7+
<small>Available in GraphQLite 4.0+</small>
8+
9+
<div class="alert alert-info">If you are not familiar with the <code>@Factory</code> tag, <a href="input-types">read first the "input types" guide</a>.</div>
10+
11+
Fields exposed in a GraphQL input type do not need to be all part of the factory method.
12+
13+
Just like with output type (that can be [extended using the `ExtendType` annotation](extend_type.md)), you can extend/modify
14+
an input type using the `@Decorate` annotation.
15+
16+
Use the `@Decorate` annotation to add additional fields to an input type that is already declared by a `@Factory` annotation,
17+
or to modify the returned object.
18+
19+
<div class="alert alert-info">
20+
The <code>@Decorate</code> annotation is very useful in scenarios where you cannot touch the <code>@Factory</code> method.
21+
This can happen if the <code>@Factory</code> method is defined in a third-party library or if the <code>@Factory</code> method is part
22+
of auto-generated code.
23+
</div>
24+
25+
Let's assume you have a `Filter` class used as an input type. You most certainly have a `@Factory` to create the input type.
26+
27+
```
28+
class MyFactory
29+
{
30+
/**
31+
* @Factory()
32+
*/
33+
public function createFilter(string $name): Filter
34+
{
35+
// Let's assume you have a flexible 'Filter' class that can accept any kind of filter
36+
$filter = new Filter();
37+
$filter->addFilter('name', $name);
38+
return $filter;
39+
}
40+
}
41+
```
42+
43+
Assuming you **cannot** modify the code of this factory, you can still modify the GraphQL input type generated by
44+
adding a "decorator" around the factory.
45+
46+
```
47+
class MyDecorator
48+
{
49+
/**
50+
* @Decorate(inputTypeName="FilterInput")
51+
*/
52+
public function addTypeFilter(Filter $filter, string $type): Filter
53+
{
54+
$filter->addFilter('type', $type);
55+
return $filter;
56+
}
57+
}
58+
```
59+
60+
In the example above, the "Filter" input type is modified. We add an additional "type" field to the input type.
61+
62+
A few things to notice:
63+
64+
- The decorator takes the object generated by the factory as first argument
65+
- The decorator MUST return an object of the same type (or a sub-type)
66+
- The decorator CAN contain additional parameters. They will be added to the fields of the GraphQL input type.
67+
- The `@Decorate` annotation must contain a `inputTypeName` attribute that contains the name of the GraphQL input type
68+
that is decorated. If you did not specify this name in the `@Factory` annotation, this is by default the name of the
69+
PHP class + "Input" (for instance: "Filter" => "FilterInput")
70+
71+
72+
<div class="alert alert-warning"><strong>Heads up!</strong> The <code>MyDecorator</code> class must exist in the container of your
73+
application and the container identifier MUST be the fully qualified class name.<br/><br/>
74+
If you are using the Symfony bundle (or a framework with autowiring like Laravel), this
75+
is usually not an issue as the container will automatically create the controller entry if you do not explicitly
76+
declare it.</div>
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
---
2+
id: version-4.0-extend_type
3+
title: Extending a type
4+
sidebar_label: Extending a type
5+
original_id: extend_type
6+
---
7+
8+
Fields exposed in a GraphQL type do not need to be all part of the same class.
9+
10+
Use the `@ExtendType` annotation to add additional fields to a type that is already declared.
11+
12+
<div class="alert alert-info">
13+
Extending a type has nothing to do with type inheritance.
14+
If you are looking for a way to expose a class and its children classes, have a look at
15+
the <a href="inheritance-interfaces">Inheritance</a> section</a>
16+
</div>
17+
18+
Let's assume you have a `Product` class. In order to get the name of a product, there is no `getName()` method in
19+
the product because the name needs to be translated in the correct language. You have a `TranslationService` to do that.
20+
21+
```php
22+
namespace App\Entities;
23+
24+
use TheCodingMachine\GraphQLite\Annotations\Field;
25+
use TheCodingMachine\GraphQLite\Annotations\Type;
26+
27+
/**
28+
* @Type()
29+
*/
30+
class Product
31+
{
32+
// ...
33+
34+
/**
35+
* @Field()
36+
*/
37+
public function getId(): string
38+
{
39+
return $this->id;
40+
}
41+
42+
/**
43+
* @Field()
44+
*/
45+
public function getPrice(): ?float
46+
{
47+
return $this->price;
48+
}
49+
}
50+
```
51+
52+
```php
53+
// You need to use a service to get the name of the product in the correct language.
54+
$name = $translationService->getProductName($productId, $language);
55+
```
56+
57+
Using `@ExtendType`, you can add an additional `name` field to your product:
58+
59+
```php
60+
namespace App\Types;
61+
62+
use TheCodingMachine\GraphQLite\Annotations\ExtendType;
63+
use TheCodingMachine\GraphQLite\Annotations\Field;
64+
use App\Entities\Product;
65+
66+
/**
67+
* @ExtendType(class=Product::class)
68+
*/
69+
class ProductType
70+
{
71+
private $translationService;
72+
73+
public function __construct(TranslationServiceInterface $translationService)
74+
{
75+
$this->translationService = $translationService;
76+
}
77+
78+
/**
79+
* @Field()
80+
*/
81+
public function getName(Product $product, string $language): string
82+
{
83+
return $this->translationService->getProductName($product->getId(), $language);
84+
}
85+
}
86+
```
87+
88+
Let's break this sample:
89+
90+
```php
91+
/**
92+
* @ExtendType(class=Product::class)
93+
*/
94+
```
95+
96+
With the `@ExtendType` annotation, we tell GraphQLite that we want to add fields in the GraphQL type mapped to
97+
the `Product` PHP class.
98+
99+
```php
100+
class ProductType
101+
{
102+
private $translationService;
103+
104+
public function __construct(TranslationServiceInterface $translationService)
105+
{
106+
$this->translationService = $translationService;
107+
}
108+
109+
// ...
110+
}
111+
```
112+
113+
114+
- The `ProductType` class must be in the types namespace. You configured this namespace when you installed GraphQLite.
115+
- The `ProductType` class is actually a **service**. You can therefore inject dependencies in it (like the `$translationService` in this example)
116+
117+
<div class="alert alert-warning"><strong>Heads up!</strong> The <code>ProductType</code> class must exist in the container of your
118+
application and the container identifier MUST be the fully qualified class name.<br/><br/>
119+
If you are using the Symfony bundle (or a framework with autowiring like Laravel), this
120+
is usually not an issue as the container will automatically create the controller entry if you do not explicitly
121+
declare it.</div>
122+
123+
```php
124+
/**
125+
* @Field()
126+
*/
127+
public function getName(Product $product, string $language): string
128+
{
129+
return $this->translationService->getProductName($product->getId(), $language);
130+
}
131+
```
132+
133+
The `@Field` annotation is used to add the "name" field to the `Product` type.
134+
135+
Take a close look at the signature. The first parameter is the "resolved object" we are working on.
136+
Any additional parameters are used as arguments.
137+
138+
Using the "[Type language](https://graphql.org/learn/schema/#type-language)" notation, we defined a type extension for
139+
the GraphQL "Product" type:
140+
141+
```graphql
142+
Extend type Product {
143+
name(language: !String): String!
144+
}
145+
```
146+
147+
<div class="alert alert-success">Type extension is a very powerful tool. Use it to add fields that needs to be
148+
computed from services not available in the entity.
149+
</div>
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
---
2+
id: version-4.0-external_type_declaration
3+
title: External type declaration
4+
sidebar_label: External type declaration
5+
original_id: external_type_declaration
6+
---
7+
8+
In some cases, you cannot or do not want to put an annotation on a domain class.
9+
10+
For instance:
11+
12+
* The class you want to annotate is part of a third party library and you cannot modify it
13+
* You are doing domain-driven design and don't want to clutter your domain object with annotations from the view layer
14+
* etc.
15+
16+
## `@Type` annotation with the `class` attribute
17+
18+
GraphQLite allows you to use a *proxy* class thanks to the `@Type` annotation with the `class` attribute:
19+
20+
```php
21+
namespace App\Types;
22+
23+
use TheCodingMachine\GraphQLite\Annotations\Type;
24+
use TheCodingMachine\GraphQLite\Annotations\Field;
25+
use App\Entities\Product;
26+
27+
/**
28+
* @Type(class=Product::class)
29+
*/
30+
class ProductType
31+
{
32+
/**
33+
* @Field()
34+
*/
35+
public function getId(Product $product): string
36+
{
37+
return $product->getId();
38+
}
39+
}
40+
```
41+
42+
The `ProductType` class must be in the *types* namespace. You configured this namespace when you installed GraphQLite.
43+
44+
The `ProductType` class is actually a **service**. You can therefore inject dependencies in it.
45+
46+
<div class="alert alert-warning"><strong>Heads up!</strong> The <code>ProductType</code> class must exist in the container of your application and the container identifier MUST be the fully qualified class name.<br/><br/>
47+
If you are using the Symfony bundle (or a framework with autowiring like Laravel), this
48+
is usually not an issue as the container will automatically create the controller entry if you do not explicitly
49+
declare it.</div>
50+
51+
In methods with a `@Field` annotation, the first parameter is the *resolved object* we are working on. Any additional parameters are used as arguments.
52+
53+
## `@SourceField` annotation
54+
55+
If you don't want to rewrite all *getters* of your base class, you may use the `@SourceField` annotation:
56+
57+
```php
58+
use TheCodingMachine\GraphQLite\Annotations\Type;
59+
use TheCodingMachine\GraphQLite\Annotations\SourceField;
60+
use App\Entities\Product;
61+
62+
/**
63+
* @Type(class=Product::class)
64+
* @SourceField(name="name")
65+
* @SourceField(name="price")
66+
*/
67+
class ProductType
68+
{
69+
}
70+
```
71+
72+
By doing so, you let GraphQLite know that the type exposes the `getName` method of the underlying `Product` object.
73+
74+
Internally, GraphQLite will look for methods named `name()`, `getName()` and `isName()`).
75+
76+
## `@MagicField` annotation
77+
78+
If your object has no getters, but instead uses magic properties (using the magic `__get` method), you should use the `@MagicField` annotation:
79+
80+
```php
81+
use TheCodingMachine\GraphQLite\Annotations\Type;
82+
use TheCodingMachine\GraphQLite\Annotations\SourceField;
83+
use App\Entities\Product;
84+
85+
/**
86+
* @Type()
87+
* @MagicField(name="name", outputType="String!")
88+
* @MagicField(name="price", outputType="Float")
89+
*/
90+
class ProductType
91+
{
92+
public function __get(string $property) {
93+
// return some magic property
94+
}
95+
}
96+
```
97+
98+
By doing so, you let GraphQLite know that the type exposes "name" and the "price" magic properties of the underlying `Product` object.
99+
100+
This is particularly useful in frameworks like Laravel, where Eloquent is making a very wide use of such properties.
101+
102+
Please note that GraphQLite has no way to know the type of a magic property. Therefore, you have specify the GraphQL type
103+
of each property manually.
104+
105+
### Authentication and authorization
106+
107+
You may also check for logged users or users with a specific right using the "annotations" property.
108+
109+
```php
110+
use TheCodingMachine\GraphQLite\Annotations\Type;
111+
use TheCodingMachine\GraphQLite\Annotations\SourceField;
112+
use TheCodingMachine\GraphQLite\Annotations\Logged;
113+
use TheCodingMachine\GraphQLite\Annotations\Right;
114+
use TheCodingMachine\GraphQLite\Annotations\FailWith;
115+
use App\Entities\Product;
116+
117+
/**
118+
* @Type(class=Product::class)
119+
* @SourceField(name="name")
120+
* @SourceField(name="price", annotations={@Logged, @Right(name="CAN_ACCESS_Price", @FailWith(null)}))
121+
*/
122+
class ProductType extends AbstractAnnotatedObjectType
123+
{
124+
}
125+
```
126+
127+
Any annotations described in the [Authentication and authorization page](authentication_authorization.md) can be used in the `@SourceField` "annotations" attribute.
128+
129+
## Declaring fields dynamically (without annotations)
130+
131+
In some very particular cases, you might not know exactly the list of `@SourceField` annotations at development time.
132+
If you need to decide the list of `@SourceField` at runtime, you can implement the `FromSourceFieldsInterface`:
133+
134+
```php
135+
use TheCodingMachine\GraphQLite\FromSourceFieldsInterface;
136+
137+
/**
138+
* @Type(class=Product::class)
139+
*/
140+
class ProductType implements FromSourceFieldsInterface
141+
{
142+
/**
143+
* Dynamically returns the array of source fields
144+
* to be fetched from the original object.
145+
*
146+
* @return SourceFieldInterface[]
147+
*/
148+
public function getSourceFields(): array
149+
{
150+
// You may want to enable fields conditionally based on feature flags...
151+
if (ENABLE_STATUS_GLOBALLY) {
152+
return [
153+
new SourceField(['name'=>'status', 'logged'=>true]),
154+
];
155+
} else {
156+
return [];
157+
}
158+
}
159+
}
160+
```
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
---
2+
id: version-4.0-features
3+
title: GraphQLite
4+
sidebar_label: GraphQLite
5+
original_id: features
6+
---
7+
8+
<p align="center">
9+
<img src="https://graphqlite.thecodingmachine.io/img/logo.svg" alt="GraphQLite logo" width="250" height="250" />
10+
</p>
11+
12+
13+
A PHP library that allows you to write your GraphQL queries in simple-to-write controllers.
14+
15+
## Features
16+
17+
* Create a complete GraphQL API by simply annotating your PHP classes
18+
* Framework agnostic, but Symfony, Laravel and PSR-15 bindings available!
19+
* Comes with batteries included: queries, mutations, mapping of arrays / iterators, file uploads, security, validation, extendable types and more!
20+
21+
## Basic example
22+
23+
First, declare a query in your controller:
24+
25+
```php
26+
class ProductController
27+
{
28+
/**
29+
* @Query()
30+
*/
31+
public function product(string $id): Product
32+
{
33+
// Some code that looks for a product and returns it.
34+
}
35+
}
36+
```
37+
38+
Then, annotate the `Product` class to declare what fields are exposed to the GraphQL API:
39+
40+
```php
41+
/**
42+
* @Type()
43+
*/
44+
class Product
45+
{
46+
/**
47+
* @Field()
48+
*/
49+
public function getName(): string
50+
{
51+
return $this->name;
52+
}
53+
// ...
54+
}
55+
```
56+
57+
That's it, you're good to go! Query and enjoy!
58+
59+
```grapql
60+
{
61+
product(id: 42) {
62+
name
63+
}
64+
}
65+
```
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
---
2+
id: version-4.0-field-middlewares
3+
title: Adding custom annotations with Field middlewares
4+
sidebar_label: Custom annotations
5+
original_id: field-middlewares
6+
---
7+
<small>Available in GraphQLite 4.0+</small>
8+
9+
Just like the `@Logged` or `@Right` annotation, you can develop your own annotation that extends/modifies the behaviour
10+
of a field/query/mutation.
11+
12+
<div class="alert alert-warning">If you want to create an annotation that targets a single argument (like <code>@AutoWire(for="$service")</code>),
13+
you should rather check the documentation about <a href="argument-resolving">custom argument resolving</a></div>
14+
15+
## Field middlewares
16+
17+
GraphQLite is based on the Webonyx/Graphql-PHP library. In Webonyx, fields are represented by the `FieldDefinition` class.
18+
In order to create a `FieldDefinition` instance for your field, GraphQLite goes through a series of "middlewares".
19+
20+
![](assets/field_middleware.svg)
21+
22+
Each middleware is passed a `TheCodingMachine\GraphQLite\QueryFieldDescriptor` instance. This object contains all the
23+
parameters used to initialize the field (like the return type, the list of arguments, the resolver to be used, etc...)
24+
25+
Each middleware must return a `GraphQL\Type\Definition\FieldDefinition` (the object representing a field in Webonyx/GraphQL-PHP).
26+
27+
```php
28+
/**
29+
* Your middleware must implement this interface.
30+
*/
31+
interface FieldMiddlewareInterface
32+
{
33+
public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): ?FieldDefinition;
34+
}
35+
```
36+
37+
```php
38+
class QueryFieldDescriptor
39+
{
40+
public function getName() { /* ... */ }
41+
public function setName(string $name) { /* ... */ }
42+
public function getType() { /* ... */ }
43+
public function setType($type): void { /* ... */ }
44+
public function getParameters(): array { /* ... */ }
45+
public function setParameters(array $parameters): void { /* ... */ }
46+
public function getPrefetchParameters(): array { /* ... */ }
47+
public function setPrefetchParameters(array $prefetchParameters): void { /* ... */ }
48+
public function getPrefetchMethodName(): ?string { /* ... */ }
49+
public function setPrefetchMethodName(?string $prefetchMethodName): void { /* ... */ }
50+
public function setCallable(callable $callable): void { /* ... */ }
51+
public function setTargetMethodOnSource(?string $targetMethodOnSource): void { /* ... */ }
52+
public function isInjectSource(): bool { /* ... */ }
53+
public function setInjectSource(bool $injectSource): void { /* ... */ }
54+
public function getComment(): ?string { /* ... */ }
55+
public function setComment(?string $comment): void { /* ... */ }
56+
public function getMiddlewareAnnotations(): MiddlewareAnnotations { /* ... */ }
57+
public function setMiddlewareAnnotations(MiddlewareAnnotations $middlewareAnnotations): void { /* ... */ }
58+
public function getOriginalResolver(): ResolverInterface { /* ... */ }
59+
public function getResolver(): callable { /* ... */ }
60+
public function setResolver(callable $resolver): void { /* ... */ }
61+
}
62+
```
63+
64+
The role of a middleware is to analyze the `QueryFieldDescriptor` and modify it (or to directly return a `FieldDefinition`).
65+
66+
If you want the field to purely disappear, your middleware can return `null`.
67+
68+
## Annotations parsing
69+
70+
Take a look at the `QueryFieldDescriptor::getMiddlewareAnnotations()`.
71+
72+
It returns the list of annotations applied to your field that implements the `MiddlewareAnnotationInterface`.
73+
74+
Let's imagine you want to add a `@OnlyDebug` annotation that displays a field/query/mutation only in debug mode (and
75+
hides the field in production). That could be useful, right?
76+
77+
First, we have to define the annotation. Annotations are handled by the great [doctrine/annotations](https://www.doctrine-project.org/projects/doctrine-annotations/en/1.6/index.html) library.
78+
79+
**OnlyDebug.php**
80+
```php
81+
namespace App\Annotations;
82+
83+
use TheCodingMachine\GraphQLite\Annotations\MiddlewareAnnotationInterface;
84+
85+
/**
86+
* @Annotation
87+
* @Target({"METHOD", "ANNOTATION"})
88+
*/
89+
class OnlyDebug implements MiddlewareAnnotationInterface
90+
{
91+
}
92+
```
93+
94+
Apart from being a classical annotation, this class implements the `MiddlewareAnnotationInterface`. This interface
95+
is a "marker" interface. It does not have any methods. It is just used to tell GraphQLite that this annotation
96+
is to be used by middlewares.
97+
98+
Now, we can write a middleware that will act upon this annotation.
99+
100+
```php
101+
namespace App\Middlewares;
102+
103+
use App\Annotations\OnlyDebug;
104+
use TheCodingMachine\GraphQLite\Middlewares\FieldMiddlewareInterface;
105+
use GraphQL\Type\Definition\FieldDefinition;
106+
use TheCodingMachine\GraphQLite\QueryFieldDescriptor;
107+
108+
/**
109+
* Middleware in charge of hiding a field if it is annotated with @OnlyDebug and the DEBUG constant is not set
110+
*/
111+
class OnlyDebugFieldMiddleware implements FieldMiddlewareInterface
112+
{
113+
public function process(QueryFieldDescriptor $queryFieldDescriptor, FieldHandlerInterface $fieldHandler): ?FieldDefinition
114+
{
115+
$annotations = $queryFieldDescriptor->getMiddlewareAnnotations();
116+
117+
/**
118+
* @var OnlyDebug $onlyDebug
119+
*/
120+
$onlyDebug = $annotations->getAnnotationByType(OnlyDebug::class);
121+
122+
if ($onlyDebug !== null && !DEBUG) {
123+
// If the onlyDebug annotation is present, returns null.
124+
// Returning null will hide the field.
125+
return null;
126+
}
127+
128+
// Otherwise, let's continue the middleware pipe without touching anything.
129+
return $fieldHandler->handle($queryFieldDescriptor);
130+
}
131+
}
132+
```
133+
134+
The final thing we have to do is to register the middleware.
135+
136+
- Assuming you are using the `SchemaFactory` to initialize GraphQLite, you can register the field middleware using:
137+
```php
138+
$schemaFactory->addFieldMiddleware(new OnlyDebugFieldMiddleware());
139+
```
140+
- If you are using the Symfony bundle, you can register your field middleware services by tagging them with the `graphql.field_middleware` tag.
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
---
2+
id: version-4.0-fine-grained-security
3+
title: Fine grained security
4+
sidebar_label: Fine grained security
5+
original_id: fine-grained-security
6+
---
7+
8+
If the [`@Logged` and `@Right` annotations](authentication_authorization.md#logged-and-right-annotations) are not
9+
granular enough for your needs, you can use the advanced `@Security` annotation.
10+
11+
Using the `@Security` annotation, you can write an *expression* that can contain custom logic. For instance:
12+
13+
- Check that a user can access a given resource
14+
- Check that a user has one right or another right
15+
- ...
16+
17+
## Using the @Security annotation
18+
19+
The `@Security` annotation is very flexible: it allows you to pass an expression that can contains custom logic:
20+
21+
```php
22+
use TheCodingMachine\GraphQLite\Annotations\Security;
23+
24+
// ...
25+
26+
/**
27+
* @Query
28+
* @Security("is_granted('ROLE_ADMIN') or is_granted('POST_SHOW', post)")
29+
*/
30+
public function getPost(Post $post): array
31+
{
32+
// ...
33+
}
34+
```
35+
36+
The *expression* defined in the `@Security` annotation must conform to [Symfony's Expression Language syntax](https://symfony.com/doc/4.4/components/expression_language/syntax.html)
37+
38+
<div class="alert alert-info">
39+
If you are a Symfony user, you might already be used to the <code>@Security</code> annotation. Most of the inspiration
40+
of this annotation comes from Symfony. Warning though! GraphQLite's <code>@Security</code> annotation and
41+
Symfony's <code>@Security</code> annotation are slightly different. Especially, the two annotations do not live
42+
in the same namespace!
43+
</div>
44+
45+
## The `is_granted` function
46+
47+
Use the `is_granted` function to check if a user has a special right.
48+
49+
```php
50+
@Security("is_granted('ROLE_ADMIN')")
51+
```
52+
53+
is similar to
54+
55+
```php
56+
@Right("ROLE_ADMIN")
57+
```
58+
59+
In addition, the `is_granted` function accepts a second optional parameter: the "scope" of the right.
60+
61+
```php
62+
/**
63+
* @Query
64+
* @Security("is_granted('POST_SHOW', post)")
65+
*/
66+
public function getPost(Post $post): array
67+
{
68+
// ...
69+
}
70+
```
71+
72+
In the example above, the `getPost` method can be called only if the logged user has the 'POST_SHOW' permission on the
73+
`$post` object. You can notice that the `$post` object comes from the parameters.
74+
75+
## Accessing method parameters
76+
77+
All parameters passed to the method can be accessed in the `@Security` expression.
78+
79+
```php
80+
/**
81+
* @Query
82+
* @Security("startDate < endDate", statusCode=400, message="End date must be after start date")
83+
*/
84+
public function getPosts(DateTimeImmutable $startDate, DateTimeImmutable $endDate): array
85+
{
86+
// ...
87+
}
88+
```
89+
90+
In the example above, we tweak a bit the Security annotation purpose to do simple input validation.
91+
92+
## Setting HTTP code and error message
93+
94+
You can use the `statusCode` and `message` attributes to set the HTTP code and GraphQL error message.
95+
96+
```php
97+
/**
98+
* @Query
99+
* @Security("is_granted('POST_SHOW', post)", statusCode=404, message="Post not found (let's pretend the post does not exists!)")
100+
*/
101+
public function getPost(Post $post): array
102+
{
103+
// ...
104+
}
105+
```
106+
107+
Note: since a single GraphQL call contain many errors, 2 errors might have conflicting HTTP status code.
108+
The resulting status code is up to the GraphQL middleware you use. Most of the time, the status code with the
109+
higher error code will be returned.
110+
111+
## Setting a default value
112+
113+
If you do not want an error to be thrown when the security condition is not met, you can use the `failWith` attribute
114+
to set a default value.
115+
116+
```php
117+
/**
118+
* @Field
119+
* @Security("is_granted('CAN_SEE_MARGIN', this)", failWith=null)
120+
*/
121+
public function getMargin(): float
122+
{
123+
// ...
124+
}
125+
```
126+
127+
The `failWith` attribute behaves just like the [`@FailWith` annotation](authentication_authorization.md#not-throwing-errors)
128+
but for a given `@Security` annotation.
129+
130+
You cannot use the `failWith` attribute along `statusCode` or `message` attributes.
131+
132+
## Accessing the user
133+
134+
You can use the `user` variable to access the currently logged user.
135+
You can use the `is_logged()` function to check if a user is logged or not.
136+
137+
```php
138+
/**
139+
* @Query
140+
* @Security("is_logged() && user.age > 18")
141+
*/
142+
public function getNSFWImages(): array
143+
{
144+
// ...
145+
}
146+
```
147+
148+
## Accessing the current object
149+
150+
You can use the `this` variable to access any (public) property / method of the current class.
151+
152+
```php
153+
class Post {
154+
/**
155+
* @Query
156+
* @Security("this.canAccess(post, user)")
157+
*/
158+
public function getPost(Post $post): array
159+
{
160+
// ...
161+
}
162+
163+
public function canAccess(Post $post, User $user): bool
164+
{
165+
// Some custom logic here
166+
}
167+
}
168+
```
169+
170+
## Available scope
171+
172+
The `@Security` annotation can be used in any query, mutation or field, so anywhere you have a `@Query`, `@Mutation`
173+
or `@Field` annotation.
174+
175+
## How to restrict access to a given resource
176+
177+
The `is_granted` method can be used to restrict access to a specific resource.
178+
179+
```php
180+
@Security("is_granted('POST_SHOW', post)")
181+
```
182+
183+
If you are wondering how to configure these fine-grained permissions, this is not something that GraphQLite handles
184+
itself. Instead, this depends on the framework you are using.
185+
186+
If you are using Symfony, you will [create a custom voter](https://symfony.com/doc/current/security/voters.html).
187+
188+
If you are using Laravel, you will [create a Gate or a Policy](https://laravel.com/docs/6.x/authorization).
189+
190+
If you are using another framework, you need to know that the `is_granted` function simply forwards the call to
191+
the `isAllowed` method of the configured `AuthorizationSerice`. See [Connecting GraphQLite to your framework's security module
192+
](implementing-security.md) for more details
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
---
2+
id: version-4.0-implementing-security
3+
title: Connecting GraphQLite to your framework's security module
4+
sidebar_label: Connecting security to your framework
5+
original_id: implementing-security
6+
---
7+
8+
<div class="alert alert-info">
9+
This step is NOT necessary for users using GraphQLite through the Symfony Bundle or the Laravel package
10+
</div>
11+
12+
GraphQLite needs to know if a user is logged or not, and what rights it has.
13+
But this is specific of the framework you use.
14+
15+
To plug GraphQLite to your framework's security mechanism, you will have to provide two classes implementing:
16+
17+
* `TheCodingMachine\GraphQLite\Security\AuthenticationServiceInterface`
18+
* `TheCodingMachine\GraphQLite\Security\AuthorizationServiceInterface`
19+
20+
Those two interfaces act as adapters between GraphQLite and your framework:
21+
22+
```php
23+
interface AuthenticationServiceInterface
24+
{
25+
/**
26+
* Returns true if the "current" user is logged
27+
*/
28+
public function isLogged(): bool;
29+
30+
/**
31+
* Returns an object representing the current logged user.
32+
* Can return null if the user is not logged.
33+
*/
34+
public function getUser(): ?object;
35+
}
36+
```
37+
38+
```php
39+
interface AuthorizationServiceInterface
40+
{
41+
/**
42+
* Returns true if the "current" user has access to the right "$right"
43+
*
44+
* @param mixed $subject The scope this right applies on. $subject is typically an object or a FQCN. Set $subject to "null" if the right is global.
45+
*/
46+
public function isAllowed(string $right, $subject = null): bool;
47+
}
48+
```
49+
50+
You need to write classes that implement these interfaces. Then, you must register those classes with GraphQLite.
51+
It you are [using the `SchemaFactory`](other_frameworks.md), you can register your classes using:
52+
53+
```php
54+
// Configure an authentication service (to resolve the @Logged annotations).
55+
$schemaFactory->setAuthenticationService($myAuthenticationService);
56+
// Configure an authorization service (to resolve the @Right annotations).
57+
$schemaFactory->setAuthorizationService($myAuthorizationService);
58+
```
59+
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
---
2+
id: version-4.0-inheritance-interfaces
3+
title: Inheritance and interfaces
4+
sidebar_label: Inheritance and interfaces
5+
original_id: inheritance-interfaces
6+
---
7+
8+
## Modeling inheritance
9+
10+
Some of your entities may extend other entities. GraphQLite will do its best to represent this hierarchy of objects in GraphQL using interfaces.
11+
12+
Let's say you have two classes, `Contact` and `User` (which extends `Contact`):
13+
14+
```php
15+
/**
16+
* @Type
17+
*/
18+
class Contact
19+
{
20+
// ...
21+
}
22+
23+
/**
24+
* @Type
25+
*/
26+
class User extends Contact
27+
{
28+
// ...
29+
}
30+
```
31+
32+
Now, let's assume you have a query that returns a contact:
33+
34+
```php
35+
class ContactController
36+
{
37+
/**
38+
* @Query()
39+
*/
40+
public function getContact(): Contact
41+
{
42+
// ...
43+
}
44+
}
45+
```
46+
47+
When writing your GraphQL query, you are able to use fragments to retrieve fields from the `User` type:
48+
49+
```graphql
50+
contact {
51+
name
52+
... User {
53+
email
54+
}
55+
}
56+
```
57+
58+
Written in [GraphQL type language](https://graphql.org/learn/schema/#type-language), the representation of types
59+
would look like this:
60+
61+
```graphql
62+
interface ContactInterface {
63+
// List of fields declared in Contact class
64+
}
65+
66+
type Contact implements ContactInterface {
67+
// List of fields declared in Contact class
68+
}
69+
70+
type User implements ContactInterface {
71+
// List of fields declared in Contact and User classes
72+
}
73+
```
74+
75+
Behind the scene, GraphQLite will detect that the `Contact` class is extended by the `User` class.
76+
Because the class is extended, a GraphQL `ContactInterface` interface is created dynamically.
77+
78+
The GraphQL `User` type will also automatically implement this `ContactInterface`. The interface contains all the fields
79+
available in the `Contact` type.
80+
81+
## Mapping interfaces
82+
83+
If you want to create a pure GraphQL interface, you can also add a `@Type` annotation on a PHP interface.
84+
85+
```php
86+
/**
87+
* @Type
88+
*/
89+
interface UserInterface
90+
{
91+
/**
92+
* @Field
93+
*/
94+
public function getUserName(): string;
95+
}
96+
```
97+
98+
This will automatically create a GraphQL interface whose description is:
99+
100+
```graphql
101+
interface UserInterface {
102+
userName: String!
103+
}
104+
```
105+
106+
### Implementing interfaces
107+
108+
You don't have to do anything special to implement an interface in your GraphQL types.
109+
Simply "implement" the interface in PHP and you are done!
110+
111+
```php
112+
/**
113+
* @Type
114+
*/
115+
class User implements UserInterface
116+
{
117+
public function getUserName(): string;
118+
}
119+
```
120+
121+
This will translate in GraphQL schema as:
122+
123+
```graphql
124+
interface UserInterface {
125+
userName: String!
126+
}
127+
128+
type User implements UserInterface {
129+
userName: String!
130+
}
131+
```
132+
133+
Please note that you do not need to put the `@Field` annotation again in the implementing class.
134+
135+
### Interfaces without an explicit implementing type
136+
137+
You don't have to explicitly put a `@Type` annotation on the class implementing the interface (though this
138+
is usually a good idea).
139+
140+
```php
141+
/**
142+
* Look, this class has no @Type annotation
143+
*/
144+
class User implements UserInterface
145+
{
146+
public function getUserName(): string;
147+
}
148+
```
149+
150+
```php
151+
class UserController
152+
{
153+
/**
154+
* @Query()
155+
*/
156+
public function getUser(): UserInterface // This will work!
157+
{
158+
// ...
159+
}
160+
}
161+
```
162+
163+
<div class="alert alert-info">If GraphQLite cannot find a proper GraphQL Object type implementing an interface, it
164+
will create an object type "on the fly".</div>
165+
166+
In the example above, because the `User` class has no `@Type` annotations, GraphQLite will
167+
create a `UserImpl` type that implements `UserInterface`.
168+
169+
```graphql
170+
interface UserInterface {
171+
userName: String!
172+
}
173+
174+
type UserImpl implements UserInterface {
175+
userName: String!
176+
}
177+
```
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
---
2+
id: version-4.0-input-types
3+
title: Input types
4+
sidebar_label: Input types
5+
original_id: input-types
6+
---
7+
8+
Let's admit you are developing an API that returns a list of cities around a location.
9+
10+
Your GraphQL query might look like this:
11+
12+
```php
13+
class MyController
14+
{
15+
/**
16+
* @Query
17+
* @return City[]
18+
*/
19+
public function getCities(Location $location, float $radius): array
20+
{
21+
// Some code that returns an array of cities.
22+
}
23+
}
24+
25+
// Class Location is a simple value-object.
26+
class Location
27+
{
28+
private $latitude;
29+
private $longitude;
30+
31+
public function __construct(float $latitude, float $longitude)
32+
{
33+
$this->latitude = $latitude;
34+
$this->longitude = $longitude;
35+
}
36+
37+
public function getLatitude(): float
38+
{
39+
return $this->latitude;
40+
}
41+
42+
public function getLongitude(): float
43+
{
44+
return $this->longitude;
45+
}
46+
}
47+
```
48+
49+
If you try to run this code, you will get the following error:
50+
51+
```
52+
CannotMapTypeException: cannot map class "Location" to a known GraphQL input type. Check your TypeMapper configuration.
53+
```
54+
55+
You are running into this error because GraphQLite does not know how to handle the `Location` object.
56+
57+
In GraphQL, an object passed in parameter of a query or mutation (or any field) is called an **Input Type**.
58+
59+
In order to declare that type, in GraphQLite, we will declare a **Factory**.
60+
61+
A **Factory** is a method that takes in parameter all the fields of the input type and return an object.
62+
63+
Here is an example of factory:
64+
65+
```
66+
class MyFactory
67+
{
68+
/**
69+
* The Factory annotation will create automatically a LocationInput input type in GraphQL.
70+
*
71+
* @Factory()
72+
*/
73+
public function createLocation(float $latitude, float $longitude): Location
74+
{
75+
return new Location($latitude, $longitude);
76+
}
77+
}
78+
```
79+
80+
and now, you can run query like this:
81+
82+
```
83+
mutation {
84+
getCities(location: {
85+
latitude: 45.0,
86+
longitude: 0.0,
87+
},
88+
radius: 42)
89+
{
90+
id,
91+
name
92+
}
93+
}
94+
```
95+
96+
- Factories must be declared with the **@Factory** annotation.
97+
- The parameters of the factories are the field of the GraphQL input type
98+
99+
A few important things to notice:
100+
101+
- The container MUST contain the factory class. The identifier of the factory MUST be the fully qualified class name of the class that contains the factory.
102+
This is usually already the case if you are using a container with auto-wiring capabilities
103+
- We recommend that you put the factories in the same directories as the types.
104+
105+
### Specifying the input type name
106+
107+
The GraphQL input type name is derived from the return type of the factory.
108+
109+
Given the factory below, the return type is "Location", therefore, the GraphQL input type will be named "LocationInput".
110+
111+
```
112+
/**
113+
* @Factory()
114+
*/
115+
public function createLocation(float $latitude, float $longitude): Location
116+
{
117+
return new Location($latitude, $longitude);
118+
}
119+
```
120+
121+
In case you want to override the input type name, you can use the "name" attribute of the @Factory annotation:
122+
123+
```
124+
/**
125+
* @Factory(name="MyNewInputName", default=true)
126+
*/
127+
```
128+
129+
Note that you need to add the "default" attribute is you want your factory to be used by default (more on this in
130+
the next chapter).
131+
132+
Unless you want to have several factories for the same PHP class, the input type name will be completely transparent
133+
to you, so there is no real reason to customize it.
134+
135+
### Forcing an input type
136+
137+
You can use the `@UseInputType` annotation to force an input type of a parameter.
138+
139+
Let's say you want to force a parameter to be of type "ID", you can use this:
140+
141+
```php
142+
/**
143+
* @Factory()
144+
* @UseInputType(for="$id", inputType="ID!")
145+
*/
146+
public function getProductById(string $id): Product
147+
{
148+
return $this->productRepository->findById($id);
149+
}
150+
```
151+
152+
### Declaring several input types for the same PHP class
153+
<small>Available in GraphQLite 4.0+</small>
154+
155+
There are situations where a given PHP class might use one factory or another depending on the context.
156+
157+
This is often the case when your objects map database entities.
158+
In these cases, you can use combine the use of `@UseInputType` and `@Factory` annotation to achieve your goal.
159+
160+
Here is an annotated sample:
161+
162+
```php
163+
/**
164+
* This class contains 2 factories to create Product objects.
165+
* The "getProduct" method is used by default to map "Product" classes.
166+
* The "createProduct" method will generate another input type named "CreateProductInput"
167+
*/
168+
class ProductFactory
169+
{
170+
// ...
171+
172+
/**
173+
* This factory will be used by default to map "Product" classes.
174+
* @Factory(name="ProductRefInput", default=true)
175+
*/
176+
public function getProduct(string $id): Product
177+
{
178+
return $this->productRepository->findById($id);
179+
}
180+
/**
181+
* We specify a name for this input type explicitly.
182+
* @Factory(name="CreateProductInput", default=false)
183+
*/
184+
public function createProduct(string $name, string $type): Product
185+
{
186+
return new Product($name, $type);
187+
}
188+
}
189+
190+
class ProductController
191+
{
192+
/**
193+
* The "createProduct" factory will be used for this mutation.
194+
*
195+
* @Mutation
196+
* @UseInputType(for="$product", inputType="CreateProductInput!")
197+
*/
198+
public function saveProduct(Product $product): Product
199+
{
200+
// ...
201+
}
202+
203+
/**
204+
* The default "getProduct" factory will be used for this query.
205+
*
206+
* @Query
207+
* @return Color[]
208+
*/
209+
public function availableColors(Product $product): array
210+
{
211+
// ...
212+
}
213+
}
214+
```
215+
216+
### Ignoring some parameters
217+
<small>Available in GraphQLite 4.0+</small>
218+
219+
GraphQLite will automatically map all your parameters to an input type.
220+
But sometimes, you might want to avoid exposing some of those parameters.
221+
222+
Image your `getProductById` has an additional `lazyLoad` parameter. This parameter is interesting when you call
223+
directly the function in PHP because you can have some level of optimisation on your code. But it is not something that
224+
you want to expose in the GraphQL API. Let's hide it!
225+
226+
```php
227+
/**
228+
* @Factory()
229+
* @HideParameter(for="$lazyLoad")
230+
*/
231+
public function getProductById(string $id, bool $lazyLoad = true): Product
232+
{
233+
return $this->productRepository->findById($id, $lazyLoad);
234+
}
235+
```
236+
237+
With the `@HideParameter` annotation, you can choose to remove from the GraphQL schema any argument.
238+
239+
To be able to hide an argument, the argument must have a default value.
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
---
2+
id: version-4.0-internals
3+
title: Internals
4+
sidebar_label: Internals
5+
original_id: internals
6+
---
7+
<script src="https://unpkg.com/mermaid@8.0.0/dist/mermaid.min.js"></script>
8+
9+
## Mapping types
10+
11+
The core of GraphQLite is its ability to map PHP types to GraphQL types. This mapping is performed by a series of
12+
"type mappers".
13+
14+
GraphQLite contains 4 categories of type mappers:
15+
16+
- **Parameter mappers**
17+
- **Root type mappers**
18+
- **Recursive (class) type mappers**
19+
- **(class) type mappers**
20+
21+
22+
<script>
23+
mermaid.initialize({
24+
theme: 'forest',
25+
// themeCSS: '.node rect { fill: red; }',
26+
logLevel: 3,
27+
flowchart: { curve: 'linear' },
28+
gantt: { axisFormat: '%m/%d/%Y' },
29+
sequence: { actorMargin: 50 },
30+
});
31+
</script>
32+
<div class="mermaid">
33+
graph TD
34+
classDef custom fill:#cfc,stroke:#7a7,stroke-width:2px,stroke-dasharray: 5, 5;
35+
subgraph RootTypeMapperInterface
36+
NullableTypeMapperAdapter-->CompoundTypeMapper
37+
CompoundTypeMapper-->IteratorTypeMapper
38+
IteratorTypeMapper-->YourCustomRootTypeMapper
39+
YourCustomRootTypeMapper-->MyCLabsEnumTypeMapper
40+
MyCLabsEnumTypeMapper-->BaseTypeMapper
41+
BaseTypeMapper-->FinalRootTypeMapper
42+
end
43+
subgraph RecursiveTypeMapperInterface
44+
BaseTypeMapper-->RecursiveTypeMapper
45+
end
46+
subgraph TypeMapperInterface
47+
RecursiveTypeMapper-->YourCustomTypeMapper
48+
YourCustomTypeMapper-->PorpaginasTypeMapper
49+
PorpaginasTypeMapper-->GlobTypeMapper
50+
end
51+
class YourCustomRootTypeMapper,YourCustomTypeMapper custom;
52+
53+
</div>
54+
55+
## Root type mappers
56+
57+
(Classes implementing the [`RootTypeMapperInterface`](https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Root/RootTypeMapperInterface.php))
58+
59+
These type mappers are the first type mappers called.
60+
61+
They are responsible for:
62+
63+
- mapping scalar types (for instance mapping the "int" PHP type to GraphQL Integer type)
64+
- detecting nullable/non-nullable types (for instance interpreting "?int" or "int|null")
65+
- mapping list types (mapping a PHP array to a GraphQL list)
66+
- mapping union types
67+
- mapping enums
68+
69+
Root type mappers have access to the *context* of a type: they can access the PHP DocBlock and read annotations.
70+
If you want to write a custom type mapper that needs access to annotations, it needs to be a "root type mapper".
71+
72+
GraphQLite provides 6 classes implementing `RootTypeMapperInterface`:
73+
74+
- `NullableTypeMapperAdapter`: a type mapper in charge of making GraphQL types non-nullable if the PHP type is non-nullable
75+
- `CompoundTypeMapper`: a type mapper in charge of union types
76+
- `IteratorTypeMapper`: a type mapper in charge of iterable types (for instance: `MyIterator|User[]`)
77+
- `MyCLabsEnumTypeMapper`: maps MyCLabs/enum types to GraphQL enum types
78+
- `BaseTypeMapper`: maps scalar types and lists. Passes the control to the "recursive type mappers" if an object is encountered.
79+
- `FinalRootTypeMapper`: the last type mapper of the chain, used to throw error if no other type mapper managed to handle the type.
80+
81+
Type mappers are organized in a chain; each type-mapper is responsible for calling the next type mapper.
82+
83+
<div class="mermaid">
84+
graph TD
85+
classDef custom fill:#cfc,stroke:#7a7,stroke-width:2px,stroke-dasharray: 5, 5;
86+
subgraph RootTypeMapperInterface
87+
NullableTypeMapperAdapter-->CompoundTypeMapper
88+
CompoundTypeMapper-->IteratorTypeMapper
89+
IteratorTypeMapper-->YourCustomRootTypeMapper
90+
YourCustomRootTypeMapper-->MyCLabsEnumTypeMapper
91+
MyCLabsEnumTypeMapper-->BaseTypeMapper
92+
BaseTypeMapper-->FinalRootTypeMapper
93+
end
94+
class YourCustomRootTypeMapper custom;
95+
</div>
96+
97+
98+
## Class type mappers
99+
100+
(Classes implementing the [`TypeMapperInterface`](https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/TypeMapperInterface.php))
101+
102+
Class type mappers are mapping PHP classes to GraphQL object types.
103+
104+
GraphQLite provide 3 default implementations:
105+
106+
- `CompositeTypeMapper`: a type mapper that delegates mapping to other type mappers using the Composite Design Pattern.
107+
- `GlobTypeMapper`: scans classes in a directory for the `@Type` or `@ExtendType` annotation and maps those to GraphQL types
108+
- `PorpaginasTypeMapper`: maps and class implementing the Porpaginas `Result` interface to a [special paginated type](pagination.md).
109+
110+
### Registering a type mapper in Symfony
111+
112+
If you are using the GraphQLite Symfony bundle, you can register a type mapper by tagging the service with the "graphql.type_mapper" tag.
113+
114+
### Registering a type mapper using the SchemaFactory
115+
116+
If you are using the `SchemaFactory` to bootstrap GraphQLite, you can register a type mapper using the `SchemaFactory::addTypeMapper` method.
117+
118+
## Recursive type mappers
119+
120+
(Classes implementing the [`RecursiveTypeMapperInterface`](https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/RecursiveTypeMapperInterface.php))
121+
122+
There is only one implementation of the `RecursiveTypeMapperInterface`: the `RecursiveTypeMapper`.
123+
124+
Standard "class type mappers" are mapping a given PHP class to a GraphQL type. But they do not handle class hierarchies.
125+
This is the role of the "recursive type mapper".
126+
127+
Imagine that class "B" extends class "A" and class "A" maps to GraphQL type "AType".
128+
129+
Since "B" *is a* "A", the "recursive type mapper" role is to make sure that "B" will also map to GraphQL type "AType".
130+
131+
## Parameter mapper middlewares
132+
133+
"Parameter middlewares" are used to decide what argument should be injected into a parameter.
134+
135+
Let's have a look at a simple query:
136+
137+
```php
138+
/**
139+
* @Query
140+
* @return Product[]
141+
*/
142+
public function products(ResolveInfo $info): array
143+
```
144+
145+
As you may know, [the `ResolveInfo` object injected in this query comes from Webonyx/GraphQL-PHP library](query_plan.md).
146+
GraphQLite knows that is must inject a `ResolveInfo` instance because it comes with a [`ResolveInfoParameterHandler`](https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ResolveInfoParameterHandler.php) class
147+
that implements the [`ParameterMiddlewareInterface`](https://github.com/thecodingmachine/graphqlite/blob/master/src/Mappers/Parameters/ParameterMiddlewareInterface.php)).
148+
149+
You can register your own parameter middlewares using the `SchemaFactory::addParameterMiddleware()` method, or by tagging the
150+
service as "graphql.parameter_middleware" if you are using the Symfony bundle.
151+
152+
<div class="alert alert-info">Use a parameter middleware if you want to inject an argument in a method and if this argument
153+
is not a GraphQL input type or if you want to alter the way input types are imported (for instance if you want to add a validation step)</div>
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
---
2+
id: version-4.0-laravel-package-advanced
3+
title: Laravel package: advanced usage
4+
sidebar_label: Laravel specific features
5+
original_id: laravel-package-advanced
6+
---
7+
8+
The Laravel package comes with a number of features to ease the integration of GraphQLite in Laravel.
9+
10+
## Support for Laravel validation rules
11+
12+
The GraphQLite Laravel package comes with a special `@Validate` annotation to use Laravel validation rules in your
13+
input types.
14+
15+
```php
16+
use TheCodingMachine\GraphQLite\Laravel\Annotations\Validate;
17+
18+
class MyController
19+
{
20+
/**
21+
* @Mutation
22+
* @Validate(for="$email", rule="email|unique:users")
23+
* @Validate(for="$password", rule="gte:8")
24+
*/
25+
public function createUser(string $email, string $password): User
26+
{
27+
// ...
28+
}
29+
}
30+
```
31+
32+
You can use the `@Validate` annotation in any query / mutation / field / factory / decorator.
33+
34+
If a validation fails to pass, the message will be printed in the "errors" section and you will get a HTTP 400 status code:
35+
36+
```json
37+
{
38+
"errors": [
39+
{
40+
"message": "The email must be a valid email address.",
41+
"extensions": {
42+
"argument": "email",
43+
"category": "Validate"
44+
}
45+
},
46+
{
47+
"message": "The password must be greater than or equal 8 characters.",
48+
"extensions": {
49+
"argument": "password",
50+
"category": "Validate"
51+
}
52+
}
53+
]
54+
}
55+
```
56+
57+
You can use any validation rule described in [the Laravel documentation](https://laravel.com/docs/6.x/validation#available-validation-rules)
58+
59+
## Support for pagination
60+
61+
In your query, if you explicitly return an object that extends the `Illuminate\Pagination\LengthAwarePaginator` class,
62+
the query result will be wrapped in a "paginator" type.
63+
64+
```php
65+
class MyController
66+
{
67+
/**
68+
* @Query
69+
* @return Product[]
70+
*/
71+
public function products(): Illuminate\Pagination\LengthAwarePaginator
72+
{
73+
return Product::paginate(15);
74+
}
75+
}
76+
```
77+
78+
Notice that:
79+
80+
- the method return type MUST BE `Illuminate\Pagination\LengthAwarePaginator` or a class extending `Illuminate\Pagination\LengthAwarePaginator`
81+
- you MUST add a `@return` statement to help GraphQLite find the type of the list
82+
83+
Once this is done, you can get plenty of useful information about this page:
84+
85+
```
86+
products {
87+
items { # The items for the selected page
88+
id
89+
name
90+
}
91+
totalCount # The total count of items.
92+
lastPage # Get the page number of the last available page.
93+
firstItem # Get the "index" of the first item being paginated.
94+
lastItem # Get the "index" of the last item being paginated.
95+
hasMorePages # Determine if there are more items in the data source.
96+
perPage # Get the number of items shown per page.
97+
hasPages # Determine if there are enough items to split into multiple pages.
98+
currentPage # Determine the current page being paginated.
99+
isEmpty # Determine if the list of items is empty or not.
100+
isNotEmpty # Determine if the list of items is not empty.
101+
}
102+
```
103+
104+
105+
<div class="alert alert-warning">Be sure to type hint on the class (<code>Illuminate\Pagination\LengthAwarePaginator</code>)
106+
and not on the interface (<code>Illuminate\Contracts\Pagination\LengthAwarePaginator</code>). The interface
107+
itself is not iterable (it does not extend <code>Traversable</code>) and therefore, GraphQLite will refuse to
108+
iterate over it.</div>
109+
110+
### Simple paginator
111+
112+
Note: if you are using `simplePaginate` instead of `paginate`, you can type hint on the `Illuminate\Pagination\Paginator` class.
113+
114+
```php
115+
class MyController
116+
{
117+
/**
118+
* @Query
119+
* @return Product[]
120+
*/
121+
public function products(): Illuminate\Pagination\Paginator
122+
{
123+
return Product::simplePaginate(15);
124+
}
125+
}
126+
```
127+
128+
The behaviour will be exactly the same except you will be missing the `totalCount` and `lastPage` fields.
129+
130+
## Using GraphQLite with Eloquent efficiently
131+
132+
In GraphQLite, you are supposed to put a `@Field` annotation on each getter.
133+
134+
Eloquent uses PHP magic properties to expose your database records.
135+
Because Eloquent relies on magic properties, it is quite rare for an Eloquent model to have proper getters and setters.
136+
137+
So we need to find a workaround. GraphQLite comes with a `@MagicField` annotation to help you
138+
working with magic properties.
139+
140+
```php
141+
/**
142+
* @Type()
143+
* @MagicField(name="id" outputType="ID!")
144+
* @MagicField(name="name" phpType="string")
145+
* @MagicField(name="categories" phpType="Category[]")
146+
*/
147+
class Product extends Model
148+
{
149+
}
150+
```
151+
152+
Please note that since the properties are "magic", they don't have a type. Therefore,
153+
you need to pass either the "outputType" attribute with the GraphQL type matching the property,
154+
or the "phpType" attribute with the PHP type matching the property.
155+
156+
### Pitfalls to avoid with Eloquent
157+
158+
When designing relationships in Eloquent, you write a method to expose that relationship this way:
159+
160+
```php
161+
class User extends Model
162+
{
163+
/**
164+
* Get the phone record associated with the user.
165+
*/
166+
public function phone()
167+
{
168+
return $this->hasOne('App\Phone');
169+
}
170+
}
171+
```
172+
173+
It would be tempting to put a `@Field` annotation on the `phone()` method, but this will not work. Indeed,
174+
the `phone()` method does not return a `App\Phone` object. It is the `phone` magic property that returns it.
175+
176+
In short:
177+
178+
<div class="alert alert-error">This does not work:
179+
<pre><code class="hljs css language-php">
180+
class User extends Model
181+
{
182+
/**
183+
* @Field
184+
*/
185+
public function phone()
186+
{
187+
return $this->hasOne('App\Phone');
188+
}
189+
}</code></pre>
190+
</div>
191+
192+
<div class="alert alert-success">This works:
193+
<pre><code class="hljs css language-php">
194+
/**
195+
* @MagicField(name="phone", phpType="App\\Phone")
196+
*/
197+
class User extends Model
198+
{
199+
public function phone()
200+
{
201+
return $this->hasOne('App\Phone');
202+
}
203+
}</code></pre>
204+
</div>
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
---
2+
id: version-4.0-laravel-package
3+
title: Getting started with Laravel
4+
sidebar_label: Laravel package
5+
original_id: laravel-package
6+
---
7+
8+
The GraphQLite-Laravel package is compatible with **Laravel 5.7+** and **Laravel 6.x**.
9+
10+
## Installation
11+
12+
Open a terminal in your current project directory and run:
13+
14+
```console
15+
$ composer require thecodingmachine/graphqlite-laravel
16+
```
17+
18+
If you want to publish the configuration (in order to edit it), run:
19+
20+
```console
21+
$ php artisan vendor:publish --provider=TheCodingMachine\GraphQLite\Laravel\Providers\GraphQLiteServiceProvider
22+
```
23+
24+
You can then configure the library by editing `config/graphqlite.php`.
25+
26+
**config/graphqlite.php**
27+
```php
28+
<?php
29+
30+
use GraphQL\Error\Debug;
31+
32+
return [
33+
/*
34+
|--------------------------------------------------------------------------
35+
| GraphQLite Configuration
36+
|--------------------------------------------------------------------------
37+
|
38+
| Use this configuration to customize the namespace of the controllers and
39+
| types.
40+
| These namespaces must be autoloadable from Composer.
41+
| GraphQLite will find the path of the files based on composer.json settings.
42+
|
43+
| You can put a single namespace, or an array of namespaces.
44+
|
45+
*/
46+
'controllers' => 'App\\Http\\Controllers',
47+
'types' => 'App\\',
48+
'debug' => Debug::RETHROW_UNSAFE_EXCEPTIONS,
49+
'uri' => env('GRAPHQLITE_URI', '/graphql'),
50+
'middleware' => ['web'],
51+
'guard' => ['web'],
52+
];
53+
```
54+
55+
The debug parameters are detailed in the [documentation of the Webonyx GraphQL library](https://webonyx.github.io/graphql-php/error-handling/)
56+
which is used internally by GraphQLite.
57+
58+
## Configuring CSRF protection
59+
60+
<div class="alert alert-warning">By default, the <code>/graphql</code> route is placed under <code>web</code> middleware group which requires a
61+
<a href="https://laravel.com/docs/6.x/csrf">CSRF token</a>.</div>
62+
63+
You have 3 options:
64+
65+
- Use the `api` middleware
66+
- Disable CSRF for GraphQL routes
67+
- or configure your GraphQL client to pass the `X-CSRF-TOKEN` with every GraphQL query
68+
69+
### Use the `api` middleware
70+
71+
If you plan to use graphql for server-to-server connection only, you should probably configure GraphQLite to use the
72+
`api` middleware instead of the `web` middleware:
73+
74+
**config/graphqlite.php**
75+
```php
76+
<?php
77+
return [
78+
'middleware' => ['api'],
79+
'guard' => ['api'],
80+
];
81+
```
82+
83+
### Disable CSRF for the /graphql route
84+
85+
If you plan to use graphql from web browsers and if you want to explicitly allow access from external applications
86+
(through CORS headers), you need to disable the CSRF token.
87+
88+
Simply add `graphql` to `$except` in `app/Http/Middleware/VerifyCsrfToken.php`.
89+
90+
### Configuring your GraphQL client
91+
92+
If you are planning to use `graphql` only from your website domain, then the safest way is to keep CSRF enabled and
93+
configure your GraphQL JS client to pass the CSRF headers on any graphql request.
94+
95+
The way you do this depends on the Javascript GraphQL client you are using.
96+
97+
Assuming you are using [Apollo](https://www.apollographql.com/docs/link/links/http/), you need to be sure that Apollo passes the token
98+
back to Laravel on every request.
99+
100+
**Sample Apollo client setup with CSRF support**
101+
```js
102+
import { ApolloClient, ApolloLink, InMemoryCache, HttpLink } from 'apollo-boost';
103+
104+
const httpLink = new HttpLink({ uri: 'https://api.example.com/graphql' });
105+
106+
const authLink = new ApolloLink((operation, forward) => {
107+
// Retrieve the authorization token from local storage.
108+
const token = localStorage.getItem('auth_token');
109+
110+
// Get the XSRF-TOKEN that is set by Laravel on each request
111+
var cookieValue = document.cookie.replace(/(?:(?:^|.*;\s*)XSRF-TOKEN\s*\=\s*([^;]*).*$)|^.*$/, "$1");
112+
113+
// Use the setContext method to set the X-CSRF-TOKEN header back.
114+
operation.setContext({
115+
headers: {
116+
'X-CSRF-TOKEN': cookieValue
117+
}
118+
});
119+
120+
// Call the next link in the middleware chain.
121+
return forward(operation);
122+
});
123+
124+
const client = new ApolloClient({
125+
link: authLink.concat(httpLink), // Chain it with the HttpLink
126+
cache: new InMemoryCache()
127+
});
128+
```
129+
130+
## Adding GraphQL DevTools
131+
132+
GraphQLite does not include additional GraphQL tooling, such as the GraphiQL editor.
133+
To integrate a web UI to query your GraphQL endpoint with your Laravel installation,
134+
we recommend installing [GraphQL Playground](https://github.com/mll-lab/laravel-graphql-playground)
135+
136+
```console
137+
$ composer require mll-lab/laravel-graphql-playground
138+
```
139+
140+
By default, the playground will be available at `/graphql-playground`.
141+
142+
You can also use any external client with GraphQLite, make sure to point it to the URL defined in the config (`'/graphql'` by default).
143+
144+
## Troubleshooting HTTP 419 errors
145+
146+
If HTTP requests to GraphQL endpoint generate responses with the HTTP 419 status code, you have an issue with the configuration of your
147+
CSRF token. Please check again [the paragraph dedicated to CSRF configuration](#configuring-csrf-protection).
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
id: version-4.0-migrating
3+
title: Migrating
4+
sidebar_label: Migrating
5+
original_id: migrating
6+
---
7+
8+
## Migrating from v3.0 to v4.0
9+
10+
If you are a "regular" GraphQLite user, migration to v4 should be straightforward:
11+
12+
- Annotations are mostly untouched. The only annotation that is changed is the `@SourceField` annotation.
13+
- Check your code for every places where you use the `@SourceField` annotation:
14+
- The "id" attribute has been remove (`@SourceField(id=true)`). Instead, use `@SourceField(outputType="ID")`
15+
- The "logged", "right" and "failWith" attributes have been removed (`@SourceField(logged=true)`).
16+
Instead, use the annotations attribute with the same annotations you use for the `@Field` annotation:
17+
`@SourceField(annotations={@Logged, @FailWith(null)})`
18+
- If you use magic property and were creating a getter for every magic property (to put a `@Field` annotation on it),
19+
you can now replace this getter with a `@MagicField` annotation.
20+
- In GraphQLite v3, the default was to hide a field from the schema if a user has no access to it.
21+
In GraphQLite v4, the default is to still show this field, but to throw an error if the user makes a query on it
22+
(this way, the schema is the same for all users). If you want the old mode, use the new
23+
[`@HideIfUnauthorized` annotation](annotations_reference.md#hideifunauthorized-annotation)
24+
- If you are using the Symfony bundle, the Laravel package or the Universal module, you must also upgrade those to 4.0.
25+
These package will take care of the wiring for you. Apart for upgrading the packages, you have nothing to do.
26+
- If you are relying on the `SchemaFactory` to bootstrap GraphQLite, you have nothing to do.
27+
28+
On the other hand, if you are a power user and if you are wiring GraphQLite services yourself (without using the
29+
`SchemaFactory`) or if you implemented custom "TypeMappers", you will need to adapt your code:
30+
31+
- The `FieldsBuilderFactory` is gone. Directly instantiate `FieldsBuilder` in v4.
32+
- The `CompositeTypeMapper` class has no more constructor arguments. Use the `addTypeMapper` method to register
33+
type mappers in it.
34+
- The `FieldsBuilder` now accept an extra argument: the `RootTypeMapper` that you need to instantiate accordingly. Take
35+
a look at the `SchemaFactory` class for an example of proper configuration.
36+
- The `HydratorInterface` and all implementations are gone. When returning an input object from a TypeMapper, the object
37+
must now implement the `ResolvableMutableInputInterface` (an input object type that contains its own resolver)
38+
39+
Note: we strongly recommend to use the Symfony bundle, the Laravel package, the Universal module or the SchemaManager
40+
to bootstrap GraphQLite. Wiring directly GraphQLite classes (like the `FieldsBuilder`) into your container is not recommended,
41+
as the signature of the constructor of those classes may vary from one minor release to another.
42+
Use the `SchemaManager` instead.
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
---
2+
id: version-4.0-multiple_output_types
3+
title: Mapping multiple output types for the same class
4+
sidebar_label: Class with multiple output types
5+
original_id: multiple_output_types
6+
---
7+
<small>Available in GraphQLite 4.0+</small>
8+
9+
In most cases, you have one PHP class and you want to map it to one GraphQL output type.
10+
11+
But in very specific cases, you may want to use different GraphQL output type for the same class.
12+
For instance, depending on the context, you might want to prevent the user from accessing some fields of your object.
13+
14+
To do so, you need to create 2 output types for the same PHP class. You typically do this using the "default" attribute of the `@Type` annotation.
15+
16+
## Example
17+
18+
Here is an example. Say we are manipulating products. When I query a `Product` details, I want to have access to all fields.
19+
But for some reason, I don't want to expose the price field of a product if I query the list of all products.
20+
21+
```php
22+
/**
23+
* @Type()
24+
*/
25+
class Product
26+
{
27+
// ...
28+
29+
/**
30+
* @Field()
31+
*/
32+
public function getName(): string
33+
{
34+
return $this->name;
35+
}
36+
37+
/**
38+
* @Field()
39+
*/
40+
public function getPrice(): ?float
41+
{
42+
return $this->price;
43+
}
44+
}
45+
```
46+
47+
The `Product` class is declaring a classic GraphQL output type named "Product".
48+
49+
```php
50+
/**
51+
* @Type(class=Product::class, name="LimitedProduct", default=false)
52+
* @SourceField(name="name")
53+
*/
54+
class LimitedProductType
55+
{
56+
// ...
57+
58+
/**
59+
* @Field()
60+
*/
61+
public function getName(Product $product): string
62+
{
63+
return $product->getName();
64+
}
65+
}
66+
```
67+
68+
69+
The `LimitedProductType` also declares a ["external" type](external_type_declaration.md) mapping the `Product` class.
70+
But pay special attention to the `@Type` annotation.
71+
72+
First of all, we specify `name="LimitedProduct"`. This is useful to avoid having colliding names with the "Product" GraphQL output type
73+
that is already declared.
74+
75+
Then, we specify `default=false`. This means that by default, the `Product` class should not be mapped to the `LimitedProductType`.
76+
This type will only be used when we explicitly request it.
77+
78+
Finally, we can write our requests:
79+
80+
```php
81+
class ProductController
82+
{
83+
/**
84+
* This field will use the default type.
85+
*
86+
* @Field
87+
*/
88+
public function getProduct(int $id): Product { /* ... */ }
89+
90+
/**
91+
* Because we use the "outputType" attribute, this field will use the other type.
92+
*
93+
* @Field(outputType="[LimitedProduct!]!")
94+
* @return Product[]
95+
*/
96+
public function getProducts(): array { /* ... */ }
97+
}
98+
```
99+
100+
Notice how the "outputType" attribute is used in the `@Field` annotation to force the output type.
101+
102+
Is a result, when the end user calls the `product` query, we will have the possibility to fetch the `name` and `price` fields,
103+
but if he calls the `products` query, each product in the list will have a `name` field but no `price` field. We managed
104+
to successfully expose a different set of fields based on the query context.
105+
106+
## Extending a non-default type
107+
108+
If you want to extend a type using the `@ExtendType` annotation and if this type is declared as non-default,
109+
you need to target the type by name instead of by class.
110+
111+
So instead of writing:
112+
113+
```php
114+
/**
115+
* @ExtendType(class=Product::class)
116+
*/
117+
```
118+
119+
you will write:
120+
121+
```php
122+
/**
123+
* @ExtendType(name="LimitedProduct")
124+
*/
125+
```
126+
127+
Notice how we use the "name" attribute instead of the "class" attribute in the `@ExtendType` annotation.

0 commit comments

Comments
 (0)
Please sign in to comment.