David NĂ©grier CTO

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.

GraphQLite?

You don't know GraphQLite? 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.

/**
 * @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.

If you are living in UK, I'll be presenting a GraphQLite tutorial at PHP UK Conference on 21st February 2020. Don't hesitate to come and say hi!

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. Also, do not hesitate to jump directly to the documentation.

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).

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, 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:

{
    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:

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.

/**
 * @Type
 */
interface UserInterface
{
    /**
     * @Field
     */
    public function getUserName(): string;
}

This will automatically create a GraphQL interface whose description is:

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.

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

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)

/**
 * @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.

/**
 * @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 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:

The prefetch method will let you fetch all the objects of a given type in a single DB call.

/**
 * @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.
    }
}
Note: the prefetchMethod 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. I wrote an article about this idea, check it out.

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.

Improved input types

In GraphQLite v3, you could already map a PHP class to a GraphQL input type (using the @Factory annotation).

/**
 * 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:
/**
 * @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.

/**
 * 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:

/**
 * @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.

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.

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:

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:

/**
 * @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.

Also, GraphQLite now comes with its own PSR-15 middleware so it is relatively easy to setup a working environment with Zend Expressive, Slim 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, we could add support for GraphQL subscriptions quite easily.

You can check the issues targeted at the upcoming 4.1 release here.

Follow us

We hope you will be interested in GraphQLite.

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 (we love stars!) or follow me (@david_negrier) on Twitter for GraphQLite related news.

About the author

David is CTO and co-founder of TheCodingMachine and WorkAdventure. He is the co-editor of PSR-11, the standard that provides interoperability between dependency injection containers. He is also the lead developper of GraphQLite, a framework-agnostic PHP library to implement a GraphQL API easily.