diff --git a/core/events.md b/core/events.md
index 1c535d79d4a..ee5319f7c83 100644
--- a/core/events.md
+++ b/core/events.md
@@ -1,9 +1,13 @@
# The Event System
-In API Platform 3.2 you may need `event_listeners_backward_compatibility_layer: true` to keep event listeners activated.
+> [!WARNING]
+> In API Platform 4.0 with Symfony, you need `use_symfony_listeners: true` to activate event listeners.
-Note: using Kernel event with API Platform should be mostly limited to tweaking the generated HTTP response. Also, GraphQL is **not supported**.
-[For most use cases, better extension points, working both with REST and GraphQL, are available](extending.md).
+---
+
+> [!NOTE]
+> Using Kernel event with API Platform should be mostly limited to tweaking the generated HTTP response. Also, GraphQL is **not supported**.
+> We recommend to use [System providers and processors](extending.md#system-providers-and-processors) to extend API Platform internals.
API Platform Core implements the [Action-Domain-Responder](https://github.com/pmjones/adr) pattern. This implementation
is covered in depth in the [Creating custom operations and controllers](operations.md#creating-custom-operations-and-controllers)
diff --git a/core/extending.md b/core/extending.md
index 73e393654c4..47f37af53e9 100644
--- a/core/extending.md
+++ b/core/extending.md
@@ -36,3 +36,115 @@ For instance, if you want to send a mail after a resource has been persisted, bu
To replace existing API Platform services with your decorators, [check out how to decorate services](https://symfony.com/doc/current/service_container/service_decoration.html).
Watch the Service Decoration screencast
+
+## System Providers and Processors
+
+The system is based on a workflow composed of **state providers** and **state processors**.
+
+The schema below describes them:
+
+```mermaid
+---
+title: System providers and processors
+---
+flowchart TB
+ C1(ReadProvider) --> C2(AccessCheckerProvider)
+ C2 --> C3(DeserializeProvider)
+ C3 --> C4(ParameterProvider)
+ C4 --> C5(ValidateProcessor)
+ C5 --> C6(WriteProcessor)
+ C6 --> C7(SerializeProcessor)
+```
+
+### Symfony Access Checker Provider
+
+When using Symfony, the access checker provider is used at three different stages:
+- `api_platform.state_provider.access_checker.post_validate` decorates the `ValidateProvider`
+- `api_platform.state_provider.access_checker.post_deserialize` decorates the `DeserializeProvider`
+- `api_platform.state_provider.access_checker` decorates the `ReadProvider`
+
+> [!NOTE]
+> For graphql use: `api_platform.graphql.state_provider.access_checker.post_deserialize`,
+> `api_platform.graphql.state_provider.access_checker.post_validate`, `api_platform.graphql.state_provider.validate` and
+> `api_platform.graphql.state_provider.access_checker.after_resolver`
+
+### Decoration Example
+
+Here is an example of the decoration of the RespondProcessor:
+
+Starts by creating your `CustomRespondProcessor`:
+
+```php
+processor->process($data, $operation, $uriVariables, $context);
+
+ // You can add post-write code here.
+
+ return $writtenObject;
+ }
+}
+```
+
+Now decorate the `RespondProcessor` with the `CustomRespondProcessor` using Symfony or Laravel:
+
+### Symfony Processor Decoration
+
+With Symfony you can simply do that by adding the `#[AsDecorator]` attribute as following:
+
+```php
+namespace App\State;
+
+use ApiPlatform\State\ProcessorInterface;
+
+#[AsDecorator(decorates: 'api_platform.state.processor.respond_processor')]
+final class CustomRespondProcessor implements ProcessorInterface
+{
+ // ...
+}
+```
+
+or in the `services.yaml` by defining:
+
+```yaml
+# api/config/services.yaml
+services:
+ # ...
+ App\State\CustomRespondProcessor:
+ decorates: api_platform.state.processor.respond_processor
+```
+
+And that's it!
+
+### Laravel Processor Decoration
+```php
+app->extend(RespondProcessor::class, function (RespondProcessor $respondProcessor) {
+ return new CustomRespondProcessor($respondProcessor);
+ });
+ }
+}
+```
diff --git a/core/graphql.md b/core/graphql.md
index 3392112f33c..b3125977e24 100644
--- a/core/graphql.md
+++ b/core/graphql.md
@@ -10,23 +10,25 @@ Once enabled, you have nothing to do: your schema describing your API is automat
## Enabling GraphQL
-To enable GraphQL and its IDE (GraphiQL and GraphQL Playground) in your API, simply require the [graphql-php](https://webonyx.github.io/graphql-php/) package using Composer and clear the cache one more time:
+To enable GraphQL and its IDE (GraphiQL and GraphQL Playground) in your API, simply require the `api-platform/graphql` package using Composer:
```console
-docker compose exec php sh -c '
- composer require webonyx/graphql-php
- bin/console cache:clear
-'
+ composer require api-platform/graphql
```
You can now use GraphQL at the endpoint: `https://localhost:8443/graphql`.
-*Note:* If you used [Symfony Flex to install API Platform](../symfony/index.md#using-symfony-flex-and-composer-advanced-users), URLs will be prefixed with `/api` by default. For example, the GraphQL endpoint will be: `https://localhost:8443/api/graphql`.
+> [!NOTE]
+> If you used [the Symfony Variant thanks to Symfony Flex](../symfony/index.md#using-symfony-flex-and-composer-advanced-users) or the Laravel variant, URLs will be prefixed with `/api` by default. For example, the GraphQL endpoint will be: `https://localhost:8443/api/graphql`.
## Changing Location of the GraphQL Endpoint
Sometimes you may want to have the GraphQL endpoint at a different location. This can be done by manually configuring the GraphQL controller.
+### Symfony Routes
+
+Using the Symfony variant we can do this modification by adding the following code:
+
```yaml
# api/config/routes.yaml
api_graphql_entrypoint:
@@ -37,14 +39,29 @@ api_graphql_entrypoint:
Change `/api/graphql` to the URI you wish the GraphQL endpoint to be accessible on.
+### Laravel Routes
+
+Using the Laravel variant we can do this modification by adding the following code:
+```php
+// routes/web.php
+use Illuminate\Support\Facades\Route;
+use ApiPlatform\GraphQL\Action\EntrypointAction;
+
+Route::post('/api/graphql', EntrypointAction::class)
+ ->name('api_graphql_entrypoint');
+```
+
+Change `/api/graphql` to the URI you wish the GraphQL endpoint to be accessible on.
+
## GraphiQL
-If Twig is installed in your project, go to the GraphQL endpoint with your browser. You will see a nice interface provided by GraphiQL to interact with your API.
+Go to the GraphQL endpoint with your browser, you will see a nice interface provided by GraphiQL to interact with your API.
The GraphiQL IDE can also be found at `/graphql/graphiql`.
If you need to disable it, it can be done in the configuration:
+### Disabling GraphiQL with Symfony
```yaml
# api/config/packages/api_platform.yaml
api_platform:
@@ -54,9 +71,27 @@ api_platform:
# ...
```
+### Disabling GraphiQL with Laravel
+
+```php
+ [
+ 'graphiql' => [
+ 'enabled' => false,
+ ]
+ ],
+];
+```
+
### Add another Location for GraphiQL
-If you want to add a different location besides `/graphql/graphiql`, you can do it like this:
+Sometimes you may want to have the GraphiQL at a different location. This can be done by manually configuring the GraphiQL controller.
+
+### Symfony config routes for GraphiQL
+If you want to add a different location besides `/graphql/graphiql`, you can do it like this if you are using the Symfony variant:
```yaml
# app/config/routes.yaml
@@ -65,13 +100,27 @@ graphiql:
controller: api_platform.graphql.action.graphiql
```
+### Laravel config routes for GraphiQL
+
+If you want to add a different location besides `/graphql/graphiql`, you can do it like this if you are using the Laravel variant:
+```php
+// routes/web.php
+use Illuminate\Support\Facades\Route;
+use ApiPlatform\GraphQL\Action\GraphiQlAction;
+
+Route::post('/docs/graphiql', GraphiQlAction::class)
+ ->name('graphiql');
+```
+
## GraphQL Playground
Another IDE is by default included in API Platform: GraphQL Playground.
It can be found at `/graphql/graphql_playground`.
-You can disable it if you want in the configuration:
+You can disable it if you want in the configuration.
+
+### Disable GraphQL Playground with Symfony
```yaml
# api/config/packages/api_platform.yaml
@@ -82,9 +131,17 @@ api_platform:
# ...
```
+### Disable GraphQL Playground with Laravel
+
+> [!WARNING]
+> This is not yet available with Laravel, you're welcome to contribute [on Github](github.com/api-platform/core)
+
### Add another Location for GraphQL Playground
+You can add a different location besides `/graphql/graphql_playground`.
+
+### Symfony config routes for GraphQL Playground
-You can add a different location besides `/graphql/graphql_playground`:
+Using the Symfony variant we can do this modification by adding the following code:
```yaml
# app/config/routes.yaml
@@ -93,10 +150,24 @@ graphql_playground:
controller: api_platform.graphql.action.graphql_playground
```
+### Laravel config routes for GraphQL Playground
+
+Using the Laravel variant we can do this modification by adding the following code:
+```php
+// routes/web.php
+use Illuminate\Support\Facades\Route;
+use ApiPlatform\GraphQL\Action\GraphQlPlaygroundAction;
+
+Route::post('/docs/graphql_playground', GraphQlPlaygroundAction::class)
+ ->name('graphql_playground');
+```
+
## Modifying or Disabling the Default IDE
When going to the GraphQL endpoint, you can choose to launch the IDE you want.
+### Symfony config to modifying the default IDE
+
```yaml
# api/config/packages/api_platform.yaml
api_platform:
@@ -106,8 +177,23 @@ api_platform:
# ...
```
+### Laravel config to modifying the default IDE
+
+```php
+ [
+ // Choose between graphiql or graphql-playground
+ 'default_ide' => 'graphql-playground',
+ ],
+];
+```
+
You can also disable this feature by setting the configuration value to `false`.
+### Symfony config to disable default IDE
```yaml
# api/config/packages/api_platform.yaml
api_platform:
@@ -116,10 +202,24 @@ api_platform:
# ...
```
+### Laravel config to disable default IDE
+```php
+ [
+ 'default_ide' => false,
+ ],
+];
+```
+
## Disabling the Introspection Query
For security reason, the introspection query should be disabled to not expose the GraphQL schema.
+
+### Symfony config to disable the Introspection Query
If you need to disable it, it can be done in the configuration:
```yaml
@@ -130,11 +230,27 @@ api_platform:
# ...
```
+### Laravel config to disable the Introspection Query
+If you need to disable it, it can be done in the configuration:
+
+```php
+ [
+ 'introspection' => false,
+ ],
+];
+```
+
## Request with `application/graphql` Content-Type
If you wish to send a [POST request using the `application/graphql` Content-Type](https://graphql.org/learn/serving-over-http/#post-request),
you need to enable it in the [allowed formats of API Platform](content-negotiation.md#configuring-formats-globally):
+### Symfony config for GraphQL Content-Type
+
```yaml
# api/config/packages/api_platform.yaml
api_platform:
@@ -143,8 +259,26 @@ api_platform:
graphql: ['application/graphql']
```
+### Laravel config for GraphQL Content-Type
+
+```php
+ [
+ 'graphql' => [
+ 'application/graphql',
+ ],
+ ],
+];
+```
+
## Operations
+> [!NOTE]
+> In Symfony we use the term “entities”, while the following documentation is mostly for Laravel “models”.
+
To understand what an operation is, please refer to the [operations documentation](operations.md).
For GraphQL, the operations are defined by using the `Query`, `QueryCollection`, `Mutation`, `DeleteMutation` and `Subscription` attributes.
@@ -183,6 +317,7 @@ class Book
```
```yaml
+#The YAML syntax is only supported for Symfony
resources:
App\Entity\Book:
graphQlOperations:
@@ -192,6 +327,7 @@ resources:
```
```xml
+
-
-
-
-
-
-
-
-
-
-
-
-
- ID!
-
-
-
-
-
-
-
-
- Boolean!
- Send a mail?
-
-
-
-
-
-
-
-
-```
-
-
+If you're using Laravel, don't forget to tag the resolver service with the `ApiPlatform\GraphQl\Resolver\MutationResolverInterface`.
Note that you need to explicitly add the auto-generated queries and mutations if they are needed when configuring custom mutations, like it's done for the [operations](#operations).
@@ -709,38 +748,6 @@ You can also use the `extraArgs` property in case you need to add additional arg
The arguments will be in `$context['args']['input']` of your resolvers.
-Your custom mutations will be available like this:
-
-```graphql
-{
- mutation {
- mutationBook(input: {id: "/books/18", title: "The Fitz and the Fool"}) {
- book {
- title
- }
- }
- }
-
- mutation {
- withCustomArgsMutationBook(input: {sendMail: true, clientMutationId: "myId"}) {
- book {
- title
- }
- clientMutationId
- }
- }
-
- mutation {
- disabledStagesMutationBook(input: {title: "The Fitz and the Fool"}) {
- book {
- title
- }
- clientMutationId
- }
- }
-}
-```
-
## Subscriptions
Subscriptions are an [RFC](https://github.com/graphql/graphql-spec/blob/master/rfcs/Subscriptions.md#rfc-graphql-subscriptions) to allow a client to receive pushed realtime data from the server.
@@ -780,6 +787,7 @@ class Book
```
```yaml
+#The YAML syntax is only supported for Symfony
resources:
App\Entity\Book:
graphQlOperations:
@@ -789,6 +797,7 @@ resources:
```
```xml
+
[
+ 'nesting_separator' => '__'
+ ],
+];
+```
In this case, your query will be:
@@ -2521,9 +2580,11 @@ You can also check the documentation of [graphql-php](https://webonyx.github.io/
The big difference in API Platform is that the value is already serialized when it's received in your type class.
Similarly, you would not want to denormalize your parsed value since it will be done by API Platform later.
+### Custom Types config for Symfony
+
If you use autoconfiguration (the default Symfony configuration) in your application, then you are done!
-Else, you need to tag your type class like this:
+Else, you need to tag your type class like this, if you're using Symfony :
```yaml
# api/config/services.yaml
@@ -2538,6 +2599,33 @@ Your custom type is now registered and is available in the `TypesContainer`.
To use it please [modify the extracted types](#modify-the-extracted-types) or use it directly in [custom queries](#custom-queries) or [custom mutations](#custom-mutations).
+
+### Custom Types config for Laravel
+
+If you are using Laravel tag your type with:
+
+```php
+app->tag([DateTimeType::class], TypeInterface::class);
+ }
+}
+```
+
+Your custom type is now registered and is available in the `TypesContainer`.
+
+To use it please [modify the extracted types](#modify-the-extracted-types) or use it directly in [custom queries](#custom-queries) or [custom mutations](#custom-mutations).
+
## Modify the Extracted Types
The GraphQL schema and its types are extracted from your resources.
@@ -2545,6 +2633,8 @@ In some cases, you would want to modify the extracted types for instance to use
To do so, you need to decorate the `api_platform.graphql.type_converter` service:
+### Symfony TypeConverter Decoration
+
```yaml
# api/config/services.yaml
services:
@@ -2553,7 +2643,29 @@ services:
decorates: api_platform.graphql.type_converter
```
-Your class needs to look like this:
+### Laravel TypeConverter Decoration
+
+```php
+app->extend(TypeConverterInterface::class, function (TypeConverterInterface $typeConverter) {
+ return new TypeConverter($typeConverter);
+ });
+ }
+}
+```
+
+Then, your class needs to look like this:
```php
defaultTypeConverter = $defaultTypeConverter;
- }
+ public function __construct(private readonly TypeConverterInterface $defaultTypeConverter) {}
public function convertType(Type $type, bool $input, Operation $rootOperation, string $resourceClass, string $rootResource, ?string $property, int $depth)
{
@@ -2616,6 +2724,8 @@ The service is `api_platform.graphql.serializer.context_builder` and the method
The decorator could be like this:
+### Symfony Serialization Context Decoration
+
```php
decorated = $decorated;
- $this->authorizationChecker = $authorizationChecker;
+ $context = $this->decorated->create($resourceClass, $operationName, $resolverContext, $normalization);
+ $resourceClass = $context['resource_class'] ?? null;
+
+ if ($resourceClass === Book::class && isset($context['groups']) && $this->authorizationChecker->isGranted('ROLE_ADMIN') && false === $normalization) {
+ $context['groups'][] = 'admin:input';
+ }
+
+ return $context;
}
+}
+```
+
+### Laravel Serialization Context Decoration
+```php
+decorated->create($resourceClass, $operationName, $resolverContext, $normalization);
$resourceClass = $context['resource_class'] ?? null;
- if ($resourceClass === Book::class && isset($context['groups']) && $this->authorizationChecker->isGranted('ROLE_ADMIN') && false === $normalization) {
+ if ($resourceClass === Book::class && isset($context['groups']) && $this->isAdmin() && !$normalization) {
$context['groups'][] = 'admin:input';
}
return $context;
}
+
+ private function isAdmin(): bool
+ {
+ $user = Auth::user();
+
+ return $user && $user->role === 'admin';
+ }
}
```
## Export the Schema in SDL
+> [!WARNING]
+> This command is not yet available with Laravel, you're welcome to contribute [on Github](github.com/api-platform/core)
+
You may need to export your schema in SDL (Schema Definition Language) to import it in some tools.
The `api:graphql:export` command is provided to do so:
```shell-session
-docker compose exec php \
bin/console api:graphql:export -o path/to/your/volume/schema.graphql
```
Since the command prints the schema to the output if you don't use the `-o` option, you can also use this command:
```shell-session
-docker compose exec php \
bin/console api:graphql:export > path/in/host/schema.graphql
```
@@ -2784,6 +2926,7 @@ Following the specification, the upload must be done with a `multipart/form-data
You need to enable it in the [allowed formats of API Platform](content-negotiation.md#configuring-formats-globally):
+#### Modifying allowed formats with Symfony
```yaml
# api/config/packages/api_platform.yaml
api_platform:
@@ -2792,6 +2935,19 @@ api_platform:
multipart: ['multipart/form-data']
```
+#### Modifying allowed formats with Laravel
+```php
+ [
+ // ...
+ 'multipart' => ['multipart/form-data']
+ ],
+];
+```
+
You can now upload files using the `createMediaObject` mutation, for details check [GraphQL multipart request specification](https://github.com/jaydenseric/graphql-multipart-request-spec)
and for an example implementation for the Apollo client check out [Apollo Upload Client](https://github.com/jaydenseric/apollo-upload-client).