studio-net / laravel-graphql Goto Github PK
View Code? Open in Web Editor NEWGraphQL implementation with power of Laravel
Home Page: https://github.com/studio-net/laravel-graphql/
License: MIT License
GraphQL implementation with power of Laravel
Home Page: https://github.com/studio-net/laravel-graphql/
License: MIT License
Can we get 5.7 support :)?
It would be nice, if using of pipes in definition would be documented. I also asked you, if it is possible to use pipes for authentication or other type of permission check is possible. You replied with "no", but actually it is possible. By the way, HTTP Middlewares in laravel, like auth middleware, works just the same.
I thing I got the idea of pipes, it is really nice! I also noticed, that it is generally possible to define pipe in following format: Class\Name\Of\Pipe:argument,another argument
. So pipline can parse this string format, and get the class of pipe for creating it from container, and passes to the handle()
method some addition parameters, in the example above: argument
and another argument
as 4th and 5th argument. It works well with pipeline code, but you also use pipes for gql fields generation... and this part of code dont accept this string format. May we fix it? As I would like to add a pipe for checking user role.
I'll do a merge request, so you can see what I mean... When we merge fix-bug, I can also write a small doc for pipe usage
Currently I am working a lot with morphed many2many relationships. They gets stored and linked perfectly, but if I want to remove related object, I have to call separately delete mutation for those related objects... For one - it is ok, but when I got many, it gets a little cumbersome.
So here is an Idea - may be can we add an additional field for those relation inputs, like _delete: boolean
, so that unlinked objects will be directly removed?
For exampe: I have an user with multiple locations (e.g. with IDs 3 and 4). Then I would like to store a user with locations like this:
mutation {
user(id: 1, with: {
locations: [
{id: 3, _delete: true}, {id: 4, _delete: true}
]}
) {
id
}
}
So on execution locations with IDs 3 and 4 will be also deleted.
If it is too strange, then may be can we add ability to remove multiple items with one mutation call on dropTransformer?
When trying to run a query like query={users(filter:{id:"1"}){id,name}}
it yields the error in MySql:
Column not found: 1054 Unknown column 'LOWER(id)' in 'where clause' (SQL: select * from `users` where `LOWER(id)` = 1)"
shouldn't this be LOWER(`id`)
?
Thanks for making this great package. It makes GraphQL even easier! However, I seem to have ran in to a problem. I was hoping I could make a filter function like this in my AlertDefinition
:
/**
* @return array
*/
public function getFilterable()
{
return [
'active' => function (Builder $builder, $value) {
if (!is_bool($value)) {
throw new InvalidArgumentException('Active filter must be of type boolean');
}
return $builder->whereActive($value);
},
];
}
And in my EndpointDefinition
I would call the relation like this:
/**
* Which fields are queryable ?
*
* @return array
*/
public function getFetchable()
{
return [
'id' => Type::id(),
'alerts' => Type::listOf(\GraphQL::type('alert')),
];
}
Now unfortunately, this does not work. The filter will be applied to the root alert query, but it will not allow me to filter like this:
query {
endpoints {
id,
name,
alerts (filter: {active: true}) {
id
created_at
}
}
}
Is there anyway to achieve this "automagically" instead of having to use a custom resolver? Because that would mean that if I would like to apply the same filter on the root alert query, I would either have to write the logic twice, or do some class extending / traits magic which seems rather cumbersome.
I suppose it would be ideal if something like this would be possible:
/**
* Which fields are queryable ?
*
* @return array
*/
public function getFetchable()
{
return [
'id' => Type::id(),
'alerts' => AlertDefinition::class,
];
}
(How) can something like this be achieved?
Moved as pull request #2
I currently have sometimes problems, if I throw Error in my code. I implemented a custom scalar type for my schema: ObfId. I use it to obfuscate ID in graphql (on result and on input). I have some checks on parsing a value, e.g. in parseLiteral
method, that parse value of this type in graphql-query:
use GraphQL\Error\Error;
use GraphQL\Language\AST\IntValueNode;
use Jenssegers\Optimus\Optimus;
...
function ($valueNode) {
if (!$valueNode instanceof IntValueNode) {
throw new Error('Query error: Can only parse integer got: '.$valueNode->kind, [$valueNode]);
}
/** @var Optimus $optimus */
$optimus = resolve(Optimus::class);
return $optimus->decode($valueNode->value);
}
So I check, if given value is an Integer, and if not, I want to throw an error. So I expect to get from server something like
{
"errors": [
{
"message": "Query error: Can only parse integer got: StringValue",
"category": "graphql",
"locations": [
{
"line": 2,
"column": 11
}
]
}
]
}
But I get following (I shortened it):
<!DOCTYPE html><!--
Symfony\Component\Debug\Exception\FatalThrowableError: Argument 1 passed to StudioNet\GraphQL\GraphQL::formatGraphQLException() must implement interface Throwable, null given, called in /var/www/my-headhunter/server/vendor/studio-net/laravel-graphql/src/GraphQL.php on line 140 in file /var/www/my-headhunter/server/vendor/studio-net/laravel-graphql/src/GraphQL.php on line 152
Stack trace:
1. Symfony\Component\Debug\Exception\FatalThrowableError->() /var/www/my-headhunter/server/vendor/studio-net/laravel-graphql/src/GraphQL.php:152
2. StudioNet\GraphQL\GraphQL->formatGraphQLException() /var/www/my-headhunter/server/vendor/studio-net/laravel-graphql/src/GraphQL.php:140
3. StudioNet\GraphQL\GraphQL->StudioNet\GraphQL\{closure}() [internal]:0
4. array_map() /var/www/my-headhunter/server/vendor/webonyx/graphql-php/src/Executor/ExecutionResult.php:130
5. GraphQL\Executor\ExecutionResult->GraphQL\Executor\{closure}() /var/www/my-headhunter/server/vendor/webonyx/graphql-php/src/Executor/ExecutionResult.php:134
6. GraphQL\Executor\ExecutionResult->toArray() /var/www/my-headhunter/server/vendor/studio-net/laravel-graphql/src/GraphQL.php:142
7. StudioNet\GraphQL\GraphQL->execute() /var/www/my-headhunter/server/vendor/studio-net/laravel-graphql/src/GraphQLController.php:96
8. StudioNet\GraphQL\GraphQLController->executeQuery() /var/www/my-headhunter/server/vendor/studio-net/laravel-graphql/src/GraphQLController.php:48
9. StudioNet\GraphQL\GraphQLController->query() /var/www/my-headhunter/server/vendor/laravel/framework/src/Illuminate/Routing/Controller.php:54
10. call_user_func_array() /var/www/my-headhunter/server/vendor/laravel/framework/src/Illuminate/Routing/Controller.php:54
11. Illuminate\Routing\Controller->callAction() /var/www/my-headhunter/server/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php:45
12. Illuminate\Routing\ControllerDispatcher->dispatch() /var/www/my-headhunter/server/vendor/laravel/framework/src/Illuminate/Routing/Route.php:212
13. Illuminate\Routing\Route->runController() /var/www/my-headhunter/server/vendor/laravel/framework/src/Illuminate/Routing/Route.php:169
14. Illuminate\Routing\Route->run() /var/www/my-headhunter/server/vendor/laravel/framework/src/Illuminate/Routing/Router.php:665
15. Illuminate\Routing\Router->Illuminate\Routing\{closure}() /var/www/my-headhunter/server/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php:30
16. Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}() /var/www/my-headhunter/server/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:104
17. Illuminate\Pipeline\Pipeline->then() /var/www/my-headhunter/server/vendor/laravel/framework/src/Illuminate/Routing/Router.php:667
18. Illuminate\Routing\Router->runRouteWithinStack() /var/www/my-headhunter/server/vendor/laravel/framework/src/Illuminate/Routing/Router.php:642
19. Illuminate\Routing\Router->runRoute() /var/www/my-headhunter/server/vendor/laravel/framework/src/Illuminate/Routing/Router.php:608
20. Illuminate\Routing\Router->dispatchToRoute() /var/www/my-headhunter/server/vendor/laravel/framework/src/Illuminate/Routing/Router.php:597
21. Illuminate\Routing\Router->dispatch() /var/www/my-headhunter/server/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php:176
22. Illuminate\Foundation\Http\Kernel->Illuminate\Foundation\Http\{closure}() /var/www/my-headhunter/server/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php:30
23. Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}() /var/www/my-headhunter/server/vendor/fideloper/proxy/src/TrustProxies.php:57
24. Fideloper\Proxy\TrustProxies->handle() /var/www/my-headhunter/server/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:151
25. Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}() /var/www/my-headhunter/server/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php:53
26. Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}() /var/www/my-headhunter/server/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php:31
27. Illuminate\Foundation\Http\Middleware\TransformsRequest->handle() /var/www/my-headhunter/server/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:151
28. Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}() /var/www/my-headhunter/server/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php:53
29. Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}() /var/www/my-headhunter/server/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php:31
30. Illuminate\Foundation\Http\Middleware\TransformsRequest->handle() /var/www/my-headhunter/server/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:151
31. Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}() /var/www/my-headhunter/server/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php:53
32. Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}() /var/www/my-headhunter/server/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ValidatePostSize.php:27
33. Illuminate\Foundation\Http\Middleware\ValidatePostSize->handle() /var/www/my-headhunter/server/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:151
34. Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}() /var/www/my-headhunter/server/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php:53
35. Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}() /var/www/my-headhunter/server/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/CheckForMaintenanceMode.php:62
36. Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode->handle() /var/www/my-headhunter/server/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:151
37. Illuminate\Pipeline\Pipeline->Illuminate\Pipeline\{closure}() /var/www/my-headhunter/server/vendor/laravel/framework/src/Illuminate/Routing/Pipeline.php:53
38. Illuminate\Routing\Pipeline->Illuminate\Routing\{closure}() /var/www/my-headhunter/server/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php:104
39. Illuminate\Pipeline\Pipeline->then() /var/www/my-headhunter/server/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php:151
40. Illuminate\Foundation\Http\Kernel->sendRequestThroughRouter() /var/www/my-headhunter/server/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php:116
41. Illuminate\Foundation\Http\Kernel->handle() /var/www/my-headhunter/server/public/index.php:55
...
So I dived into StudioNet\GraphQL\GraphQL::formatGraphQLException()
- on exception, it tries to get previous exception, which is not defined, as I threw a simple exception, without to define what previous exception was, as there is currently no previous exception...
I guess there is a point, why $error->getPrevious()
is used, but can we extend it with a check? Like check, if previous exception exists, if yes, format previous, if not format current one.
Or am I throwing wrong type of Error?
There is a bug when a nested request is sent and a belongsTo property is set to null.
mutation ($propertyId: ID!, $property: PropertyInput!) {
updateProperty: property(id: $propertyId, with: $property) {
id,
owner {
id
}
}
}
With values
{
"propertyId": "1",
"property": {
"owner": null
}
}
doesn't work,
Undefined column: 7 ERREUR: la colonne « owner » de la relation « properties » n'existe pas
But it works fine when filled with data
StudioNet\GraphQL\Generator\Query\NodesEloquentGenerator::getFilterType()
method is using InputObjectType
type and each field within has a String
type, that prevent us to do following :
entities (filter : {"reference": {"or" : ["20%", "30%", "(gt) 5000"]}}) {
# ...
}
The type should be our scalar Array
type. I prefer using array each time instead of string.
Hi. I decided to discuss it with you... I wish a possibility simply to define, if some queries are protected. As I used rebing/graphql-laravel before, I am very inspired how they solved it there.
As first - there are multiple possibilities, how it could be used (all situations are already implemented in rebing lib)
This seems to be the simplest solution. We can define multiple schemas. As option, it would be nice to define middlewares for schemas in config. Maybe something like this:
[...
'schema' => [
'default' => 'default',
'definitions' => [
'default' => [
'query' => [...],
'mutation' => [...],
'middleware' => ['auth']
]
]
],
]
OR: as we speak about authorization, just define 'authorization' => true
It looks simple though, I don't think it is very useful. In my last application I was using apollo-client with some extra plugins. We decided to use single schema, but if user is logged in - then we simple added authorisation header. If user is allowed to query particular data was decided on server, depending on authorisation header. Anyway I think it is quite uncomfortable to use multiple schemas on client side, and each time to decide, which should be used... So in my opinion this option make simply no sense...
May be we could implement something like authorize()
Method in query/mutation, that should return a boolean. If false
is returned, then GraphQL throws an error (like 'Unauthorized'). Usage could then look like this (same as in rebing lib)
use Auth;
class UsersQuery extends Query
{
public function authorize(array $args)
{
// true, if logged in
return !Auth::guest();
}
...
}
This is not as much as middleware, but as idea - hide fields, that user may not access. This is called privacy in rebing lib (see here). This seems to be very helpful, but I don't know, if it is so in practice.
What do you think? Should we implement something of this?
When some of my code throws an \Error
or \Exception
, the error I get back includes a "location", but no indication of where the error occurred — no file, or class name, or trace.
Is this by design? Could we do something like this in formatError()
?
if (!empty($prev)) {
if ($prev instanceof ValidationError) {
$error['validation'] = $prev->getValidatorMessages()->toArray();
} else if ($prev instanceof \Throwable) {
// Throw in the trace so one can know where an exception occurred
$error['trace'] = explode("\n", $prev->getTraceAsString());
}
}
That would give us a nicely formatted trace back to where the original \Throwable
was thrown, so the error is findable (though it should probably be switched based on env(APP_DEBUG)
)
Hi there! Thank you very much for this awesome package! I am new to GraphQL and tried before a package from rebing, but you package seems to be more fresh and your features are simple crazy awesome!
When I tried your library, it was important to me, that all relationships loads eager. As you provide transformers, I didn't even need to write a query for testing this out - nice! BUT exactly there I found a bug.
I would like to fix it, but I don't feel much confidence with your code, as I see at it since 1 hour. So I just describe the problem ;)
In file StudioNet\GraphQL\Support\Transformer\Transformer.php
in method getResolverCallable()
you tries to guess the relations with $this->guessWithRelations($definition, $fields)
. For ViewTransformation (and all other mutation-transformations) it works perfectly. Relations are guessed correctly, as fields at the root contains exactly the structure of queried type.
But to query data via ListTransformer, we have to use query like this:
{
items {...}
}
So in getResolverCallable()
the variable $fields contains that, what was given (same as in example).
When you then call $this->guessWithRelations($definition, $fields)
, variable $fields
contains wrong attributes, better saying it contains correct attributes, but inside items{...}
. So the guessWithRelations()
guesses, there are no relations at all, because it checks only items
field.
I know, you have a TODO there (Improve this checker). If you wish, I could try to improve it.
P.s. Child-relations are also not guessed.
Hi, I just installed a plugin on my PhpStorm: JS GraphQL. It has a great support for working with graphql. When I parsed my schema with this plugin, it threw me an error:
Error: Name "__typename" must not begin with "__", which is reserved by GraphQL introspection.
So my question - Is there any sense in appending __typename
field of type "string" to all input types for definitions? I didn't see this in other gql schemas before.
Can we get rid of it? I also didn't find any example for creation Input types with __typename
field.
On defining mutable fields, it is currently possible to simply define which relation should be updated. Input type of this relation is automatically used, as input type of the defined field.
But what I faced today - for the relations transformation there is pretty simple code, that is not using something from definitions of this relation at all.
What I was trying: Let's say we have User and Location models. User can have many Locations. Location has a custom input "shape", which could be either a polygon, or simple point with lat/long and radius in meters, for creation circle on the map. On the server side this field is always converted so, that DB stores only polygons.
If I create Location directly, my custom input works properly. But when I am using nested mutation, it doesn't work, because relations transformation is processed on its own, and has nothing in common with definition.
So it would be really nice, when on defining custom input in definition, this custom input would be respected also on nested mutations (relations).
I did a quick look on code, and didn't find any quick solution. So what du you think? As idea - good/bad? And if its good, how would you like to implement it? May be if you could give me some advices, I could try to code it on my own
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.