Download the PHP package t3n/graphql without Composer
On this page you can find all versions of the php package t3n/graphql. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Informations about the package graphql
t3n.GraphQL
Flow Package to add graphql APIs to Neos and Flow that also supports advanced features like schema stitching, validation rules, schema directives and more. This package doesn't provide a GraphQL client to test your API. We suggest to use the GraphlQL Playground
Simply install the package via composer:
Version 2.x supports neos/flow >= 6.0.0
Configuration
In order to use your GraphQL API endpoint some configuration is necessary.
Endpoints
Let's assume that the API should be accessible under the URL http://localhost/api/my-endpoint.
To make this possible, you first have to add the route to your Routes.yaml
:
Don't forget to load your routes at all:
Now the route is activated and available.
Schema
The next step is to define a schema that can be queried.
Create a schema.graphql
file:
/Your.Package/Resources/Private/GraphQL/schema.root.graphql
Under the hood we use t3n/graphql-tools. This package is a php port from Apollos graphql-tools. This enables you to use some advanced features like schema stitching. So it's possible to configure multiple schemas per endpoint. All schemas will be merged internally together to a single schema.
Add a schema to your endpoint like this:
To add another schema just add a new entry below the schemas
index for your endpoint.
You can also use the extend feature:
/Your.Package/Resources/Private/GraphQL/schema.yeah.graphql
Resolver
Now you need to add some Resolver. You can add a Resolver for each of your types. Given this schema:
You might want to configure Resolver for both types:
Each resolver must implement t3n\GraphQL\ResolverInterface
!
You can also add resolvers dynamically so you don't have to configure each resolver separately:
With this configuration the class Your\Package\GraphQL\Resolver\Type\ProductResolver
would be responsible
for queries on a Product type. The {Type} will evaluate to your type name.
As a third option you can create resolvers programmatically. Therefore you can register a class that implements
the t3n\GraphQL\ResolverGeneratorInterface
. This might be useful to auto generate a resolver mapping:
The Generator must return an array with this structure: ['typeName' => \Resolver\Class\Name]
☝️ Note: Your Resolver can override each other. All resolver configurations are applied in this order:
- ResolverGenerator
- dynamic "{Type}Resolver"
- specific Resolver
Resolver Implementation
A implementation for our example could look like this (pseudocode):
An example query like:
would invoke the QueryResolver in first place and call the products()
method. This method
returns an array with Product objects. For each of the objects the ProductResolver is used.
To fetch the actual value there is a DefaultFieldResolver. If you do not configure a method
named as the requests property it will be used to fetch the value. The DefaultFieldResolver
will try to fetch the data itself via ObjectAccess::getProperty($source, $fieldName)
.
So if your Product Object has a getName()
it will be used. You can still overload the
implementation just like in the example.
All resolver methods share the same signature:
Interface Types
When working with interfaces, you need Resolvers for your interfaces as well. Given this schema:
You need to configure a Person
Resolver as well as Resolvers for the concrete implementations.
While the concrete Type Resolvers do not need any special attention, the Person
Resolver implements the
__resolveType
function returning the type names:
Union Types
Unions are resolved the same way as interfaces. For the example above, the corresponding Schema looks like this:
It is resolved again with a PersonResolver
implementing the __resolveType
function.
Enum Types
Enums are resolved automatically. The Resolver method simply returns a string matching the Enum value. For the Schema:
The CustomerResolver
method returns the value as string:
Scalar Types
Types at the leaves of a Json tree are defined by Scalars. Own Scalars are implemented by a Resolver
implementing the functions serialize()
, parseLiteral()
and parseValue()
.
This example shows the implementation of a DateTime
Scalar Type. For the given Schema definition:
The DateTimeResolver
looks the following when working with Unix timestamps:
You have to make the DateTimeResolver
available again through one of the configuration options in the Settings.yaml.
Context
The third argument in your Resolver method signature is the Context.
By Default it's set to t3n\GraphQContext
which exposes the current request.
It's easy to set your very own Context per endpoint. This might be handy to share some Code or Objects
between all your Resolver implementations. Make sure to extend t3n\GraphQContext
Let's say we have an graphql endpoint for a shopping basket (simplified):
First of all configure your context for your shopping endpoint:
A context for this scenario would inject the current basket (probably flow session scoped);
And the corresponding resolver classes:
Log incoming requests
You can enable logging of incoming requests per endpoint:
Once activated all incoming requests will be logged to Data/Logs/GraphQLRequests.log
. Each log entry
will contain the endpoint, query and variables.
Secure your endpoint
To secure your api endpoints you have several options. The easiest way is to just configure some privilege for your Resolver:
You could also use a custom Context to access the current logged in user.
Schema directives
By default this package provides three directives:
- AuthDirective
- CachedDirective
- CostDirective
To enable those Directives add this configuration to your endpoint:
AuthDirective
The AuthDirective will check the security context for current authenticated roles. This enables you to protect objects or fields to user with given roles.
Use it like this to allow Editors to update a product but restrict the removal to Admins only:
CachedDirective
Caching is always a thing. Some queries might be expensive to resolve and it's worthy to cache the result. Therefore you should use the CachedDirective:
The CachedDirective will use a flow cache t3n_GraphQL_Resolve
as a backend. The directive accepts a maxAge
argument as well as tags. Check the flow documentation about caching to learn about them!
The cache entry identifier will respect all arguments (id in this example) as well as the query path.
CostDirective
The CostDirective will add a complexity function to your fields and objects which is used by some validation rules. Each type and children has a default complexity of 1. It allows you to annotate cost values and multipliers just like this:
If you query produts(limit: 3) { name, price }
the query would have a cost of:
9 per product (5 for the product itself and 3 for fetching the name and 1 for the price (default complexity)) multiplied with 3 cause we defined the limit value as an multiplier. So the query would have a total complexity of 27.
Validation rules
There are several Validation rules you can enable per endpoint. The most common are the QueryDepth as well as the QueryComplexity rule. Configure your endpoint to enable those rules:
The maxQueryComplexitiy
is calculated via the CostDirective.