From 502e79018dfd7d16ea4cb10e2ed315ffd7f075d5 Mon Sep 17 00:00:00 2001 From: Alan Poulain Date: Sat, 1 Jun 2019 22:31:23 +0200 Subject: [PATCH] Add documentation about custom types, queries and mutations --- core/graphql.md | 527 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 526 insertions(+), 1 deletion(-) diff --git a/core/graphql.md b/core/graphql.md index 85314f4cbfd..a8f8521899e 100644 --- a/core/graphql.md +++ b/core/graphql.md @@ -106,7 +106,180 @@ To get the next page, you would add the `endCursor` from the current page as the When the property `hasNextPage` of the `pageInfo` field is false, you've reached the last page. If you move forward, you'll end up having an empty result. -## Mutations +### Custom Queries + +To create a custom query, first of all you need to create its resolver. + +If you want a custom query for a collection, create a class like this: + +```php + $collection + * + * @return iterable + */ + public function __invoke(iterable $collection, array $context): iterable + { + // Query arguments are in $context['args']. + + foreach ($collection as $book) { + // Do something with the book. + } + + return $collection; + } +} +``` + +If you use autoconfiguration (the default Symfony configuration) in your application, then you are done! + +Else, you need to tag your resolver like this: + +```yaml +# api/config/services.yaml +services: + # ... + App\Resolver\BookCollectionResolver: + tags: + - { name: api_platform.graphql.query_resolver } +``` + +The resolver for an item is very similar: + +```php +name = 'DateTime'; + $this->description = 'The `DateTime` scalar type represents time data.'; + + parent::__construct(); + } + + public function getName(): string + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function serialize($value) + { + // Already serialized. + if (\is_string($value)) { + return (new \DateTime($value))->format('Y-m-d'); + } + + if (!($value instanceof \DateTime)) { + throw new Error(sprintf('Value must be an instance of DateTime to be represented by DateTime: %s', Utils::printSafe($value))); + } + + return $value->format(\DateTime::ATOM); + } + + /** + * {@inheritdoc} + */ + public function parseValue($value) + { + if (!\is_string($value)) { + throw new Error(sprintf('DateTime cannot represent non string value: %s', Utils::printSafeJson($value))); + } + + if (false === \DateTime::createFromFormat(\DateTime::ATOM, $value)) { + throw new Error(sprintf('DateTime cannot represent non date value: %s', Utils::printSafeJson($value))); + } + + // Will be denormalized into a \DateTime. + return $value; + } + + /** + * {@inheritdoc} + */ + public function parseLiteral($valueNode, ?array $variables = null) + { + if ($valueNode instanceof StringValueNode && false !== \DateTime::createFromFormat(\DateTime::ATOM, $valueNode->value)) { + return $valueNode->value; + } + + // Intentionally without message, as all information already in wrapped Exception + throw new \Exception(); + } +} +``` + +You can also check the documentation of [graphql-php](https://webonyx.github.io/graphql-php/type-system/scalar-types/#writing-custom-scalar-types). + +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. + +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: + +```yaml +# api/config/services.yaml +services: + # ... + App\Type\Definition\DateTimeType: + tags: + - { name: api_platform.graphql.type } +``` + +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. +In some cases, you would want to modify the extracted types for instance to use your custom ones. + +To do so, you need to decorate the `api_platform.graphql.type_converter` service: + +```yaml +# api/config/services.yaml +services: + # ... + 'App\Type\TypeConverter': + decorates: api_platform.graphql.type_converter +``` + +Your class needs to look like this: + +```php +defaultTypeConverter = $defaultTypeConverter; + } + + /** + * {@inheritdoc} + */ + public function convertType(Type $type, bool $input, ?string $queryName, ?string $mutationName, string $resourceClass, ?string $property, int $depth) + { + if ('publicationDate' === $property + && Book::class === $resourceClass + ) { + return 'DateTime'; + } + + return $this->defaultTypeConverter->convertType($type, $input, $queryName, $mutationName, $resourceClass, $property, $depth); + } +} +``` + +In this case, the `publicationDate` property of the `Book` class will have a custom `DateTime` type. + +You can even apply this logic for a kind of property. Replace the previous condition with something like this: + +```php +if (Type::BUILTIN_TYPE_OBJECT === $type->getBuiltinType() + && is_a($type->getClassName(), \DateTimeInterface::class, true) +) { + return 'DateTime'; +} +``` + +All `DateTimeInterface` properties will have the `DateTime` type in this example. + ## Export the Schema in SDL You may need to export your schema in SDL (Schema Definition Language) to import it in some tools.