Skip to content
Commits on Source (7)
......@@ -8,6 +8,7 @@ theme: main
context:
post_date: 2019-02-06
related:
- /graphqlite4
- /type-hint-all-the-things
- /adding-graphql-to-your-vuejs-application
excerpt: Come with us in a journey through the GraphQL landscape in PHP and learn about our brand new GraphQLite library.
......@@ -19,6 +20,8 @@ context:
# update_date: 2017-11-21
---
<div class="alert alert-info"><strong>Update: </strong> since the writing of this article, we released <a href="graphqlite4">GraphQLite 4</a></div>
## TL;DR
[GraphQLite](https://graphqlite.thecodingmachine.io) is a PHP library that maps your PHP classes and
......@@ -283,6 +286,8 @@ modules to help you get started quickly. So far, we have a [Symfony bundle](http
We plan on working on [container-interop/service-provider compatibility](https://github.com/container-interop/service-provider/) and a Laravel module too.
<div class="alert alert-info"><strong>Update: </strong> since the writing of this article, the <a href="https://graphqlite.thecodingmachine.io/docs/laravel-package">Laravel</a> and the <a href="https://graphqlite.thecodingmachine.io/docs/universal_service_providers">service-provider</a> packages have been released.</div>
Webonyx has already bindings with PSR-15 middlewares so it is relatively easy to setup a working environment with
[Zend Expressive](https://docs.zendframework.com/zend-expressive/) or any PSR-15 enabled framework.
......
---
inherits: base.yml
url: /graphqlite4
title: "Releasing GraphQLite 4: GraphQL in PHP made easy"
lang: en
theme: main
#menu: Foo/Bar
context:
post_date: 2020-01-06
related:
- /graphqlite-graphql-in-php-easy
- /type-hint-all-the-things
- /adding-graphql-to-your-vuejs-application
excerpt: GraphQLite 4 is released, with a whole host of new very exciting features! Come with us in this introduction of the new release.
introduction: >
We are pretty thrilled to announce GraphQLite v4! This v4 release is the culmination of 8 months of hard work. It
comes with a whole host of new very exciting features! Come an join us for a ride in the land of v4.
# update_date: 2017-11-21
---
## GraphQLite?
You don't know <a href="https://graphqlite.thecodingmachine.io/">GraphQLite</a>? It is a PHP library aiming at exposing
a GraphQL API in PHP dead simple.
GraphQLite exposes your PHP functions as queries or mutations using simple PHP annotations.
```php
/**
* @Query
*/
public function product(string $id): Product
{
// Some code that looks for a product and returns it.
}
```
This simple code will expose a GraphQL schema with a `product` query that accepts an `$id` argument and returns a
`Product` output type.
<div class="alert alert-info">If you are living in UK, I'll be presenting a GraphQLite tutorial at
<a href="https://www.phpconference.co.uk/schedule/">PHP UK Conference</a> on 21st February 2020. Don't hesitate to come
and say hi!</div>
This article really focuses on the improvements since GraphQLite 3. If you have not played with GraphQLite yet,
you should maybe start by reading more about the GraphQLite philosophy in our [initial blog post announcement](/graphqlite-graphql-in-php-easy).
Also, do not hesitate to [jump directly to the documentation](https://graphqlite.thecodingmachine.io/).
## So, what's new?
GraphQLite 3 already offered these features:
- Declaration of queries and mutations in "controller" classes
- Declaration of types and fields using annotations (`@Type` and `@Field`)
- Support for GraphQL input types (`@Factory` annotation)
- Mapping PHP inheritance to GraphQL interface
- Support for basic authentication and authorization (`@Logged` and `@Right` annotation)
With GraphQLite 4, we focused a lot on:
- improving developer experience
- the few missing GraphQL features remaining
- adding hook points to make GraphQLite extensible
So let's dive into the main new features:
## Autowiring services in resolvers
Maybe my favorite feature of this new release, because it simplifies a lot the way you organize your code.
Some frameworks (Symfony in particular) can inject services directly in controller actions.
The interest is somewhat limited since it is pretty easy to inject the services in the controller's constructor.
But in the context of GraphQLite, it starts to make a lot of sense, because resolvers are scattered
all over your code (and particularly in your models). And injecting a service in a model is not something
you can usually do.
Let's see how this works with a simple sample. Let's assume you are running an international store. You have a `Product` class. Each product has many names (depending
on the language of the user).
```php
namespace App\Entities;
use TheCodingMachine\GraphQLite\Annotations\Autowire;
use TheCodingMachine\GraphQLite\Annotations\Field;
use TheCodingMachine\GraphQLite\Annotations\Type;
use Symfony\Component\Translation\TranslatorInterface;
/**
* @Type()
*/
class Product
{
// ...
/**
* @Field()
* @Autowire(for="$translator")
*/
public function getName(TranslatorInterface $translator): string
{
return $translator->trans('product_name_'.$this->id);
}
}
```
Thanks to the [`@Autowire` annotation](https://graphqlite.thecodingmachine.io/docs/next/autowiring), GraphQLite will inject a translation service in the `$translator` parameter.
So any time you run a GraphQL query on the `Product` type, GraphQLite will inject the services for you:
```graphql
{
products {
name
}
}
```
I absolutely love this feature, because it pushes the developer in organizing his/her code in a
fashion that looks a lot like Domain Driven Design.
In the example above, a "name" clearly belongs to a product, and if we need an additional service
to be able to retrieve it, it makes sense to pass the service as an argument to the `getName` method.
Compare that to performing the translation outside the model. In a REST approach, it is terribly
easy to write something like:
```php
return new JsonResponse([
'name' => $this->translateService->trans('product_name_'.$product->getId()),
]);
```
If you write your code like this, the notion of "product name" becomes external to the `Product` class. Looking at
the `Product` class code only, a developer does not even know that it "has" a name.
In GraphQLite 3, to do the same thing, you would have had to create an "external type", which led to having
fields declared in 2 classes. Same result in the end, your `Product` class is stripped from
possessing a name. Autowiring allows you to keep all your fields in the same class. Use it!
## Mapping GraphQL interfaces
You can now map a PHP interface to a GraphQL interface, using the same `@Type` annotation you are already using on classes.
```php
/**
* @Type
*/
interface UserInterface
{
/**
* @Field
*/
public function getUserName(): string;
}
```
This will automatically create a GraphQL interface whose description is:
```graphql
interface UserInterface {
userName: String!
}
```
Of course, a PHP class implementing this PHP interface will translate into a GraphQL output type implementing the
GraphQL interface automatically!
## Validating user input
GraphQL input types can now be [validated using simple to use annotations](https://graphqlite.thecodingmachine.io/docs/next/validation).
I must admit I was first reluctant to add validation to GraphQLite, but this was certainly the most requested feature.
So here we go! The way you validate those will depend on the framework you are using, since we are tapping directly into your framework's
validation library.
### In Laravel
```php
class MyController
{
/**
* @Mutation
* @Validate(for="$email", rule="email|unique:users")
* @Validate(for="$password", rule="gte:8")
*/
public function createUser(string $email, string $password): User
{
// ...
}
}
```
### In Symfony
... or in any other framework (since you can use the symfony/validator component)
```php
/**
* @Query
* @Assertion(for="email", constraint=@Assert\Email())
*/
public function findByMail(string $email): User
{
// ...
}
```
## Improved security handling
A much requested feature: GraphQLite 4 comes with fine-grained security.
Until v4, you could deny access to users based on user rights. With v4, you can grant or deny access based on the context.
For instance, imagine you are developing a blog platform. When writing a new article, a user can access his blog post,
but no-one else should be allowed to. You can express this in GraphQLite using the new `@Security` annotation.
```php
/**
* @Type
*/
class Post {
/**
* @Query
* @Security("this.canAccess(post, user)")
*/
public function getPost(Post $post): array
{
// ...
}
public function canAccess(Post $post, User $user): bool
{
// Some custom logic here to know if $user can access $post
}
}
```
The [`@Security` annotation](https://graphqlite.thecodingmachine.io/docs/next/fine-grained-security) lets you tap into the power of Symfony expression language to write complex expressions.
If you are used to Symfony, you already know this concept. The GraphQLite `@Security` annotation is heavily inspired
from Symfony's own `@Security` annotation. A big thanks to the Symfony team for this great idea!
## Improving performance
A common problem faced by GraphQL implementations is the way to deal with the so called N+1 problem. GraphQLite 4 comes
with 2 ways to ways to help you tackle this issue:
- you can [inspect the "query plan"](https://graphqlite.thecodingmachine.io/docs/next/query-plan) (which can help you perform joins preemptively)
- you can easily use the "dataloader" design pattern with our new ["prefetch" feature](https://graphqlite.thecodingmachine.io/docs/next/prefetch-method)
The prefetch method will let you fetch all the objects of a given type in a single DB call.
```php
/**
* @Type
*/
class Post {
/**
* @Field(prefetchMethod="prefetchUsers")
* @param mixed $prefetchedUsers
* @return User
*/
public function getUser($prefetchedUsers): User
{
// This method will receive the $prefetchedUsers as first argument.
// This is the return value of the "prefetchUsers" method below.
// Using this pre-fetched list, it should be easy to map it
// to the post
}
/**
* @param Post[] $posts
* @return mixed
*/
public function prefetchUsers(iterable $posts)
{
// This function is called only once per GraphQL request
// with the list of posts. You can fetch the list of users
// associated with this posts in a single request,
// for instance using a "IN" query in SQL or a multi-fetch
// in your cache back-end.
}
}
```
<div class="alert alert-info"><strong>Note:</strong> the <code>prefetchMethod</code> technique simplifies a lot the implementation of the dataloader pattern, but still, it
requires some work. The more I look at this problem, the more I am convinced that the issue should be tackled
not by the GraphQL library, but by the underlying ORM. <a href="/solving-n-plus-1-problem-in-orms">I wrote an article about this idea, check it out.</a></div>
## Better error handling
### Choosing an HTTP error code
Error handling is a complex topic in GraphQL. A GraphQL query can contain several queries / mutations. Some may succeed,
some may fail in various ways. At the end of the query, putting a unique HTTP code on it is hard.
Of course, if all queries succeed, the HTTP 200 code is the way to go. But as soon as one query fails, choosing an HTTP
code is hard.
It is actually so hard that there is no agreement on what to do in the wider GraphQL ecosystem, with the 2 leading
JS server-side implementation (Graph-js and Apollo Server) choosing 2 different strategies.
In GraphQLite, we externalized the choice of the HTTP code in the new `HttpCodeDecider` class. You can replace this class
by your own implementation if you have specific needs. By default, GraphQLite will return a HTTP error code (4xx or 5xx)
as soon as there is one query that fails. If many queries fail, it will choose the highest error code.
### Easier error throwing
GraphQLite now comes with a `GraphQLException` base exception that you can throw.
This exception (just like any exception extending the `ClientAware` interface) will be turned into a GraphQL `error`.
Furthermore, if you want to output many errors in the GraphQL errors section, you can the new `GraphQLAggregateException` class.
This exception aggregates many exceptions. [Each exception will be turned into a GraphQL error.](error_handling.md#many-errors-for-one-exception)
## Improved input types
In GraphQLite v3, you could already map a PHP class to a GraphQL input type (using the `@Factory` annotation).
```php
/**
* The Factory annotation will create automatically
* a UserInput input type in GraphQL.
* @Factory()
*/
public function fetchUser(int $id): User
{
return $this->userRepository->findById($id);
}
// You can now use the User class in any query / mutation / field arguments:
/**
* @Query
* @return Post[]
*/
public function getPostsByUser(User $user): iterable
{
return $this->postRepository->findByUser($user);
}
```
The issue is that sometimes, a single class can map to *many* GraphQL input types.
In the example above, when I inject the `User` class in a query / mutation, there could be 2 different meanings:
1. I want to provide only the `id` of the `User` and GraphQLite should fetch the `User` instance in DB
2. or I want to provide a complete `User` object, like in this code sample:
```php
/**
* @Factory()
*/
public function createUser(string $lastName, string $firstName): User
{
return new User($lastName, $firstName);
}
/**
* @Mutation
*/
public function saveUser(User $user): User
{
$this->em->persist($user);
$this->em->flush();
return $user;
}
```
With GraphQLite 4, a given PHP class can now be mapped by many GraphQL input type. This means that you can add many factories for the same class.
```php
/**
* This factory will be used by default
* @Factory(name="FetchUserInput", default=true)
*/
public function fetchUser(int $id): User
{
return $this->userRepository->findById($id);
}
/**
* @Factory(name="CreateUserInput")
*/
public function createUser(string $lastName, string $firstName): User
{
return new User($lastName, $firstName);
}
```
In the example above, the first factory for the `User` class is marked with the `default` attribute. It will be used
by default. The second annotation can be used if you ask for it specifically, using the new `@UseInputType` annotation:
```php
/**
* @Mutation
* @UseInputType(for="$user", inputType="CreateUserInput!")
*/
public function saveUser(User $user): User
{
$this->em->persist($user);
$this->em->flush();
return $user;
}
```
On a side-note, if an input type is provided by a third-party library, you can now [extend it using the `@Decorate` annotation](https://graphqlite.thecodingmachine.io/docs/next/extend_input_type).
## Completely reworked internals
It should be fairly easy for users to migrate from v3 to v4 as must annotations are left untouched.
But internally, there are almost no lines of code that are left in common!
GraphQLite 4 is really a huge release.
![](images/graphqlite4/release_diff.png)
The important part is that we added a lot of extension points that you can tap into in order to extend GraphQLite.
Most of these extension points have been added using the "middleware" design pattern.
You can:
- alter the way the PHPDoc and the PHP type are turned into a GraphQL type, using the "root type mapper".
["Root type mappers" can be used to add support for new custom scalar types.](https://graphqlite.thecodingmachine.io/docs/next/custom-types#registering-a-custom-scalar-type-advanced)
- add custom annotations to change the way a resolver works, using ["field middlewares"](https://graphqlite.thecodingmachine.io/docs/next/field-middlewares).
For instance, the `@Logged` and `@Right` annotation have been rewritten from the ground as "field middlewares", and you can now add your own.
- alter the way arguments are resolved using ["parameter type mappers"](https://graphqlite.thecodingmachine.io/docs/next/argument-resolving).
For instance, the `@Autowire` annotation is implemented using argument resolvers.
Actually, most of the new features we implemented in v4 are built upon those extension points that you can use yourself!
## Symfony specific features
The GraphQLite Symfony bundle has also had a ton of new features.
The most important one is probably that it now offers by default:
- a `login` and a `logout` mutation
- a `me` query
So out of the box, you can login / logout from your Symfony application using GraphQL. No need to go through the
Symfony REST API anymore!
## Laravel specific features
The Laravel package has also been improved, with an improved integration with Laravel authentication framework.
Most of all, GraphQLite now supports mapping magic properties to GraphQL fields natively, using the `@MagicField`
annotation:
```php
/**
* @Type()
* @MagicField(name="id" outputType="ID!")
* @MagicField(name="name" phpType="string")
* @MagicField(name="categories" phpType="Category[]")
*/
class Product extends Model
{
}
```
This is not a Laravel specific feature per-se, but this new feature makes working with Eloquent a lot easier.
## Framework agnostic
At TheCodingMachine, we love framework-agnostic code. GraphQLite is therefore framework agnostic too. You can [deploy
it in any framework](https://graphqlite.thecodingmachine.io/docs/other-frameworks).
Also, GraphQLite now comes with its own PSR-15 middleware so it is relatively easy to setup a working environment with
[Zend Expressive](https://docs.zendframework.com/zend-expressive/), [Slim](http://www.slimframework.com/) or any PSR-15
enabled framework.
## What's next?
v4 is a big milestone, but there is still a lot of work to be done.
In the coming months we plan to:
- **work on a tutorial**: we will write a tutorial to get started with a full-stack environment using GraphQLite.
The choice is not completely set, but it will probably be Symfony 5 + GraphQLite + Next.JS + Apollo + Typescript
- **work on a TDBM**: TDBM is our home-grown ORM. We are working on a pretty unique feature that solves automatically
the N+1 performance problem. TDBM + GraphQLite would be a perfect match!
- **improve custom Scalar GraphQL types handling**: it is already possible to add custom scalar types in GraphQLite 4,
but there is some room for simplification.
- **add support for subscriptions**: using [Mercure](https://mercure.rocks/), we could add support for GraphQL subscriptions
quite easily.
You can check [the issues targeted at the upcoming 4.1 release here](https://github.com/thecodingmachine/graphqlite/milestone/1).
## Follow us
We hope you will be interested in [GraphQLite](https://graphqlite.thecodingmachine.io).
Between v3 and v4, we spent 8 months testing and slowly improving the library. v4 is already used by several of our clients,
so I'm confident saying it is stable.
Still, feedbacks are welcome!
You can [star the project on Github](https://github.com/thecodingmachine/graphqlite) (we love stars!) or [follow me (@david_negrier) on Twitter](https://twitter.com/david_negrier)
for GraphQLite related news.