Skip to content

octaharon/graphql-doctrine

 
 

Repository files navigation

GraphQL Doctrine

Build Status Code Quality Code Coverage Total Downloads Latest Stable Version License Join the chat at https://gitter.im/Ecodev/graphql-doctrine

A library to declare GraphQL types from Doctrine entities, PHP 7.1 type hinting, and annotations, and to be used with webonyx/graphql-php.

It reads most informations from type hints, complete some things from existing Doctrine annotations and allow further customizations with specialized annotations. It will then create ObjectType and InputObjectType instances with fields for all getter and setter respectively found on Doctrine entities.

It will not build the entire schema. It is up to the user to use automated types, and other custom types, to define root queries.

Quick start

Install the library via composer:

composer require ecodev/graphql-doctrine

And start using it:

<?php

use Blog\Model\Post;
use Blog\Model\User;
use Blog\Type\DateTimeType;
use GraphQL\Type\Definition\Type;
use GraphQL\Doctrine\DefaultFieldResolver;

// Define custom types mapping
$mapping = [
    DateTime::class => DateTimeType::class,
];

// Configure the type registry
$types = new Types($entityManager, $mapping);

// Configure default field resolver to be able to use getters
GraphQL::setDefaultFieldResolver(new DefaultFieldResolver());

// Build your Schema
$schema = new Schema([
    'Query' => [
        'fields' => [
            'posts' => [
                'type' => Type::listOf($types->get(Post::class)), // Use automated ObjectType for output
                'resolve' => function ($root, $args) {
                    // call to repository...
                }
            ],
        ],
    ],
    'Mutation' => [
        'fields' => [
            'createPost' => [
                'type' => Type::nonNull($types->get(Post::class)),
                'args' => [
                    'input' => Type::nonNull($types->getInput(Post::class)), // Use automated InputObjectType for input
                ],
                'resolve' => function ($root, $args) {
                    // create new post and flush...
                }
            ],
            'updatePost' => [
                'type' => Type::nonNull($types->get(Post::class)),
                'args' => [
                    'id' => Type::nonNull(Type::id()), // Use standard API when needed
                    'input' => $types->getInput(Post::class),
                ],
                'resolve' => function ($root, $args) {
                    // update existing post and flush...
                }
            ],
        ],
    ],
]);

Usage

The public API is limited to the public methods on Types and the annotations. So the it's the constructor and:

  • $types->get() to get either an ObjectType from an entity or any other custom types (eg: string or mapped type)
  • $types->getInput() to get an InputObjectType to be used in mutations
  • $types->getId() to get an EntityIDType which should usually not be necessary for common usages and is used internally to be able to pass an object to a getter

Information priority

To avoid code duplication as much as possible, information are gathered from several places, where available. And each of those might be overridden. The order of priority, from the least to most important is:

  1. Type hinting
  2. Doc blocks
  3. Annotations

That means it is always possible to override everything with annotations. But existing type hints and dock blocks should cover the majority of cases.

Exclude a field

All getters are included by default in the type. But it can be specified otherwise for each getter. To exclude a sensitive field from ever being exposed through the API, use @API\Exclude:

/**
 * Returns the hashed password
 *
 * @API\Exclude
 * @return string
 */
public function getPassword(): string
{
    return $this->password;
}

Override scalar types

Even if a getter returns a PHP scalar type, such as string, it might be preferable to override the type with a custom GraphQL type. This is typically useful for enum or other validation purposes, such as email address. This is done by specifying the GraphQL type FQCN via @API\Field annotation:

/**
 * Get status
 *
 * @API\Field(type="GraphQLTests\Doctrine\Blog\Types\PostStatusType")
 * @return string
 */
public function getStatus(): string
{
    return $this->status;
}

That annotation can be used to override other things, such as name, description and args.

Custom types

By default all PHP scalar types and Doctrine collection are automatically detected and mapped to a GraphQL type. However if some getter return custom types, such as DateTime, or a custom class, then it will have to be configured beforehand:

$mapping = [
    DateTime::class => DateTimeType::class,
];

$types = new Types($entityManager, $mapping);

// Build schema...

That way it is not necessary to annotate every single getter returning one of the configured type. It will be mapped automatically.

Override arguments

Similarly to @API\Field, @API\Argument allows to override the type of argument if the PHP type hint is not enough:

/**
 * Returns all posts of the specified status
 * @API\Field(args={@API\Argument(name="status", type="?GraphQLTests\Doctrine\Blog\Types\PostStatusType")})
 * @param string $status the status of posts as defined in \GraphQLTests\Doctrine\Blog\Model\Post
 * @return Collection
 */
public function getPosts(?string $status = Post::STATUS_PUBLIC): Collection
{
    // ...
}

Once again, it also allows to override other things such as name, description and defaultValue.

Entities as input arguments

If a getter takes an entity as parameter, then a specialized InputType will be created automatically to accept an ID. The entity will then be automatically fetched from the database and forwarded to the getter. So this will work out of the box:

public function isAllowedEditing(User $user): bool
{
    return $this->getUser() === $user;
}

You may also get an input type for an entity by using Types::getInput():

// Custom InputType
$userIdType = $types->getInput(User::class);

Limitations

  • The use statement is not supported. So types in annotation or doc blocks should either be the FQCN or in the same namespace as the getter.

  • Entities with composite identifiers are not supported for automatic creation of input types. Possible workarounds are to change input argument to be something else than an entity, write custom input types and use them via annotations, or adapt the database schema.

Prior work

Doctrine GraphQL Mapper has been an inspiration to write this package. While the goals are similar, the way it works is different. Annotations are spread between properties and getter, but we work only on getter. Setup seems slightly more complex, but might be more flexible. We built on conventions and widespread use of PHP 7.1 type hinting to have an easier out-of-the-box experience.

About

Automatic GraphQL types from Doctrine entities

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • PHP 99.1%
  • Shell 0.9%