webonyx / graphql-php Goto Github PK
View Code? Open in Web Editor NEWPHP implementation of the GraphQL specification based on the reference implementation in JavaScript
Home Page: https://webonyx.github.io/graphql-php
License: MIT License
PHP implementation of the GraphQL specification based on the reference implementation in JavaScript
Home Page: https://webonyx.github.io/graphql-php
License: MIT License
Thank you very much for this great library.
I've got a question about Interfaces and validation
To play nice with relay it seems easier to follow the "node / viewer" pattern.
So for example a single entry point for all characters ( the node query ) could be implemented like this
return [
'name' => 'Query',
'fields' => [
'character' => [
'type' => $characterInterface,
'args' => [
'id' => [
'name' => 'id',
'description' => 'global id of the character',
'type' => Type::nonNull(Type::string())
]
],
'resolve' => function ($root, $args) {
return //.. get character by global id
}
]
//...
]
];
Then we rely on the resolveType function of our character interface to return a droid or a human type.
So far so good!
But if I want to add a field that only exists for droids let say "modelNumber" I run into a validation problem, DocumentValidator fails as it only checks the fields of the interface, and not the fields of my resolved type. Is this a bug ?
This is possibly related to #22. When I try to define the fields
as a callback function for an InterfaceType
, I get an error:
Argument 1 passed to GraphQL\Type\Definition\FieldDefinition::createMap() must be of the type array, object given, called in /Users/ivo/Documents/workspace/graphql-relay-php/vendor/webonyx/graphql-php/src/Type/Definition/InterfaceType.php on line 66 and defined
There is a new version of the GraphQL spec April2016.
It changed some parts in the introspection.
I might have time to open a PR for this, but probably at earliest in two weeks.
Deprecate following field definition form:
[
'name' => 'Foo',
'fields' => [
'bar' => ['type' => function() use(&$barType) { return $barType;}],
'baz' => ['type' => function() use(&$bazType) { return $bazType;}],
]
]
in favor of:
[
'name' => 'Foo',
'fields' => function() use (&$barType, &$bazType) {
return [
'bar' => ['type' => $barType],
'baz' => ['type' => $bazType],
];
}
]
Well, this isn't really an issue, but more an encouragement to keep up the good work -- this project is going to be really helpful for PHP users.
Thanks (and +1 star)
๐
Currently all types (including all fields) are loaded on schema instance creation.
It is suboptimal for PHP, but it works this way because in some cases we need to know all interface implementors (for validation purposes - both in Validator and in Executor). And the only way to find them is to loop through all types and check if type implements given interface. Hence all types must be loaded for interface-related checks.
The goal of this task is to find a way for lazy-loading types in schema. This is possible, yet requires additional hints from user-land code to work efficiently (e.g. #40).
Its not clear how implement fragment in php code ??
Any example for that ??
Hi,
I'd like to make mention of a project I am following and while looking through their docs, they mention an "invalidation server".
https://github.com/apollostack/apollo/blob/master/design/high-level-reactivity.md
@vladar - can you imagine this also being possible within graphql-php or is it a separate project? I'd imagine separate. What are your thoughts on the possibility of a "reactive graphQL" PHP application server?
Scott
Resolving fields with map
works really well (and fast!) when the parent type is a list. However, when using Relay connections, it's impossible to use map
on the "connected" type, since its parent is an edge and not the list of edges.
An example:
UserType -> todos -> TodoConnection -> edges -> EdgeType -> node -> TodoType -> items*
I want to use map
on items
but I cant since the root argument will contain an array with only one element - the edge.
Is there any way to work around this, or is it perhaps something you would consider improving?
I tried, no warning in php side, but the graphiql does work.
When I first learned about GraphQL there was a performance chapter. If every relation loads every single relation, it requires many queries to load a few levels of data, even if the actual data is only on the last level. The solution was to lazy load objects: first collect ids (horizontal), and then load all objects at once, and then evaluate the next level.
Just in case. This query:
query {
user {
reservations {
resource {
sport {
name
}
}
}
}
}
would take 20 queries to find 10 reservations > 10x 1 resource > 10x 1 sport. If it would eval horizontally, it would do 2 queries: load 10 resources, then load 10 sports. (Maybe not 2 levels, but even 1 could be a big difference.)
Is that a thing? It seems like the query is evaled vertically first, so it loads the entire 1 reservation (1 resource + 1 sport), and then the entire 1 next (1 resource + 1 sport) etc.
Do you have any plan on implementing this:
graphql/graphql-js#189
http://graphql.org/blog/subscriptions-in-graphql-and-relay/
Hi,
First of all, let me thank you for this great library!
We're getting a Fatal error when using graphql-php after last commit, yesterday it works fine.
PHP Deprecated: GraphQL\Schema constructor expects config object now instead of types passed as arguments. See #36 in /var/www/tnc2016/vendor/webonyx/graphql-php/src/Schema.php on line 62
PHP Fatal error: Class 'PHPUnit_Framework_TestCase' not found in /var/www/tnc2016/vendor/webonyx/graphql-php/src/Validator/Rules/LoneAnonymousOperation.php on line 18
Please contact us if you want more info on reproducing this bug.
If an error was encountered before execution begins, the data entry should not be present in the result.
https://facebook.github.io/graphql/#sec-Data
Currently this is not the case. I know it's a minor thing, but it's worth fixing to be fully compliant with the spec.
It seems very inefficient to load the entire schema for every query every time. I have a very small schema (5 types, 1 interface) and it takes 10 ms just to define the schema. Would it be possible to hint to class names instead of objects. This would also 'fix' the many &$postType
references (and closures), because the objects are really lazy loaded.
I've no idea if this would be possible. It might have huge downsides. It definitely takes a lot of change. You might've tried something like this already.
Just to clarify, this is what it would look like (in my naive mind):
$schema = new Schema([
'query' => QueryType::class,
'types' => [
UserType::class,
CourtReservationType::class,
ClassReservationType::class,
PlayerType::class,
],
]);
Schema or Executor or anyone could cache the objects by class name. All references would be class names:
'type' => Type::listOf(ReservationInterface::class),
'interfaces' => [ReservationInterface::class],
etc.
This would make the simple examples more complex, but reality simpler IMO. Every type would require a class, but not memory, until needed.
Like I said, no idea how feasible this is. Just a thought.
I've been messing around with this project a bit lately, and I've had some trouble with performance. The GraphQL executor does quite a bit of unnecessary work when it resolves fields. Since it operates recursively, larger lists have a really bad impact on performance. I have tried to fix some of the bottlenecks by memoizing objects and values that can be re-used.
I'd like to hear what you think about this approach. According to the Blackfire profiler the response was 2x faster for a large query and 1.5x faster for one I use in my app.
You can check out my fork here: https://github.com/johanobergman/graphql-php
It throws
\nFatal error: Object does not implement ArrayAccess in graphql-php/src/Type/Definition/FieldDefinition.php on line 89
Code:
$queryType = new ObjectType([
'name' => 'Query',
'fields'=> [
'today' => [
'type' => Type::string(),
'resolve' => function($value, $args, $info)
{
return date('Y-m-d H:ia');
}
]
]
]);
$schema = new Schema($queryType, null);
try {
// Define your schema:
$this->result = GraphQL::execute(
$schema,
$this->query,
/* $rootValue */ null,
null,
null
);
} catch (Exception $exception) {
$this->result = [
'errors' => [
['message' => $exception->getMessage()]
]
];
}
Request Query used was:
query try{
today {
}
}
Thanks!
As described in the issue folkloreinc/laravel-graphql#50 the type name is null when using listOf.
Query:
{
__type(name: "User") {
name
fields {
name
type {
name
}
}
}
}
Response:
{
"data":{
"__type":{
"name": "User",
"fields":[
{"name": "followings", "type":{"name": null }},
]
}
}
}
I suspect this is because public $name
is not set in the ListOfType. It may be setted in the constructor. I can submit a pull request if it makes sense.
I have a BIG problem with memoized results, in this example with memoizing on I get faulty result on the leaf node:
Query:
{
surveys(school_category:SCHOOL) {
nid
modules {
survey
}
}
}
Faulty result, survey
in each module should match the nid
from the survey.
{
"data": {
"surveys": [
{
"nid": 85,
"modules": [
{
"survey": 85
},
{
"survey": 85
},
{
"survey": 85
},
{
"survey": 85
},
{
"survey": 85
},
{
"survey": 85
},
{
"survey": 85
}
]
},
{
"nid": 707,
"modules": [
{
"survey": 85
},
{
"survey": 85
},
{
"survey": 85
},
{
"survey": 85
},
{
"survey": 85
},
{
"survey": 85
},
{
"survey": 85
}
]
}
]
}
}
And if I turn off memoization (Executor.php#L366-L372) I get the correct result:
{
"data": {
"surveys": [
{
"nid": 85,
"modules": [
{
"survey": 85
},
{
"survey": 85
},
{
"survey": 85
},
{
"survey": 85
},
{
"survey": 85
},
{
"survey": 85
},
{
"survey": 85
}
]
},
{
"nid": 707,
"modules": [
{
"survey": 707
},
{
"survey": 707
},
{
"survey": 707
},
{
"survey": 707
},
{
"survey": 707
},
{
"survey": 707
},
{
"survey": 707
}
]
}
]
}
}
I'm not sure it's safe to memoize using the AST location and fieldname, my recommendation is to remove this performance optimization now until someone figures out how to look at the parent if that is different than previously memoized, but also that doesn't take into account if say any contextual parameter has changed. Say the user is added in some level of the graph and passed down, although atm it doesn't seem to be possible to reference the context in the resolvers to be able to add stuff (this works in the JavaScript implementation, context is passed as the third parameter). But that seems like another issue.
When creating a root query & mutation with the same result type, an exception is thrown:
Schema must contain unique named types but contains multiple types named "<type>".
I would think it's rather common to reuse the same type โ e.g. when returning an instance after creating it.
The express-graphql
implementation allows this, I quickly checked.
When commenting out that check in \GraphQL\Schema
everything seems to work, too.. Any particular reason for this invariant?
PS: Great library, very useful! ;)
For those wondering for a much simpler introduction to the lib:
https://gist.github.com/leocavalcante/9e61ca6065130e37737f24892d81fa40
Without more information this project looks interesting but I'm not sure how to even get started. Is this something you're going to be expanding on in the future?
When using GraphQL to a higher level you might need promise resolver to manage data loader by example. My proposal it to implement this missing feature. We can use one of those two lib to full fill that need:
amphp/amp look to be the right solution but required php >= 5.5...
what do you think of this?
Is there an easy/efficient way to get the ObjectType's name and Field from the query string? I want to wrap my requests in some middleware based on these, but I haven't found an easy way to get access to them.
For example, if my server was sent the following query:
{
userQuery(email: "[email protected]") {
id
firstName
lastName
}
}
I am trying to determine if this is a Query/Mutation/Subscription Type (query in this example) and the name of the ObjectType's field (userQuery in this example). Any help would be appreciated!!
Currently Introspection for __InputValue.defaultValue
uses json_encode
, which works for simple input values, but fails when input type is a complex InputObjectType
.
Currently introspection produces following value:
[{"selectId":"value","selectedOptions":["a", "b", "c"]}]
While GraphQL JS (and Relay) expect:
[{selectId: "value", selectedOptions: ["a", "b", "c"]}]
Need to implement custom astFromValue
helper and use Printer
instead of json_encode
.
query { user { reservations } }
returns ReservationInterface
s, implemented by XReservation
and YReservation
. Let's say it returns 2 XReservations and 2 YReservations. If you add an inline fragment to only return anything for XReservations, the 2 YReservations will be completely empty. An empty array in PHP translates to an empty array in JSON, instead of an empty object.
Query:
{
user {
reservations {
... on XReservation {
id
}
}
}
}
Result:
reservations: [
{id},
{id},
[],
[]
]
I think those 2 []
should be {}
, because they're known objects, not lists.
Reference implementation has nice findBreakingChanges
utility which allows to compare two schemas to figure out if newer one contains breaking changes.
That would be great if we also have an example on file uploading example.
Thanks!
After this change following code will trigger deprecation notice:
$schema = new GraphQL\Schema($objectType, $mutationType);
It should be rewritten with
$schema = new GraphQL\Schema([
'query' => $objectType,
'mutation' => $mutationType,
// Other possible options:
// 'directives' => $directives
// 'subscription' => $subscription,
// 'types' => $types
])
Splitting ObjectType#initialize
in two methods would solve the issue.
When using recursive types with interfaces InterfaceType::addImplementationToInterfaces
, ObjectType#initialize
and ObjectType#getInterfaces
result in an infinite loop. Using the same implementation as the JS version would solve the issue since getInterfaces
would not trigger getFields
.
I am having some issues with the new lazy loading of interfaces that was implemented:
I have all of my fields defined in callback functions and inside of those callback functions I have a typeRegistry that creates the types on demand. That has the massive advantage that the instances of the types are only created when they are needed.
That worked fine until the latest version when lazy loading of interfaces was introduced. The loading of the interfaces seems to be called on schema creation:
https://github.com/webonyx/graphql-php/blob/master/src/Schema.php#L36
So the types with interfaces are not known at this point in my setup, because the callback functions that create the types did not execute yet.
When the interface is called, I'm getting an error, because the types are not recognized properly.
The following example query:
query Query {
node(id: "UmVnaW9uOjRhYWNkZTQwNzBhZWIxY2RhZTAy") {
... on User {
firstname
}
id
}
}
Results in an error:
{
"data": null,
"errors": [
{
"message": "Fragment cannot be spread here as objects of type \"Node\" can never be of type \"User\".",
"locations": [
{
"line": 3,
"column": 5
}
]
}
]
}
If I change the function getPossibleTypes
to the following, it works again, at least the error is gone
public function getPossibleTypes()
{
if (self::$_lazyLoadImplementations) {
self::loadImplementationToInterfaces();
}
return $this->_implementations;
}
The node is resolved properly but I don't get the fields returned from the fragments. That was also not working with the previous version v0.5.7.
So I'm wondering if there is something else wrong with the InterfaceType, possibly in combination with the definition of the fields in the callback functions. But I feel like creating them on demand would be the most performant way.
What do we need the lazy loading for, if the function loadImplementationToInterfaces
is called in the Schema constructor?
Any ideas how I should fix that?
Currently for field (such as currency in below example), only array type definition is allowed (has to be an array within which defines a "type"). In other GraphQL implementations such as https://github.com/Youshido/GraphQL, a simpler shorthand like this is allowed:
Proposed
'currency' => Type::string()
Current
$ProductType = new ObjectType([
'name' => 'Product',
'fields' => [
'title' => [
'type' => Type::string()
],
'priceStr' => [
'type' => Type::string()
],
'currency' => [
'type' => Type::string()
]
]
]);
I have a rough code change that can convert to a pull request, if more ppl find it handy.
I'm using the laravel-graphql, and created an issue but I'm not sure if it's something that should actually be created here (still trying to wrap my head around GraphQL).
When working with Relay, it expects that mutations have only one argument called input. Looking at the todo example, in the schema.json file it has that field listed as an INPUT_OBJECT type. I created the following (again, using laravel-graphql), but I'm unable to get the fields from the InputObjectType. Am I doing something wrong?
namespace App\GraphQL\Mutations;
use GraphQL;
use App\Models\Customer;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\InputObjectType;
use Folklore\GraphQL\Support\Mutation;
class UpdateCustomerEmail extends Mutation
{
/**
* Associated GraphQL Type.
*
* @return mixed
*/
public function type()
{
return GraphQL::type('customerPayload');
}
/**
* Available arguments for mutation.
*
* @return array
*/
public function args()
{
return [
'input' => [
'name' => 'input',
'type' => new InputObjectType([
'name' => 'updateCustomerEmailInput',
'fields' => [
'entity_id' => [
'name' => 'entity_id',
'type' => Type::nonNull(Type::string())
],
'email' => [
'name' => 'email',
'type' => Type::nonNull(Type::string())
]
]
])
]
];
}
/**
* Resolve mutation.
*
* @param string $root
* @param array $args
* @return Customer
*/
public function resolve($root, $args, $info)
{
// $root is null, $args is an empty array and nothing in $info gives me the
// variables passed in the request
$customer = Customer::find($args['input']['entity_id']); // $args['input'] is not set
$customer->email = $args['input']['email'];
return $customer;
}
}
Now when interface implementor is not referenced anywhere in schema it remains invisible for GraphQL
during validation phase.
As a workaround we allow types
option on Schema
to pass such implementors directly. But a better way for this could be adding implementors
option to interface type definition that could act as a hint for schema.
This option will accept an array or thunk of types (similar to fields
and interfaces
options on ObjectType
).
This mechanism will have advantages over current types
option on Schema
only when type definitions are initialized on demand (vs collected during schema creation as it is done now).
See #38 for more info
Currently we have ResolveInfo::getFieldSelection()
to do simple look-aheads. But it is too limited for real-world scenarios and provides little help when resolving field of union or interface type. Better tool has to address more use-cases. Basically it should provide convenient interface to answer following questions:
Current getFieldSelection
only answers question 1.
The syntax for this could be something like this:
function resolve($value, $args, $context, ResolveInfo $info) {
$queryPlan = $info->lookAhead();
// Answers #1:
$fields = $queryPlan->subFields();
$deepFieldRequested = $queryPlan->has(['nested', 'deeper', 'evenDeeper']);
// Answers #2:
$types = $queryPlan->referencedTypes();
// Answers #3:
$typeSpecificFields = $queryPlan->subFields('TypeReferencedByFragment');
}
This needs further design with regards to nested syntax + things may get quite complicated for type-dependent deep fields.
Are you planning on integrating any of the graphql relay helpers into this library? https://github.com/graphql/graphql-relay-js
With GraphQL it is quite easy to create a query that crashes the server. Especially with graphs that have some kind of recursive structure.
How do you guys protect against that?
I like how they did it in the Scala project:
http://sangria-graphql.org/learn/#protection-against-malicious-queries
Example of how schema initialization might look like (see #36 for discussion on this subject):
$schema = new GraphQL\Schema();
$schema->setQueryType( $objectType)
->setMutationType($mutationType)
/* Other possible setters:
->setDirectives($directives)
->setTypes($types)
*/
Hi,
Say I have a query:
query GracefulFailure {
user {
nullableFieldWithFailure
name
}
}
{
data: {
user: {
nullableFieldWithFailure: null
name: 'Andreas'
}
},
error: [
{
message: "Some error message provided by me",
locations: [
{
column: 3,
line: 2
}
]
}
]
}
It is my current understanding that the resolvers only get an array representation of the context. The context is therefore passed by value and adding an error to it will be pointless.
It seems like the suggested way of adding errors to a response is by throwing an exception in the resolver. The problem with this approach is that it would stop the query execution.
Is there currently a way to add a custom error from a resolver without stopping query execution?
Related issue: #29
Context is supposed to be something you can pass down to any resolver, and modify from any point, but I wasn't able to make this work without changing the line in https://github.com/webonyx/graphql-php/blob/master/src/Executor/Executor.php#L460 to
return $resolveFn($source, $args, $context, $info);
Without this change, defining the resolve function as function ($parent, $args, &$context, $ast) {}
is an error because of some bug/weird behaviour with call_user_func
EDIT: I've made the change in my fork over at https://github.com/JohnyDays/graphql-php/tree/directlyCallResolver, should I open a pull request, or am I missing something? Cheers!
EDIT2: I actually had to change a few more lines in the end to make it work, assigning context by reference.
I have been looking around at the source code and haven't come across a way to do this yet, so I figured I would reach out. I am trying to make multiple requests such as this:
{
users {
id,
name
},
posts {
id,
title
}
}
Now what I want to do is cause an error on just one of those queries and allow the other to continue. So ideally it would be nice if we could find out where the error was that way we could key the errors
array like this:
array(
'errors' => array(
'users' => array(
)
)
)
Is there something I am missing here or is this really not possible to see which query threw the error right now?
When calling ResolveInfo::foldSelectionSet
recursively, --$descend
should be replaced with $descend - 1
otherwise in the next iteration step (in case we are in a loop) the results will be wrong.
Looks like if argument is not set explicitly - it is not passed to "resolve" function even if it's "defaultValue" is set.
As the title suggests, any ideas here?
Im almost at a loss at where to start, im using the laravel wrapper package, but neither this or the laravel project reference connection types, or how you would go about creating them?
Im guessing i could do it through the type system, but wanted to check if ive missed some docs somehwere that could help me out.
Allow following definition form (in addition to existing):
'fields' => [
'id' => 'Id!',
'author' => 'User',
'mentions' => '[Mention!]',
'totalCommentCount' => 'Int',
'comments' => [
'type' =>'[Comment!]',
'args' => [
'after' => [
'type' => 'Id',
'description' => 'Load all comments listed after given comment ID'
],
'limit' => [
'type' => 'Int',
'defaultValue' => 5
]
]
]
];
It opens up path to schema serialization and constant arrays for schema definitions, which may be useful for future optimizations. Also it opens up path to defining schema using GraphQL schema language.
Obviously actual loading of type by name has to be delegated to user-land code (will likely add new option to schema constructor for type resolver).
Should be BC.
Opening this per https://github.com/jayS-de/graphql-ast request for public discussion
Sometimes the fields do not get added to an InputObjectType when they are defined as a callback.
Specific example:
https://github.com/ivome/graphql-relay-php/blob/master/src/Mutation/Mutation.php#L47
If we change that to a function, the test suite fails because the fields do not get added. For the normal ObjectType this seems to work as you can see a few lines after that.
There were several discussions about ways to solve N+1 problem in GraphQL - #42, #60.
There are two classes of solutions for this problem:
Deferring syntax:
'resolve' => function($obj) {
$this->dataLoader->buffer($obj->someId);
return new GraphQL\Deferred(function() use ($obj) {
// This will internally load all buffered ids (once) and return the one requested here:
return $this->dataLoader->fetch($obj->someId);
});
}
Other possible syntax:
'buffer' => function($obj) {
$this->dataLoader->buffer($obj->someId);
},
'resolve' => function($obj) {
return $this->dataLoader->fetch($obj->someId);
}
Technically it will require Executor
algorith change from Depth-First to mix of Breath-First and Depth-First (requires researching and prototyping).
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.