Giter VIP home page Giter VIP logo

graphqlite's Introduction

GraphQLite logo

GraphQLite

GraphQL in PHP made easy.

Documentation ยท Contributing

Latest Stable Version Total Downloads License Continuous Integration Code Coverage


A GraphQL library for PHP that allows you to use attributes (or annotations) to define your schema and write your queries and mutations using simple-to-write controllers.

Features

  • Create a complete GraphQL API by simply annotating your PHP classes
  • Framework agnostic, but with Symfony and Laravel integrations available!
  • Comes with batteries included ๐Ÿ”‹: queries, mutations, subscriptions, mapping of arrays/iterators, file uploads, extendable types and more!

Basic example

First, declare a mutation in your controller:

class ProductController
{
    #[Mutation]
    public function updateProduct(Product $product): Product
    {
        // Some code that gets and updates a Product
        return $product;
    }
}

Then, annotate the Product class to declare what fields are exposed to the GraphQL API:

#[Type]
#[Input(update: true)]
class Product
{
    #[Field]
    public function getName(): string
    {
        return $this->name;
    }
    
    #[Field]
    public function setName(string $name): void
    {
        $this->name = $name;
    }
    
    // ...
}

That's it, you're good to go ๐ŸŽ‰ mutate away!

{
  updateProduct(product: {
    name: 'John Doe'
  }) {
    name
  }
}

Want to learn more? Head to the documentation!

Contributing

Contributions are welcomed via pull requests. If you'd like to discuss prior to submitting a PR, consider a discussion. If it's a bug/issue, you can submit an issue first.

All PRs should have sufficient test coverage for any additions or changes. PRs will not be merged without these.

graphqlite's People

Contributors

andrew-demb avatar aszenz avatar cvergne avatar danatfh avatar dependabot-preview[bot] avatar dependabot[bot] avatar devmaslov avatar dsavina avatar fezfez avatar fogrye avatar gulien avatar kharhamel avatar l-you avatar lsimeonov avatar mattbred avatar mdoelker avatar michael-vostrikov avatar mistraloz avatar moufmouf avatar oojacoboo avatar oprypkhantc avatar realflowcontrol avatar shish avatar snyk-bot avatar spartaksun avatar tamasszigeti avatar thibbal avatar willyreyno avatar xyng avatar zheness avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

graphqlite's Issues

"Use your beans as input types" doesn't work on Symfony

Stack: Symfony 4 - TDBM5 - GraphQLite

Situation: I have an AdminUser bean, an AdminUserDAO (with the Factory annotation). I want to inject into my mutation the AdminUser automatically.

public function saveAdmin(?AdminUser $adminUser, string $lastname, string $firstname, string $email, ?string $login, ?string $phone, ?string $password, string $status, string $role, string $pole): AdminUser

Error : I have an error :

In DefinitionErrorExceptionPass.php line 54:

Cannot autowire service "App\Model\AdminUser": argument "$email" of method "App\Model\Generated\AbstractAdminUser::
__construct()" is type-hinted "string", you should configure its value explicitly.

Integration of subscriptions

This is F*CKING awesome !
I wanted to tell you that while writing the first issue, which is more a feature request.
This package only misses the integration of subscriptions requests to be perfect.

Meantime I still have a lot of new toys to play with, so thanks!

Add a @InjectUser annotation

Idea: add a @InjectUser annotation that allows injecting the current user in any query / mutation / field.

/**
 * @InjectUser(for="$user")
 * @Query
 */
public function foo(User $user): string {
   // Do something with the user
}

Add a way to create a field from a magic property

He should add a way to create a field from a magic property.
That could be very useful to play with Eloquent.

For instance:

@SourceField(name="user" outputType="!User" magic="true")

or

@MagicField(name="user" outputType="!User")

Fix DateTime parameter passed in variable

Passing DateTime as a request variable causes an exception:

TypeError: DateTimeImmutable::__construct() expects parameter 1 to be string, object given
/Users/admin/Projects/graphqlite/src/Types/DateTimeType.php:44
/Users/admin/Projects/graphqlite/src/Types/ArgumentResolver.php:54

Passing the same value inline works good.
Fixes and tests are in pull-request: #124
Thanks!

Add support for static factories

Currently, any method annotated with @factory will be treated as-if it was non-static.
This means GraphQLite will try to instantiate the class the factory is in, even if the factory is public static.

GraphQLite should not try to instantiate the class if the factory is static.

Default values in mutation

Hello,

I have a mutation where i can put array in parameters. But even if i set the parameter as empty array, my front always returns error $categories" of type "[Int!]" used in position expecting type "[Int!]!

/**
* ....
* @param int[] $categories
*/
updateCompanies(int $Idcompany, array $categories = []) { ... }

How should i do to have default values ?

Thank you :)

Implement @FailWith401 annotation

When an unlogged user tries to call a query with @Logged annotation, he receives a "Cannot query field \"foo\" on type \"Query\"." error.
Could be great if he receives a 401 response instead.

Temporary solution : @FailWith(null) (thanks @moufmouf)

[Semantical Error] Annotation @Autowire is not allowed to be declared on class

[Semantical Error] Annotation @autowire is not allowed to be declared on class Foo. You may only use this annotation on these code elements: METHOD.

Trying to use the following annotation on the class

@SourceField(name="company", annotations={@Autowire(for="$con")})

public function getCompany(ConnectionInterface $con = null)

This is necessary since Propel, the ORM we use, takes in the connection object in order to hydrate a relational object, if needed - lazy loading.

Feature Request: Caching on the server

Hi, I was wondering, if it would be possible to add caching on the server side using i.e. Redis.

As far as I can tell it should be possible to implement your own caching in a controller, that's tagged with a @Query annotation, however you'll have to be careful, to get all the parameters for the cache key.
What I was thinking about, was to be able to register a cache pool with graphqlite and then telling it by a @Cache annotation, to cache the queries (or possible something like @Query(cache=true)). If nothing else is specified, I'd suggest to simply use the request url along with all parameters as cache key and a default cache time of 1 hour.
Through a config file you should be able to adjust the default cache time plus by setting a @Cache(time="xxx") parameter in the annotation. Additionally I'd add a parameter to that annotation to either include or exclude certain parameters from the cache key, as for example the preference for the UI (list view/table view) for a list gets sent along but doesn't influence the query results, so devs should be able to exclude these parameters.

Do you think this is doable and if so do you see any gains from implementing such a system?

Interfaces as Type

Can't make php interface as the type for graphql, it seems that annotation parser might see(parse) only classes. In my app, I have a lot of interfaces that I use for type hint and etc. So that is important to have the ability to create types from interfaces.

Adding support for Enum types

Currently, GraphQLite has no support for GraphQL "enum" types.

This is because PHP has no native "enum" support.

Yet, there exists a number of alternatives: myclabs/php-enum, SplEnum, eloquent/enumeration...

The idea would be to detect any class representing an enum and mapping it to the GraphQL Enum type.

Using myclabs/php-enum, code could look like this:

class Status extends Enum
{
    private const ON = 'on';
    private const OFF = 'off';
    private const PENDING = 'pending';
}

class UserController {
    /**
     * @Query
     * @return User[]
     */
    public function viewUsers(Status $status): array {
        // ...
    }
}

The "$status" argument will automatically be mapped to an InputType named "Status" of GraphQL type enum, with 3 possible values ('on', 'off' and 'pending').

Support for major enum libraries should be built in GraphQLite, with the ability to add any other library.

Implementation details

To do this, we need to be able to get a hook at the root type mapping level of GraphQLite (in https://github.com/thecodingmachine/graphqlite/blob/master/src/FieldsBuilder.php#L682-L717)

We should probably replace the toGraphQlType method with an interface that can support many different implementation (one of which would be MyclabsEnumTypeMapper):

interface RawTypeMapper {
    private function toGraphQLType(Type $type, ?GraphQLType $subType, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?GraphQLType;

    private function toGraphQLInputType(Type $type, ?GraphQLType $subType, string $argumentName, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?GraphQLType;
}

Improve file upload documentation

File upload documentation explains the GraphQLite side, but not the client side.

It could be good to add samples on how to use it on the client side (maybe a tutorial?)

Support for symfony 3.4

We would like to use graphqlite but unfortunately we are still stuck on symfony 3.4. Are there plans to make graphqlite backwards compatible with symfony 3.4?

Adding Deferred types support

We should add support for Webonyx Deferred types in order to offer a way to avoid the N+1 issues.

Implementation should be as easy as possible on the user side.

Current proposal:

Consider a request where a user attached to a post must be returned.

{
    posts {
        id
        user {
            id
        }
    }
}
/**
 * @Type
 */
class PostType {
    /**
     * @Field(prefetchMethod="prefetchUsers")
     * @param Post $post
     * @param mixed $prefetchedUsers
     * @return User
     */
    public function getUser(Post $post, $prefetchedUsers): User
    {
        // This method will receive the $prefetchedUsers as second argument. This is the return value of the "prefetchUsers" method below.
        // Using this prefetched 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.
    }
}

When the "prefetchMethod" attribute is detected in the "@field" annotation, the method is called automatically. The first argument of the method is an iterable over the instances of the main type.
The "prefetchMethod" can return absolutely anything (mixed). The return value will be passed as the second parameter of the "@field" annotated method.

Important: field arguments can be set either on the @field annotated method OR/AND on the prefetchMethod.

For instance:

/**
 * @Type
 */
class PostType {
    /**
     * @Field(prefetchMethod="prefetchComments")
     * @param Post $post
     * @param mixed $prefetchedComments
     * @return Comment[]
     */
    public function getComments(Post $post, $prefetchedComments): array
    {
        // ...
    }

    /**
     * @param Post[] $posts
     * @return mixed
     */
    public function prefetchComments(iterable $posts, bool $hideSpam, int $filterByScore)
    {
        // Parameters passed after the first parameter (hideSpam, filterByScore...) are automatically exposed as GraphQL arguments for the "comments" field.
    }
}

The prefetch method MUST be in the same class as the @Field-annotated method and MUST be public.

Note: the "prefetchMethod" argument CANNOT be applied on @query and @mutation annotations. We will need a different kind of attribute for this.

Note to self: implementation warning! A GraphQL query can contain many different calls with different arguments for a same field. For instance:

{
    posts {
        id
        legitComments: comments(hideSpam: true) {
            id
        }
        spamComments: comments(hideSpam: false) {
            id
        }
    }
}

With the example above, we will have to call the prefetch method twice! Once with the "hideSpam" set to true, and once with the "hideSpam" set to false.

[Feature request] Named Query

Currently the query name is get from the method name.

I would like to get name with query annotation instead.

In this way the public class api don't have to change when adding this package.

Sample:

/** @Query(name=areYourCool) */
public function __invoke()
{
    return rand(0, 1) === 1 ? 'You\'re cool, man.' : 'Not cool, man.';
}

Prefetch cache outlives the request

When using the prefetch feature, the prefetch method is called once and the result of the method is stored in the Field class (in a "PrefetchBuffer" instance):

$prefetchBuffer = new PrefetchBuffer();

The problem is that this buffer is not deleted when the query has finished running.
If another query is performed just after (in the same PHP request), the result of the buffer will be reused.

This can be an issue with long running processes (because we are caching some data that is not meant to be cached).

We need to find a way to make that cache disappear with the request.

Note: Playing with the ResolveInfo or the $context might be a solution.

Remove FactoryHydrator

The HydratorInterface is useless in 4.0 since the result of a TypeMapper on input type must implement the ResolvableInputTypeInterface.

There is only one correct implementation of HydratorInterface and it is FactoryHydrator.
We should remove this abstraction.

Cannot override Field methods

Hello,

My entity Company has many Members.
I created a new method members() in CompanyType.php but it seems that my method is never called. Only the parent one which return a Porpaginas result is called.

Even if i deactivate the main field with getMembersFiled()->hide() it doesn't work.

Any idea on how to solve ?

Thanks !

Inject ResolveInfo in fields/queries/mutation signature

If a developer puts a "ResolveInfo" argument at any position in a @field, @query or @mutation, we should fill that argument with the "ResolveInfo" of the current resolver.

For instance:

class Controller {
    /**
     * @Query
     * @return User[]
     */
    public function users(string $search, ResolveInfo $resolveInfo): array {
        // ...
    }
}

The ResolveInfo parameter will be filled in any method annotated with @field, @query or @mutation.
The ResolveInfo parameter can be put at any position eligible for an input argument (so any position except the first position in the case of some @field annotated methods)

Adding support for multiple input types (multiple factories) for one input type

Currently, one class can map to one input type only.

We do this using factories:

class FilterType {
    /**
     * @Factory()
     */
    public function createFilter(string $name, float $price): Filter
    {
        $filter = new Filter();
        $filter->addFilter('name', 'LIKE', $name);
        $filter->addFilter('price', '<', $price);
        return $filter;
    }
}

class ProductController {
    /**
     * @Query
     */
    public function listProducts(Filter $filter): Grid {
        // ...
    }
}

This will automatically map the "Filter" PHP class to a "FilterInput" GraphQL type.
The "FilterInput" type will contain 2 fields: "name" and "price".

Now, we have another query that uses the same Filter class, we have no way to specify that we want to use another factory.

Proposal: we should be able to specify the input type name of the input type in the @factory annotation:

class FilterType {
    /**
     * @Factory(name="FilterWithColorInput")
     */
    public function createFilterWithColor(string $name, float $price, string $color): Filter
    {
        $filter = new Filter();
        $filter->addFilter('name', 'LIKE', $name);
        $filter->addFilter('price', '<', $price);
        $filter->addFilter('color', '=', $color);
        return $filter;
    }
}

This would create a new input type that is not mapped by default to the "Filter" class.
Now, to use this filter, we would use another annotation:

class PhoneController {
    /**
     * @Query
     * @UseInputType(for="$filter", name="FilterWithColorInput")
     */
    public function listPhones(Filter $filter): Grid {
        // ...
    }
}

The UseInputType annotation takes 2 compulsory arguments:

  • for: the argument we are talking about
  • name: the name of the input type

Furthermore, we will add another annotation that allows extending an input type and adding fields to it:

class ExtendedFilterType {
    /**
     * @Decorate(name="FilterWithColorInput")
     */
    public function createFilterWithColor(Filter $filter, float $maxWeight): Filter
    {
        $filter->addFilter('weight', '<', $maxWeight);
        return $filter;
    }
}

The Decorate annotation allows adding additional fields to an input type.
The first attribute of the method MUST be the input object to be extended.
From second attribute onwards, these are the fields that will be added in the input type.
In the example above, the "maxWeight" field is added to the Filter object.
Finally, the object MUST return the input object (or another object with the same type).

The @Decorate annotation accepts an optional "name" argument. If no name is passed, the fields are added to the default implementation.

Adding support for custom Scalar types

We should find a way to add support for custom GraphQL scalar types.

Currently support for the DateTime custom scalar type is hard-coded and third-party developers cannot add their own scalar types.

Note: this should be possible using the new RawTypeMapper interface described in #43

Implementation for a custom any-scalar type (accepting string/bool/float...) would go like this:

class ScalarTypeMapper implements RawTypeMapper {
    private function toGraphQLType(Type $type, ?GraphQLType $subType, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?GraphQLType {
        return null;
    }

    private function toGraphQLInputType(Type $type, ?GraphQLType $subType, string $argumentName, ReflectionMethod $refMethod, DocBlock $docBlockObj): ?GraphQLType {
        if ($type instanceof phpDocumentor\Reflection\Type\Scalar) {
            // AnyScalarType is a class implementing the Webonyx ScalarType type.
            return AnyScalarType::getInstance();
        }
    }
}

Registering a RawTypeMapper is done at GraphQL boot time:

$schemaFactory->registerRawTypeMapper(new ScalarTypeMapper());

Improve caching of GlobTypeMapper

There must be a way to improve the performance of the GlobTypeMapper.
Currently, if globTtl is out (> 2s), all the annotations are parsed again. This is lengty (because for Factories, we scan each method of each class).

We could try to have an analysis that returns absolutely every file scanned, and another process that returns the SAME cache if none of the files that have been used for the cache have been modified.

Note: this might be tricky when calls to the recursive type mapper are involved. We might need to push that cache up to the recursive type mapper (if possible).

HTTP 500 Error default

I'd like to suggest that the default for ClientAware exceptions follow the HTTP standards. As it is now, exceptions that implement the ClientAware interface are returning a 500 HTTP response code by default.

5** level response codes are reserved for server side errors. Since we are notifying the client of an error, it's almost certainly the case that it's a client based error. Otherwise, notification of the error details to the client is useless - the client cannot do anything about it.

Therefore, I'd like to see 4** level response codes for all exceptions implementing the ClientAware interface. If there is a way to provide more contextually relevant response codes based on the exception being thrown, that'd be great. I'll assume that this information is difficult to ascertain or futile. In which case, I'd simply recommend making it a generic 400 response.

@Security annotation does not work on @Type entity

#164
This code throws an exception:

use TheCodingMachine\GraphQLite\Annotations as GraphQLite;

/**
 * @GraphQLite\Type()
 */
class Product {
  /**
   * @GraphQLite\Field()
   * @GraphQLite\Security("this.check()")
   */
  public function getType(): string {
    return 'sale';
  }

  public function check(): bool {
    return $this->id > 0;
  }
}

After debugging, I noticed that callable insideQueryFieldDescriptor is set only for controllers.
I have tried to write my own middleware annotation, but I could not find a way to pass context (this).

Is there any way to have security validation depending on entity state?

Thanks!

An exception itself:
[Fri Oct 25 15:51:08 2019] Expected a value other than null.
[Fri Oct 25 15:51:08 2019] #0 backend/vendor/webmozart/assert/src/Assert.php(598): Webmozart\Assert\Assert::reportInvalidArgument('Expected a valu...')

#1 /backend/vendor/thecodingmachine/graphqlite/src/Middlewares/SecurityFieldMiddleware.php(88): Webmozart\Assert\Assert::notNull(NULL)
#2 /backend/vendor/thecodingmachine/graphqlite/src/Middlewares/Next.php(42): TheCodingMachine\GraphQLite\Middlewares\SecurityFieldMiddleware->process(Object(TheCodingMachine\GraphQLite\QueryFieldDescriptor), Object(TheCodingMachine\GraphQLite\Middlewares\Next))
#3 /backend/src/Middleware/AccessFieldMiddleware.php(50): TheCodingMachine\GraphQLite\Middlewares\Next->handle(Object(TheCodingMachine\GraphQLite\QueryFieldDescriptor))
#4 /backend/vendor/thecodingmachine/graphqlite/src/Middlewares/Next.php(42): CNG\Middleware\AccessFieldMiddleware->process(Object(TheCodingMachine\GraphQLite\QueryFieldDescriptor), Object(TheCodingMachine\GraphQLite\Middlewares\Next))
#5 /backend/vendor/thecodingmachine/graphqlite/src/Middlewares/FieldMiddlewarePipe.php(32): TheCodingMachine\GraphQLite\Middlewares\Next->handle(Object(TheCodingMachine\GraphQLite\QueryFieldDescriptor))
#6 /backend/vendor/thecodingmachine/graphqlite/src/FieldsBuilder.php(311): TheCodingMachine\GraphQLite\Middlewares\FieldMiddlewarePipe->process(Object(TheCodingMachine\GraphQLite\QueryFieldDescriptor), Object(class@anonymous))
#7 /backend/vendor/thecodingmachine/graphqlite/src/FieldsBuilder.php(136): TheCodingMachine\GraphQLite\FieldsBuilder->getFieldsByAnnotations(NULL, 'TheCodingMachin...', false, 'CNG\Domain\Enti...')
#8 /backend/vendor/thecodingmachine/graphqlite/src/Types/TypeAnnotatedObjectType.php(41): TheCodingMachine\GraphQLite\FieldsBuilder->getSelfFields('CNG\Domain\Enti...')
#9 /backend/vendor/webonyx/graphql-php/src/Type/Definition/FieldDefinition.php(90): TheCodingMachine\GraphQLite\Types\TypeAnnotatedObjectType::TheCodingMachine\GraphQLite\Types{closure}()
#10 /backend/vendor/webonyx/graphql-php/src/Type/Definition/ObjectType.php(151): GraphQL\Type\Definition\FieldDefinition::defineFieldMap(Object(TheCodingMachine\GraphQLite\Types\TypeAnnotatedObjectType), Object(Closure))

Denormalized Type composition

So, this question is more related to the best way to approach this issue. I wasn't able to find a solid example in the docs.

I'd like to compose custom types that are built up from multiple model objects/collections. Often times we can handle the hydration/sql side with hydration to limit db queries, but I'm really not sure how it's best to structure something like this. It seems like what I want is a custom type object that uses the constructor for DI, or a Factory. The querying is handled in the controllers and then the controller would simply return this custom type.

I'm not sure if this is the best way to approach this though. How is it generally recommended to handle this? I assume everyone is simply returning their models 1:1 with GraphQL types.

Nullable non-default parameters are causing weird behaviour.

    /**
     * @Factory()
     */
    public function someFactory(?OperatorFilter $id): Filter
    {
        // ...
    }

The OperatorFilter is nullable but has no default value.

So GraphQL will map it to 'id: OperatorFilter' very logically.

But GraphQL has no notion of nullable BUT you must provide the null value explicitly.

As a result, if you don't pass the null value, you get a PHP error instead of a GraphQL error like this:

TheCodingMachine\GraphQLite\Parameters\MissingArgumentException: Expected argument 'sorting' was not provided in /home/david/projects/sandbox/skunexus-test/vendor/thecodingmachine/graphqlite/src/Parameters/MissingArgumentException.php:17
Stack trace:
#0 /home/david/projects/sandbox/skunexus-test/vendor/thecodingmachine/graphqlite/src/Parameters/InputTypeParameter.php(61): TheCodingMachine\GraphQLite\Parameters\MissingArgumentException::create('sorting')
#1 /home/david/projects/sandbox/skunexus-test/vendor/thecodingmachine/graphqlite/src/QueryField.php(198): TheCodingMachine\GraphQLite\Parameters\InputTypeParameter->resolve(Object(Order\Types\OrderQueries), Array, NULL, Object(GraphQL\Type\Definition\ResolveInfo))
#2 [internal function]: TheCodingMachine\GraphQLite\QueryField->TheCodingMachine\GraphQLite\{closure}(Object(TheCodingMachine\GraphQLite\Parameters\InputTypeParameter))
#

We should automatically inject null values in the PHP method.

500 Error (must include parameter) - No logs

Version: dev-master (50fb4df)

I'm currently running into an issue getting things setup on a custom app. I've gotten everything setup according to the docs and I'm getting back a response, but I don't seem to be able to get past this recurring issue.

I've been able to locate the error in the library, but I haven't been able to identify the best way to resolve the issue.

I get the following error when trying to fetch the GraphQL schema using a GraphQL client. This error is accompanied by a http 500 response, yet there aren't any logs being written for this (This shouldn't be a 500 response). So, it's either squashing Exceptions, or it's sending out a 500 response explicitly. Regardless, debugging this issue hasn't been so straight forward as a result.

GraphQL Request must include at least one of those two parameters: "query" or "queryId"

In addition to getting this error when fetching the schema, I also get the same error when executing the example GraphQL query, directed at the endpoint.

{
  hello(name: Jacob)
}

This error goes away when you actually pass a query param with the GraphQL query instead of using a GraphQL client's functionality. This also seems to be, regardless of the http Content-Type header, whether it's set as application/json or application/graphql. There is logic in the middleware to confirm the application/graphql header though.

Any suggestions or thoughts here? Thanks.

Issue with fresh installation

thecodingmachine/graphqlite-bundle 3.0.x-dev requires thecodingmachine/graphqlite ^3 -> satisfiable by thecodingmachine/graphqlite[3.0.x-dev] but these conflict with your requirements or minimum-stability

Appears when fresh install bundle

Laravel relationship not working

When Declaring a model with an hasMany

<?php

class Company extends Model{
    /**
     * @Field()
     * @return Address[]|HasMany
     */
    public function addresses():array {
        return $this->hasMany('App\Address');
    }
}

I got this message:

  "message": "Return value of App\\Company::addresses() must be of the type array, object returned",
  "exception": "Symfony\\Component\\Debug\\Exception\\FatalThrowableError",

is this intended? I expect to directly query the related model

NB: i'm using v4 dev-master on laravel 6

Problem when install for first time

Error regarding stability appears in fresh installation
thecodingmachine/graphqlite-bundle 3.0.x-dev requires thecodingmachine/graphqlite ^3 -> satisfiable by thecodingmachine/graphqlite[3.0.x-dev] but these conflict with your requirements or minimum-stability

Conflicts in query/fields names are not handled

This code is currently valid:

    /**
     * @Query(name="getProduct")
     */
    public function getProduct(): SpecialProduct
    {
        return new SpecialProduct('Special box', 10.99);
    }

    /**
     * @Query(name="getProduct")
     */
    public function getProducts2(): ArrayIterator
    {
        return new ArrayIterator([new SpecialProduct('Special box', 10.99), new SpecialProduct('Special box', 10.99)]);
    }

Note: how the "name" attribute is used to pass the same query name twice.
This should throw an exception.

How to set SchemaFactory without a framework ?

Hello! I would like to create an implementation of GraphQL using the facilities of GraphQL Lite. Reading the documentation I can create a service without any framework. At this point, it's perfect! But I've had problems when I want to create an instance of SchemaFactory,because the construct need two instances: a $cache where the variable needs to be a PSR-16 compatible cache and $container where the variable needs to be a PSR-11 compatible container.

So, how can I create it ? could you help me ?

Thanks!

Add support for advanced exceptions handling

Currently, GraphQLite relies on ClientAware exceptions from Webonyx to return errors.

This is one of the only places where the end user should have to use directly Webonyx.

The current implementations has a few defaults, namely:

  • cannot return several errors for a given resolver
  • cannot add custom "extensions" in the error message (unless a custom Webonyx error formatter is used).

Unless webonyx/graphql-php#549 is accepted, we should probably provide our own exception handling.

Possible idea:

interface GraphQLiteClientExceptionInterface extends ClientAware {
     
}

For multiple exceptions:

interface GraphQLiteAggregateClientExceptionInterface extends ClientAware {
    /**
     * @return Throwable[]
     */
    public function getExceptions(): iterable;
}

In order to provide this, we need to provide a custom error handler and a custom error formatter.

This means we need to be able to provide those for all major frameworks, which in turns means we need to get back ownership of the HTTP middleware (this would also help for #103)

  • Integrate the PSR-15 HTTP middleware in GraphQLite
  • Document PSR-15 middleware
  • Modify the universal service provider to provide a PSR-15 middleware
  • Create interface GraphQLiteClientExceptionInterface
  • Create interface GraphQLiteAggregateClientExceptionInterface
  • Create default implementation for GraphQLiteAggregateClientExceptionInterface
  • Create error formatter
  • Create error handler
  • Write error handling documentation
  • Update documentation regarding integrating GraphQLite in a custom framework
  • Modify Bundle
  • Modify Laravel package

Use ocramius/package-version to purge the cache when the graphqlite version is updated

The cache can be incompatible between GraphQLite versions.

We should ensure that the cache is automatically invalidated each time a new version of GraphQLite is installed.

We can do that by using @Ocramius's package-version package and use the package-version as a namespace for the cache.

Note: all PSR-16 caches do not have a notion of "namespace". If we want to be able to target all PSR-16 caches, we might need to write an adapter for PSR-16 caches that adds a prefix to all the keys automatically.

[Questions] Array input, any scalar type and pagination improvement

It's not a bug, but just a question. In my direct webonyx/graphql implementation, I had an abstract grid schema like:

myGrid(
  filters: {
    id: { operator: in /* enum */, value: [1, 2, 3] },
    name: { operator: like, value: '%test%' }
  },
  sorting: {
    date: asc /* enum */
  },
  limit: { page: 0, size: 15}
) {
  isFirst
  isLast
  currentSize
  totalSize
  rows {
    id
    name
    // etc
  }
}

Now my questions are whether same schema is possible to be achieved in graphqlite. It doesn't have to be one to one, but:

  1. Can I add input which behaves like an array type of any scalar value?
  2. Can I have default values for the input?
  3. Can I extend what is displayed above rows, cause I saw in the documentation that you only return total size there
  4. Eager loading (deferred resolvers). I know someone asked already about it
  5. If those things are not possible, will they? and if they will, can we participate in development to speed things up?

[To Improve] Conditional return Type datas

We have a special case, for a ReservationType, we want to return the data only if it belongs to this person, so we have to add conditions on every "fields" today, as below. We would like to declare one condition for all or only certain "fields" without repeating functions in AbstractReservationType.

/**
     * @Field()
     * @param Reservation $reservation
     * @return Bill|null
     */
    public function getBill(Reservation $reservation): ?Bill
    {
        if ((($reservation->getClient() !== null) && ($this->userService->getUserId() === $reservation->getClient()->getId())) || $this->rightsService->isAllowed('IS_ADMIN')) {
            return $reservation->getBill();
        }
        return null;
    }

    /**
     * @Field()
     * @param Reservation $reservation
     * @return Client|null
     */
    public function getClient(Reservation $reservation): ?Client
    {
        if ((($reservation->getClient() !== null) && ($this->userService->getUserId() === $reservation->getClient()->getId())) || $this->rightsService->isAllowed('IS_ADMIN')) {
            return $reservation->getClient();
        }
        return null;
    }

Why mapClassToType return MutableObjectType

Method "mapClassToType" return "MutableObjectType", but "mapNameToType" return "Type"
and I can do this

    /**
     * @Field(outputType="Amount")
     */
    public function getEmail()
    {
        return $this->amount;
    }

But I can't do this

    /**
     * @Field
     */
    public function getEmail(): Amount
    {
        return $this->amount;
    }

Maybe I try to work wrong with Scalar Types?

Issue with union types in arrays

There seems to be a bug with union types in arrays or on iterators:

    /**
     * @Query(name="getProduct")
     * @return (\TheCodingMachine\GraphQLite\Fixtures\Integration\Models\Product|\TheCodingMachine\GraphQLite\Fixtures\Integration\Models\SpecialProduct)[]
     */
    public function getProducts2(): ArrayIterator
    {
        return new ArrayIterator([new SpecialProduct('Special box', 10.99), new SpecialProduct('Special box', 10.99)]);
    }

This causes this exception:

TheCodingMachine\GraphQLite\TypeMappingRuntimeException: Return type in TheCodingMachine\GraphQLite\Fixtures\Integration\Controllers\ProductController::getProducts2 is type-hinted to array. Please provide an additional @return in the PHPDoc block to further specify the type of the array. For instance: @return string[]

/home/david/projects/sandbox/graphql-controllers/src/TypeMappingRuntimeException.php:123
/home/david/projects/sandbox/graphql-controllers/src/Mappers/Parameters/TypeHandler.php:100
/home/david/projects/sandbox/graphql-controllers/src/FieldsBuilder.php:297
/home/david/projects/sandbox/graphql-controllers/src/FieldsBuilder.php:88
/home/david/projects/sandbox/graphql-controllers/src/AggregateControllerQueryProvider.php:43
/home/david/projects/sandbox/graphql-controllers/src/GlobControllerQueryProvider.php:121
/home/david/projects/sandbox/graphql-controllers/src/AggregateQueryProvider.php:34
/home/david/projects/sandbox/graphql-controllers/src/AggregateQueryProvider.php:35
/home/david/projects/sandbox/graphql-controllers/src/Schema.php:36
/home/david/projects/sandbox/graphql-controllers/vendor/webonyx/graphql-php/src/Type/Definition/FieldDefinition.php:90
/home/david/projects/sandbox/graphql-controllers/vendor/webonyx/graphql-php/src/Type/Definition/ObjectType.php:151
/home/david/projects/sandbox/graphql-controllers/vendor/webonyx/graphql-php/src/Utils/TypeInfo.php:180
/home/david/projects/sandbox/graphql-controllers/vendor/webonyx/graphql-php/src/Type/Schema.php:213
/home/david/projects/sandbox/graphql-controllers/vendor/webonyx/graphql-php/src/Type/Schema.php:199
/home/david/projects/sandbox/graphql-controllers/vendor/webonyx/graphql-php/src/Type/SchemaValidationContext.php:244
/home/david/projects/sandbox/graphql-controllers/vendor/webonyx/graphql-php/src/Type/Schema.php:517
/home/david/projects/sandbox/graphql-controllers/vendor/webonyx/graphql-php/src/Type/Schema.php:469
/home/david/projects/sandbox/graphql-controllers/tests/SchemaFactoryTest.php:144
/home/david/projects/sandbox/graphql-controllers/tests/SchemaFactoryTest.php:50

Caused by
TheCodingMachine\GraphQLite\TypeMappingRuntimeException: Don't know how to handle type (\TheCodingMachine\GraphQLite\Fixtures\Integration\Models\Product|\TheCodingMachine\GraphQLite\Fixtures\Integration\Models\SpecialProduct)[]

/home/david/projects/sandbox/graphql-controllers/src/TypeMappingRuntimeException.php:28
/home/david/projects/sandbox/graphql-controllers/src/Mappers/Parameters/TypeHandler.php:361
/home/david/projects/sandbox/graphql-controllers/src/Mappers/Parameters/TypeHandler.php:238
/home/david/projects/sandbox/graphql-controllers/src/Mappers/Parameters/TypeHandler.php:325
/home/david/projects/sandbox/graphql-controllers/src/Mappers/Parameters/TypeHandler.php:212
/home/david/projects/sandbox/graphql-controllers/src/Mappers/Parameters/TypeHandler.php:98
/home/david/projects/sandbox/graphql-controllers/src/FieldsBuilder.php:297
/home/david/projects/sandbox/graphql-controllers/src/FieldsBuilder.php:88
/home/david/projects/sandbox/graphql-controllers/src/AggregateControllerQueryProvider.php:43
/home/david/projects/sandbox/graphql-controllers/src/GlobControllerQueryProvider.php:121
/home/david/projects/sandbox/graphql-controllers/src/AggregateQueryProvider.php:34
/home/david/projects/sandbox/graphql-controllers/src/AggregateQueryProvider.php:35
/home/david/projects/sandbox/graphql-controllers/src/Schema.php:36
/home/david/projects/sandbox/graphql-controllers/vendor/webonyx/graphql-php/src/Type/Definition/FieldDefinition.php:90
/home/david/projects/sandbox/graphql-controllers/vendor/webonyx/graphql-php/src/Type/Definition/ObjectType.php:151
/home/david/projects/sandbox/graphql-controllers/vendor/webonyx/graphql-php/src/Utils/TypeInfo.php:180
/home/david/projects/sandbox/graphql-controllers/vendor/webonyx/graphql-php/src/Type/Schema.php:213
/home/david/projects/sandbox/graphql-controllers/vendor/webonyx/graphql-php/src/Type/Schema.php:199
/home/david/projects/sandbox/graphql-controllers/vendor/webonyx/graphql-php/src/Type/SchemaValidationContext.php:244
/home/david/projects/sandbox/graphql-controllers/vendor/webonyx/graphql-php/src/Type/Schema.php:517
/home/david/projects/sandbox/graphql-controllers/vendor/webonyx/graphql-php/src/Type/Schema.php:469
/home/david/projects/sandbox/graphql-controllers/tests/SchemaFactoryTest.php:144
/home/david/projects/sandbox/graphql-controllers/tests/SchemaFactoryTest.php:50

Related to #167

Enum types value incorrect

as example shows from : https://graphqlite.thecodingmachine.io/docs/next/type_mapping

use MyCLabs\Enum\Enum;

class StatusEnum extends Enum
{
    private const ON = 'on';
    private const OFF = 'off';
    private const PENDING = 'pending';
}

/**
 * @Query
 * @return User[]
 */
public function users(StatusEnum $status): array
{
    if ($status === StatusEum::ON()) {
        // ...
    }
    // ...
}

The condition "$status === StatusEum::ON()" will always be false, since it's two different objects, with an unknow cache.

After I changed to this, it gets working :

class StatusEnum extends Enum
{
    public const ON = 'on';
    public const OFF = 'off';
    public const PENDING = 'pending';
}

if ($status->getValue() === StatusEum::ON)

substr() expects parameter 1 to be string, int given

Version: 4 (50fb4df)

When executing a GET request to the /graphql endpoint, I'm getting this error:

substr() expects parameter 1 to be string, int given
thecodingmachine/graphqlite/src/Utils/NamespacedCache.php:106

The full stack is a long one, included below. I should clarify that the value it's complaining about is zero, 0.

Any thoughts?

{
  "trace": [
    {
      "file": "/.../.../.../api.php",
      "line": 34,
      "function": "run",
      "class": "Zend\\HttpHandlerRunner\\RequestHandlerRunner",
      "type": "->",
      "args": []
    },
    {
      "file": "/srv/www/vendor/zendframework/zend-httphandlerrunner/src/RequestHandlerRunner.php",
      "line": 95,
      "function": "handle",
      "class": "xxxx\\Cog\\GraphQL\\Request\\Handler",
      "type": "->",
      "args": [
        {}
      ]
    },
    {
      "file": "/srv/www/src/xxxx/Cog/GraphQL/Request/Handler.php",
      "line": 46,
      "function": "handle",
      "class": "Zend\\Stratigility\\MiddlewarePipe",
      "type": "->",
      "args": "*** RECURSION ***"
    },
    {
      "file": "/srv/www/vendor/zendframework/zend-stratigility/src/MiddlewarePipe.php",
      "line": 72,
      "function": "process",
      "class": "Zend\\Stratigility\\MiddlewarePipe",
      "type": "->",
      "args": [
        "*** RECURSION ***",
        {}
      ]
    },
    {
      "file": "/srv/www/vendor/zendframework/zend-stratigility/src/MiddlewarePipe.php",
      "line": 83,
      "function": "handle",
      "class": "Zend\\Stratigility\\Next",
      "type": "->",
      "args": "*** RECURSION ***"
    },
    {
      "file": "/srv/www/vendor/zendframework/zend-stratigility/src/Next.php",
      "line": 60,
      "function": "process",
      "class": "TheCodingMachine\\GraphQLite\\Http\\WebonyxGraphqlMiddleware",
      "type": "->",
      "args": [
        "*** RECURSION ***",
        {}
      ]
    },
    {
      "file": "/srv/www/vendor/thecodingmachine/graphqlite/src/Http/WebonyxGraphqlMiddleware.php",
      "line": 94,
      "function": "executePsrRequest",
      "class": "GraphQL\\Server\\StandardServer",
      "type": "->",
      "args": "*** RECURSION ***"
    },
    {
      "file": "/srv/www/vendor/webonyx/graphql-php/src/Server/StandardServer.php",
      "line": 170,
      "function": "executeRequest",
      "class": "GraphQL\\Server\\StandardServer",
      "type": "->",
      "args": [
        {
          "queryId": null,
          "query": "{ hello(name: Jacob)}",
          "operation": null,
          "variables": null,
          "extensions": null
        }
      ]
    },
    {
      "file": "/srv/www/vendor/webonyx/graphql-php/src/Server/StandardServer.php",
      "line": 135,
      "function": "executeOperation",
      "class": "GraphQL\\Server\\Helper",
      "type": "->",
      "args": [
        {},
        "*** RECURSION ***"
      ]
    },
    {
      "file": "/srv/www/vendor/webonyx/graphql-php/src/Server/Helper.php",
      "line": 202,
      "function": "promiseToExecuteOperation",
      "class": "GraphQL\\Server\\Helper",
      "type": "->",
      "args": [
        {},
        "*** RECURSION ***",
        "*** RECURSION ***"
      ]
    },
    {
      "file": "/srv/www/vendor/webonyx/graphql-php/src/Server/Helper.php",
      "line": 295,
      "function": "promiseToExecute",
      "class": "GraphQL\\GraphQL",
      "type": "::",
      "args": [
        "*** RECURSION ***",
        {
          "extensionASTNodes": null
        },
        {
          "kind": "Document",
          "definitions": "*** NO CLUE - CLOSURE? ***",
          "loc": {
            "start": 0,
            "end": 21,
            "startToken": {
              "kind": "<SOF>",
              "start": 0,
              "end": 0,
              "line": 0,
              "column": 0,
              "value": null,
              "prev": null,
              "next": {
                "kind": "{",
                "start": 0,
                "end": 1,
                "line": 1,
                "column": 1,
                "value": null,
                "prev": "*** RECURSION ***",
                "next": {
                  "kind": "Name",
                  "start": 2,
                  "end": 7,
                  "line": 1,
                  "column": 3,
                  "value": "hello",
                  "prev": "*** RECURSION ***",
                  "next": {
                    "kind": "(",
                    "start": 7,
                    "end": 8,
                    "line": 1,
                    "column": 8,
                    "value": null,
                    "prev": "*** RECURSION ***",
                    "next": {
                      "kind": "Name",
                      "start": 8,
                      "end": 12,
                      "line": 1,
                      "column": 9,
                      "value": "name",
                      "prev": "*** RECURSION ***",
                      "next": {
                        "kind": ":",
                        "start": 12,
                        "end": 13,
                        "line": 1,
                        "column": 13,
                        "value": null,
                        "prev": "*** RECURSION ***",
                        "next": {
                          "kind": "Name",
                          "start": 14,
                          "end": 19,
                          "line": 1,
                          "column": 15,
                          "value": "Jacob",
                          "prev": "*** RECURSION ***",
                          "next": {
                            "kind": ")",
                            "start": 19,
                            "end": 20,
                            "line": 1,
                            "column": 20,
                            "value": null,
                            "prev": "*** RECURSION ***",
                            "next": {
                              "kind": "}",
                              "start": 20,
                              "end": 21,
                              "line": 1,
                              "column": 21,
                              "value": null,
                              "prev": "*** RECURSION ***",
                              "next": {
                                "kind": "<EOF>",
                                "start": 21,
                                "end": 21,
                                "line": 1,
                                "column": 22,
                                "value": null,
                                "prev": "*** RECURSION ***",
                                "next": null
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            },
            "endToken": "*** RECURSION ***",
            "source": {
              "body": "{ hello(name: Jacob)}",
              "length": 21,
              "name": "GraphQL request",
              "locationOffset": {
                "line": 1,
                "column": 1
              }
            }
          }
        },
        null,
        {},
        null,
        null,
        null,
        null
      ]
    },
    {
      "file": "/srv/www/vendor/webonyx/graphql-php/src/GraphQL.php",
      "line": 149,
      "function": "validate",
      "class": "GraphQL\\Validator\\DocumentValidator",
      "type": "::",
      "args": [
        "*** RECURSION ***",
        "*** RECURSION ***",
        {
          "GraphQL\\Validator\\Rules\\ExecutableDefinitions": {},
          "GraphQL\\Validator\\Rules\\UniqueOperationNames": {
            "knownOperationNames": []
          },
          "GraphQL\\Validator\\Rules\\LoneAnonymousOperation": {},
          "GraphQL\\Validator\\Rules\\KnownTypeNames": {},
          "GraphQL\\Validator\\Rules\\FragmentsOnCompositeTypes": {},
          "GraphQL\\Validator\\Rules\\VariablesAreInputTypes": {},
          "GraphQL\\Validator\\Rules\\ScalarLeafs": {},
          "GraphQL\\Validator\\Rules\\FieldsOnCorrectType": {},
          "GraphQL\\Validator\\Rules\\UniqueFragmentNames": {
            "knownFragmentNames": []
          },
          "GraphQL\\Validator\\Rules\\KnownFragmentNames": {},
          "GraphQL\\Validator\\Rules\\NoUnusedFragments": {
            "operationDefs": [
              {
                "kind": "OperationDefinition",
                "name": null,
                "operation": "query",
                "variableDefinitions": "*** NO CLUE - CLOSURE? ***",
                "directives": "*** NO CLUE - CLOSURE? ***",
                "selectionSet": {
                  "kind": "SelectionSet",
                  "selections": "*** NO CLUE - CLOSURE? ***",
                  "loc": {
                    "start": 0,
                    "end": 21,
                    "startToken": "*** RECURSION ***",
                    "endToken": "*** RECURSION ***",
                    "source": "*** RECURSION ***"
                  }
                },
                "loc": {
                  "start": 0,
                  "end": 21,
                  "startToken": "*** RECURSION ***",
                  "endToken": "*** RECURSION ***",
                  "source": "*** RECURSION ***"
                }
              }
            ],
            "fragmentDefs": []
          },
          "GraphQL\\Validator\\Rules\\PossibleFragmentSpreads": {},
          "GraphQL\\Validator\\Rules\\NoFragmentCycles": {
            "visitedFrags": [],
            "spreadPath": [],
            "spreadPathIndexByName": []
          },
          "GraphQL\\Validator\\Rules\\UniqueVariableNames": {
            "knownVariableNames": []
          },
          "GraphQL\\Validator\\Rules\\NoUndefinedVariables": {},
          "GraphQL\\Validator\\Rules\\NoUnusedVariables": {
            "variableDefs": []
          },
          "GraphQL\\Validator\\Rules\\KnownDirectives": {},
          "GraphQL\\Validator\\Rules\\UniqueDirectivesPerLocation": {},
          "GraphQL\\Validator\\Rules\\KnownArgumentNames": {},
          "GraphQL\\Validator\\Rules\\UniqueArgumentNames": {
            "knownArgNames": []
          },
          "GraphQL\\Validator\\Rules\\ValuesOfCorrectType": {},
          "GraphQL\\Validator\\Rules\\ProvidedNonNullArguments": {},
          "GraphQL\\Validator\\Rules\\VariablesDefaultValueAllowed": {},
          "GraphQL\\Validator\\Rules\\VariablesInAllowedPosition": {
            "varDefMap": []
          },
          "GraphQL\\Validator\\Rules\\OverlappingFieldsCanBeMerged": {},
          "GraphQL\\Validator\\Rules\\UniqueInputFieldNames": {
            "knownNames": [],
            "knownNameStack": []
          },
          "GraphQL\\Validator\\Rules\\DisableIntrospection": {},
          "GraphQL\\Validator\\Rules\\QueryDepth": {},
          "GraphQL\\Validator\\Rules\\QueryComplexity": {}
        }
      ]
    },
    {
      "file": "/srv/www/vendor/webonyx/graphql-php/src/Validator/DocumentValidator.php",
      "line": 118,
      "function": "visitUsingRules",
      "class": "GraphQL\\Validator\\DocumentValidator",
      "type": "::",
      "args": [
        "*** RECURSION ***",
        {},
        "*** RECURSION ***",
        "*** RECURSION ***"
      ]
    },
    {
      "file": "/srv/www/vendor/webonyx/graphql-php/src/Validator/DocumentValidator.php",
      "line": 226,
      "function": "visit",
      "class": "GraphQL\\Language\\Visitor",
      "type": "::",
      "args": [
        "*** RECURSION ***",
        {
          "enter": {},
          "leave": {}
        }
      ]
    },
    {
      "file": "/srv/www/vendor/webonyx/graphql-php/src/Language/Visitor.php",
      "line": 273,
      "function": "GraphQL\\Language\\{closure}",
      "class": "GraphQL\\Language\\Visitor",
      "type": "::",
      "args": [
        "*** RECURSION ***",
        "selectionSet",
        "*** RECURSION ***",
        [
          "definitions",
          0,
          "selectionSet"
        ],
        [
          "*** RECURSION ***",
          "*** RECURSION ***"
        ]
      ]
    },
    {
      "file": "/srv/www/vendor/webonyx/graphql-php/src/Language/Visitor.php",
      "line": 466,
      "function": "GraphQL\\Language\\{closure}",
      "class": "GraphQL\\Language\\Visitor",
      "type": "::",
      "args": "*** RECURSION ***"
    },
    {
      "file": "/srv/www/vendor/webonyx/graphql-php/src/Language/Visitor.php",
      "line": 410,
      "function": "GraphQL\\Validator\\Rules\\{closure}",
      "class": "GraphQL\\Validator\\Rules\\OverlappingFieldsCanBeMerged",
      "type": "->",
      "args": "*** RECURSION ***"
    },
    {
      "file": "/srv/www/vendor/webonyx/graphql-php/src/Validator/Rules/OverlappingFieldsCanBeMerged.php",
      "line": 65,
      "function": "findConflictsWithinSelectionSet",
      "class": "GraphQL\\Validator\\Rules\\OverlappingFieldsCanBeMerged",
      "type": "->",
      "args": [
        {},
        "Query",
        "*** RECURSION ***"
      ]
    },
    {
      "file": "/srv/www/vendor/webonyx/graphql-php/src/Validator/Rules/OverlappingFieldsCanBeMerged.php",
      "line": 97,
      "function": "getFieldsAndFragmentNames",
      "class": "GraphQL\\Validator\\Rules\\OverlappingFieldsCanBeMerged",
      "type": "->",
      "args": "*** RECURSION ***"
    },
    {
      "file": "/srv/www/vendor/webonyx/graphql-php/src/Validator/Rules/OverlappingFieldsCanBeMerged.php",
      "line": 166,
      "function": "internalCollectFieldsAndFragmentNames",
      "class": "GraphQL\\Validator\\Rules\\OverlappingFieldsCanBeMerged",
      "type": "->",
      "args": [
        "*** RECURSION ***",
        "*** RECURSION ***",
        "*** RECURSION ***",
        [],
        []
      ]
    },
    {
      "file": "/srv/www/vendor/webonyx/graphql-php/src/Validator/Rules/OverlappingFieldsCanBeMerged.php",
      "line": 255,
      "function": "getFields",
      "class": "GraphQL\\Type\\Definition\\ObjectType",
      "type": "->",
      "args": []
    },
    {
      "file": "/srv/www/vendor/webonyx/graphql-php/src/Type/Definition/ObjectType.php",
      "line": 151,
      "function": "defineFieldMap",
      "class": "GraphQL\\Type\\Definition\\FieldDefinition",
      "type": "::",
      "args": [
        "*** RECURSION ***",
        "*** RECURSION ***"
      ]
    },
    {
      "file": "/srv/www/vendor/webonyx/graphql-php/src/Type/Definition/FieldDefinition.php",
      "line": 90,
      "function": "TheCodingMachine\\GraphQLite\\{closure}",
      "class": "TheCodingMachine\\GraphQLite\\Schema",
      "type": "::",
      "args": []
    },
    {
      "file": "/srv/www/vendor/thecodingmachine/graphqlite/src/Schema.php",
      "line": 36,
      "function": "getQueries",
      "class": "TheCodingMachine\\GraphQLite\\AggregateQueryProvider",
      "type": "->",
      "args": []
    },
    {
      "file": "/srv/www/vendor/thecodingmachine/graphqlite/src/AggregateQueryProvider.php",
      "line": 35,
      "function": "array_map",
      "args": [
        {},
        [
          {}
        ]
      ]
    },
    {
      "function": "TheCodingMachine\\GraphQLite\\{closure}",
      "class": "TheCodingMachine\\GraphQLite\\AggregateQueryProvider",
      "type": "::",
      "args": "*** RECURSION ***"
    },
    {
      "file": "/srv/www/vendor/thecodingmachine/graphqlite/src/AggregateQueryProvider.php",
      "line": 34,
      "function": "getQueries",
      "class": "TheCodingMachine\\GraphQLite\\GlobControllerQueryProvider",
      "type": "->",
      "args": []
    },
    {
      "file": "/srv/www/vendor/thecodingmachine/graphqlite/src/GlobControllerQueryProvider.php",
      "line": 121,
      "function": "getAggregateControllerQueryProvider",
      "class": "TheCodingMachine\\GraphQLite\\GlobControllerQueryProvider",
      "type": "->",
      "args": []
    },
    {
      "file": "/srv/www/vendor/thecodingmachine/graphqlite/src/GlobControllerQueryProvider.php",
      "line": 68,
      "function": "getInstancesList",
      "class": "TheCodingMachine\\GraphQLite\\GlobControllerQueryProvider",
      "type": "->",
      "args": []
    },
    {
      "file": "/srv/www/vendor/thecodingmachine/graphqlite/src/GlobControllerQueryProvider.php",
      "line": 84,
      "function": "get",
      "class": "Symfony\\Component\\Cache\\Adapter\\AbstractAdapter",
      "type": "->",
      "args": [
        "globQueryProvider",
        {}
      ]
    },
    {
      "file": "/srv/www/vendor/symfony/cache-contracts/CacheTrait.php",
      "line": 30,
      "function": "doGet",
      "class": "Symfony\\Component\\Cache\\Adapter\\AbstractAdapter",
      "type": "->",
      "args": [
        {},
        "globQueryProvider",
        "*** RECURSION ***",
        1,
        null
      ]
    },
    {
      "file": "/srv/www/vendor/symfony/cache/Traits/ContractsTrait.php",
      "line": 95,
      "function": "doGet",
      "class": "Symfony\\Component\\Cache\\Adapter\\AbstractAdapter",
      "type": "->",
      "args": [
        "*** RECURSION ***",
        "globQueryProvider",
        {},
        1,
        null,
        null
      ]
    },
    {
      "file": "/srv/www/vendor/symfony/cache-contracts/CacheTrait.php",
      "line": 48,
      "function": "getItem",
      "class": "Symfony\\Component\\Cache\\Adapter\\AbstractAdapter",
      "type": "->",
      "args": [
        "globQueryProvider"
      ]
    },
    {
      "file": "/srv/www/vendor/symfony/cache/Traits/AbstractAdapterTrait.php",
      "line": 51,
      "function": "doFetch",
      "class": "Symfony\\Component\\Cache\\Adapter\\Psr16Adapter",
      "type": "->",
      "args": [
        [
          "xxxx_Controller_globQueryProvider"
        ]
      ]
    },
    {
      "file": "/srv/www/vendor/symfony/cache/Adapter/Psr16Adapter.php",
      "line": 48,
      "function": "getMultiple",
      "class": "TheCodingMachine\\GraphQLite\\Utils\\NamespacedCache",
      "type": "->",
      "args": [
        "*** RECURSION ***",
        {}
      ]
    },
    {
      "file": "/srv/www/vendor/thecodingmachine/graphqlite/src/Utils/NamespacedCache.php",
      "line": 106,
      "function": "substr",
      "args": [
        0,
        8
      ]
    }
  ],
  "message": "substr() expects parameter 1 to be string, int given",
  "file": "/srv/www/vendor/thecodingmachine/graphqlite/src/Utils/NamespacedCache.php",
  "line": 106,
  "class": "TypeError",

Requested fields for a type?

Hello

Is there any way to read the requested fields for certain type?
For example I have a Product with many fields (brand, category, name, id) but client only wants the name. Can I get that in resolver or a dependency that offers it?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.