├── .coveralls.yml
├── .gitignore
├── .travis.yml
├── Readme.md
├── composer.json
├── docs
├── advanced.md
└── upgrade.md
├── phpcs.xml
├── phpunit.xml
├── release.sh
├── src
├── Folklore
│ └── GraphQL
│ │ ├── Console
│ │ ├── EnumMakeCommand.php
│ │ ├── FieldMakeCommand.php
│ │ ├── InterfaceMakeCommand.php
│ │ ├── MutationMakeCommand.php
│ │ ├── PublishCommand.php
│ │ ├── QueryMakeCommand.php
│ │ ├── ScalarMakeCommand.php
│ │ ├── TypeMakeCommand.php
│ │ └── stubs
│ │ │ ├── enum.stub
│ │ │ ├── field.stub
│ │ │ ├── interface.stub
│ │ │ ├── mutation.stub
│ │ │ ├── query.stub
│ │ │ ├── scalar.stub
│ │ │ └── type.stub
│ │ ├── Controller.php
│ │ ├── Error
│ │ ├── AuthorizationError.php
│ │ └── ValidationError.php
│ │ ├── Events
│ │ ├── SchemaAdded.php
│ │ └── TypeAdded.php
│ │ ├── Exception
│ │ ├── SchemaNotFound.php
│ │ └── TypeNotFound.php
│ │ ├── GraphQL.php
│ │ ├── GraphQLController.php
│ │ ├── LumenServiceProvider.php
│ │ ├── ServiceProvider.php
│ │ ├── Support
│ │ ├── Contracts
│ │ │ └── TypeConvertible.php
│ │ ├── EnumType.php
│ │ ├── Facades
│ │ │ └── GraphQL.php
│ │ ├── Field.php
│ │ ├── InputType.php
│ │ ├── InterfaceType.php
│ │ ├── Mutation.php
│ │ ├── PaginationCursorType.php
│ │ ├── PaginationType.php
│ │ ├── Query.php
│ │ ├── Traits
│ │ │ └── ShouldValidate.php
│ │ ├── Type.php
│ │ └── UnionType.php
│ │ ├── View
│ │ └── GraphiQLComposer.php
│ │ └── routes.php
├── config
│ └── config.php
└── resources
│ └── views
│ └── graphiql.php
└── tests
├── ConfigTest.php
├── EndpointTest.php
├── EnumTypeTest.php
├── FieldTest.php
├── GraphQLQueryTest.php
├── GraphQLTest.php
├── GraphiQLTest.php
├── InputTypeTest.php
├── InterfaceTypeTest.php
├── MutationTest.php
├── Objects
├── CustomExampleType.php
├── ErrorFormatter.php
├── ExampleEnumType.php
├── ExampleField.php
├── ExampleInputType.php
├── ExampleInterfaceType.php
├── ExampleNestedValidationInputObject.php
├── ExampleType.php
├── ExampleUnionType.php
├── ExampleValidationField.php
├── ExampleValidationInputObject.php
├── ExamplesAuthenticatedQuery.php
├── ExamplesAuthorizeQuery.php
├── ExamplesContextQuery.php
├── ExamplesPaginationQuery.php
├── ExamplesQuery.php
├── ExamplesRootQuery.php
├── UpdateExampleMutation.php
├── UpdateExampleMutationWithInputType.php
├── data.php
└── queries.php
├── PaginationTest.php
├── QueryTest.php
├── TestCase.php
├── TypeTest.php
├── UnionTypeTest.php
├── fixture
├── app
│ └── .gitignore
└── composer.json
└── logs
└── .gitignore
/.coveralls.yml:
--------------------------------------------------------------------------------
1 | service_name: travis-ci
2 | coverage_clover: coverage/clover.xml
3 | json_path: coverage/coveralls-upload.json
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | *.log
3 | composer.lock
4 | /*_backup/
5 | .idea/*
6 | /coverage
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | cache:
4 | directories:
5 | - $HOME/.cache/pip
6 | - $HOME/.composer/cache/files
7 | - ${TRAVIS_BUILD_DIR}/travis/extension-cache
8 |
9 | php:
10 | - 5.5
11 | - 5.6
12 | - 7.0
13 | - 7.1
14 |
15 | env:
16 | - ILLUMINATE_VERSION=5.1.* PHPUNIT_VERSION=~4.0
17 | - ILLUMINATE_VERSION=5.2.* PHPUNIT_VERSION=~4.0
18 | - ILLUMINATE_VERSION=5.3.* PHPUNIT_VERSION=~5.0
19 | - ILLUMINATE_VERSION=5.4.* PHPUNIT_VERSION=~5.7
20 | - ILLUMINATE_VERSION=5.5.* PHPUNIT_VERSION=~6.0
21 | - ILLUMINATE_VERSION=5.5.* PHPUNIT_VERSION=~6.0 COVERAGE=true
22 | - ILLUMINATE_VERSION=5.6.* PHPUNIT_VERSION=~7.0
23 |
24 | matrix:
25 | # For each PHP version we exclude the coverage env, except for PHP 7.1
26 | exclude:
27 | # Test only Laravel 5.1 and 5.2 on PHP 5.5
28 | - php: 5.5
29 | env: ILLUMINATE_VERSION=5.3.* PHPUNIT_VERSION=~5.0
30 | - php: 5.5
31 | env: ILLUMINATE_VERSION=5.4.* PHPUNIT_VERSION=~5.7
32 | - php: 5.5
33 | env: ILLUMINATE_VERSION=5.5.* PHPUNIT_VERSION=~6.0
34 | - php: 5.5
35 | env: ILLUMINATE_VERSION=5.5.* PHPUNIT_VERSION=~6.0 COVERAGE=true
36 | - php: 5.5
37 | env: ILLUMINATE_VERSION=5.6.* PHPUNIT_VERSION=~7.0
38 | # Don't test Laravel 5.5 on PHP 5.6
39 | - php: 5.6
40 | env: ILLUMINATE_VERSION=5.5.* PHPUNIT_VERSION=~6.0
41 | - php: 5.6
42 | env: ILLUMINATE_VERSION=5.5.* PHPUNIT_VERSION=~6.0 COVERAGE=true
43 | - php: 5.6
44 | env: ILLUMINATE_VERSION=5.6.* PHPUNIT_VERSION=~7.0
45 | # Test everything on PHP 7.0
46 | - php: 7.0
47 | env: ILLUMINATE_VERSION=5.5.* PHPUNIT_VERSION=~6.0 COVERAGE=true
48 | - php: 7.0
49 | env: ILLUMINATE_VERSION=5.6.* PHPUNIT_VERSION=~7.0
50 | # Test only Laravel 5.4 and 5.5 on PHP 7.1
51 | - php: 7.1
52 | env: ILLUMINATE_VERSION=5.1.* PHPUNIT_VERSION=~4.0
53 | - php: 7.1
54 | env: ILLUMINATE_VERSION=5.2.* PHPUNIT_VERSION=~4.0
55 | - php: 7.1
56 | env: ILLUMINATE_VERSION=5.3.* PHPUNIT_VERSION=~5.0
57 | - php: 7.1
58 | env: ILLUMINATE_VERSION=5.5.* PHPUNIT_VERSION=~6.0
59 |
60 | before_install:
61 | - cp ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini ~/xdebug.ini
62 | - phpenv config-rm xdebug.ini
63 | - composer global require hirak/prestissimo --update-no-dev
64 | - composer require "illuminate/support:${ILLUMINATE_VERSION}" --no-update --prefer-dist
65 | - composer require "orchestra/testbench:${ILLUMINATE_VERSION/5\./3\.}" --no-update --prefer-dist
66 | - composer require "phpunit/phpunit:${PHPUNIT_VERSION}" --no-update --prefer-dist
67 |
68 | install: travis_retry composer install --no-interaction --prefer-dist
69 |
70 | before_script: phpenv config-add ~/xdebug.ini
71 |
72 | script: vendor/bin/phpunit
73 |
74 | after_success: sh -c "if [ ! -z ${COVERAGE+x} ]; then php vendor/bin/coveralls; fi"
75 |
76 | notifications:
77 | email: false
78 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | # Laravel GraphQL
2 |
3 | ----
4 |
5 | ### This package is no longuer maintained. Please use [rebing/graphql-laravel](https://github.com/rebing/graphql-laravel) or [other Laravel GraphQL packages](https://github.com/search?q=laravel+graphql&type=Repositories)
6 |
7 | ----
8 |
9 | Use Facebook GraphQL with Laravel 5 or Lumen. It is based on the PHP implementation [here](https://github.com/webonyx/graphql-php). You can find more information about GraphQL in the [GraphQL Introduction](http://facebook.github.io/react/blog/2015/05/01/graphql-introduction.html) on the [React](http://facebook.github.io/react) blog or you can read the [GraphQL specifications](https://facebook.github.io/graphql/). This is a work in progress.
10 |
11 | This package is compatible with Eloquent model (or any other data source). See the example below.
12 |
13 | [](https://packagist.org/packages/folklore/graphql)
14 | [](https://travis-ci.org/Folkloreatelier/laravel-graphql)
15 | [](https://packagist.org/packages/folklore/graphql)
16 |
17 | ----
18 | ### To use laravel-graphql with Relay, check the [feature/relay](https://github.com/Folkloreatelier/laravel-graphql/tree/feature/relay) branch.
19 | ----
20 |
21 | ## Installation
22 |
23 | Version 1.0 is released. If you are upgrading from older version, you can check [Upgrade to 1.0](docs/upgrade.md).
24 |
25 | #### Dependencies:
26 |
27 | * [Laravel 5.x](https://github.com/laravel/laravel) or [Lumen](https://github.com/laravel/lumen)
28 | * [GraphQL PHP](https://github.com/webonyx/graphql-php)
29 |
30 |
31 | **1-** Require the package via Composer in your `composer.json`.
32 | ```json
33 | {
34 | "require": {
35 | "folklore/graphql": "~1.0.0"
36 | }
37 | }
38 | ```
39 |
40 | **2-** Run Composer to install or update the new requirement.
41 |
42 | ```bash
43 | $ composer install
44 | ```
45 |
46 | or
47 |
48 | ```bash
49 | $ composer update
50 | ```
51 |
52 | ### Laravel >= 5.5.x
53 |
54 | **1-** Publish the configuration file
55 |
56 | ```bash
57 | $ php artisan vendor:publish --provider="Folklore\GraphQL\ServiceProvider"
58 | ```
59 |
60 | **2-** Review the configuration file
61 |
62 | ```
63 | config/graphql.php
64 | ```
65 |
66 | ### Laravel <= 5.4.x
67 |
68 | **1-** Add the service provider to your `config/app.php` file
69 | ```php
70 | Folklore\GraphQL\ServiceProvider::class,
71 | ```
72 |
73 | **2-** Add the facade to your `config/app.php` file
74 | ```php
75 | 'GraphQL' => Folklore\GraphQL\Support\Facades\GraphQL::class,
76 | ```
77 |
78 | **3-** Publish the configuration file
79 |
80 | ```bash
81 | $ php artisan vendor:publish --provider="Folklore\GraphQL\ServiceProvider"
82 | ```
83 |
84 | **4-** Review the configuration file
85 |
86 | ```
87 | config/graphql.php
88 | ```
89 |
90 | ### Lumen
91 |
92 | **1-** Load the service provider in `bootstrap/app.php`
93 | ```php
94 | $app->register(Folklore\GraphQL\LumenServiceProvider::class);
95 | ```
96 |
97 | **2-** For using the facade you have to uncomment the line `$app->withFacades();` in `bootstrap/app.php`
98 |
99 | After uncommenting this line you have the `GraphQL` facade enabled
100 |
101 | ```php
102 | $app->withFacades();
103 | ```
104 |
105 | **3-** Publish the configuration file
106 |
107 | ```bash
108 | $ php artisan graphql:publish
109 | ```
110 |
111 | **4-** Load configuration file in `bootstrap/app.php`
112 |
113 | *Important*: this command needs to be executed before the registration of the service provider
114 |
115 | ```php
116 | $app->configure('graphql');
117 | ...
118 | $app->register(Folklore\GraphQL\LumenServiceProvider::class)
119 | ```
120 |
121 | **5-** Review the configuration file
122 |
123 | ```
124 | config/graphql.php
125 | ```
126 |
127 | ## Documentation
128 |
129 | - [Upgrade to 1.0](docs/upgrade.md)
130 |
131 | ## Usage
132 |
133 | - [Schemas](#schemas)
134 | - [Creating a query](#creating-a-query)
135 | - [Creating a mutation](#creating-a-mutation)
136 | - [Adding validation to mutation](#adding-validation-to-mutation)
137 |
138 | #### Advanced Usage
139 | - [Query variables](docs/advanced.md#query-variables)
140 | - [Query nested resource](docs/advanced.md#query-nested-resource)
141 | - [Enums](docs/advanced.md#enums)
142 | - [Interfaces](docs/advanced.md#interfaces)
143 | - [Custom field](docs/advanced.md#custom-field)
144 | - [Eager loading relationships](docs/advanced.md#eager-loading-relationships)
145 |
146 | ### Schemas
147 | Starting from version 1.0, you can define multiple schemas. Having multiple schemas can be useful if, for example, you want an endpoint that is public and another one that needs authentication.
148 |
149 | You can define multiple schemas in the config:
150 |
151 | ```php
152 | 'schema' => 'default',
153 |
154 | 'schemas' => [
155 | 'default' => [
156 | 'query' => [
157 | //'users' => 'App\GraphQL\Query\UsersQuery'
158 | ],
159 | 'mutation' => [
160 | //'updateUserEmail' => 'App\GraphQL\Query\UpdateUserEmailMutation'
161 | ]
162 | ],
163 | 'secret' => [
164 | 'query' => [
165 | //'users' => 'App\GraphQL\Query\UsersQuery'
166 | ],
167 | 'mutation' => [
168 | //'updateUserEmail' => 'App\GraphQL\Query\UpdateUserEmailMutation'
169 | ]
170 | ]
171 | ]
172 | ```
173 |
174 | Or you can add schema using the facade:
175 |
176 | ```php
177 | GraphQL::addSchema('secret', [
178 | 'query' => [
179 | 'users' => 'App\GraphQL\Query\UsersQuery'
180 | ],
181 | 'mutation' => [
182 | 'updateUserEmail' => 'App\GraphQL\Query\UpdateUserEmailMutation'
183 | ]
184 | ]);
185 | ```
186 |
187 | Afterwards, you can build the schema using the facade:
188 |
189 | ```php
190 | // Will return the default schema defined by 'schema' in the config
191 | $schema = GraphQL::schema();
192 |
193 | // Will return the 'secret' schema
194 | $schema = GraphQL::schema('secret');
195 |
196 | // Will build a new schema
197 | $schema = GraphQL::schema([
198 | 'query' => [
199 | //'users' => 'App\GraphQL\Query\UsersQuery'
200 | ],
201 | 'mutation' => [
202 | //'updateUserEmail' => 'App\GraphQL\Query\UpdateUserEmailMutation'
203 | ]
204 | ]);
205 | ```
206 |
207 | Or you can request the endpoint for a specific schema
208 |
209 | ```
210 | // Default schema
211 | http://homestead.app/graphql?query=query+FetchUsers{users{id,email}}
212 |
213 | // Secret schema
214 | http://homestead.app/graphql/secret?query=query+FetchUsers{users{id,email}}
215 | ```
216 |
217 | ### Creating a query
218 |
219 | First you need to create a type.
220 |
221 | ```php
222 | namespace App\GraphQL\Type;
223 |
224 | use GraphQL\Type\Definition\Type;
225 | use Folklore\GraphQL\Support\Type as GraphQLType;
226 |
227 | class UserType extends GraphQLType
228 | {
229 | protected $attributes = [
230 | 'name' => 'User',
231 | 'description' => 'A user'
232 | ];
233 |
234 | /*
235 | * Uncomment following line to make the type input object.
236 | * http://graphql.org/learn/schema/#input-types
237 | */
238 | // protected $inputObject = true;
239 |
240 | public function fields()
241 | {
242 | return [
243 | 'id' => [
244 | 'type' => Type::nonNull(Type::string()),
245 | 'description' => 'The id of the user'
246 | ],
247 | 'email' => [
248 | 'type' => Type::string(),
249 | 'description' => 'The email of user'
250 | ]
251 | ];
252 | }
253 |
254 | // If you want to resolve the field yourself, you can declare a method
255 | // with the following format resolve[FIELD_NAME]Field()
256 | protected function resolveEmailField($root, $args)
257 | {
258 | return strtolower($root->email);
259 | }
260 | }
261 | ```
262 |
263 | Add the type to the `config/graphql.php` configuration file
264 |
265 | ```php
266 | 'types' => [
267 | 'User' => 'App\GraphQL\Type\UserType'
268 | ]
269 | ```
270 |
271 | You could also add the type with the `GraphQL` Facade, in a service provider for example.
272 |
273 | ```php
274 | GraphQL::addType('App\GraphQL\Type\UserType', 'User');
275 | ```
276 |
277 | Then you need to define a query that returns this type (or a list). You can also specify arguments that you can use in the resolve method.
278 | ```php
279 | namespace App\GraphQL\Query;
280 |
281 | use GraphQL;
282 | use GraphQL\Type\Definition\Type;
283 | use Folklore\GraphQL\Support\Query;
284 | use App\User;
285 |
286 | class UsersQuery extends Query
287 | {
288 | protected $attributes = [
289 | 'name' => 'users'
290 | ];
291 |
292 | public function type()
293 | {
294 | return Type::listOf(GraphQL::type('User'));
295 | }
296 |
297 | public function args()
298 | {
299 | return [
300 | 'id' => ['name' => 'id', 'type' => Type::string()],
301 | 'email' => ['name' => 'email', 'type' => Type::string()]
302 | ];
303 | }
304 |
305 | public function resolve($root, $args)
306 | {
307 | if (isset($args['id'])) {
308 | return User::where('id' , $args['id'])->get();
309 | } else if(isset($args['email'])) {
310 | return User::where('email', $args['email'])->get();
311 | } else {
312 | return User::all();
313 | }
314 | }
315 | }
316 | ```
317 |
318 | Add the query to the `config/graphql.php` configuration file
319 |
320 | ```php
321 | 'schemas' => [
322 | 'default' => [
323 | 'query' => [
324 | 'users' => 'App\GraphQL\Query\UsersQuery'
325 | ],
326 | // ...
327 | ]
328 | ]
329 | ```
330 |
331 | And that's it. You should be able to query GraphQL with a request to the url `/graphql` (or anything you choose in your config). Try a GET request with the following `query` input
332 |
333 | ```
334 | query FetchUsers {
335 | users {
336 | id
337 | email
338 | }
339 | }
340 | ```
341 |
342 | For example, if you use homestead:
343 | ```
344 | http://homestead.app/graphql?query=query+FetchUsers{users{id,email}}
345 | ```
346 |
347 | ### Creating a mutation
348 |
349 | A mutation is like any other query, it accepts arguments (which will be used to do the mutation) and return an object of a certain type.
350 |
351 | For example a mutation to update the password of a user. First you need to define the Mutation.
352 |
353 | ```php
354 | namespace App\GraphQL\Mutation;
355 |
356 | use GraphQL;
357 | use GraphQL\Type\Definition\Type;
358 | use Folklore\GraphQL\Support\Mutation;
359 | use App\User;
360 |
361 | class UpdateUserPasswordMutation extends Mutation
362 | {
363 | protected $attributes = [
364 | 'name' => 'updateUserPassword'
365 | ];
366 |
367 | public function type()
368 | {
369 | return GraphQL::type('User');
370 | }
371 |
372 | public function args()
373 | {
374 | return [
375 | 'id' => ['name' => 'id', 'type' => Type::nonNull(Type::string())],
376 | 'password' => ['name' => 'password', 'type' => Type::nonNull(Type::string())]
377 | ];
378 | }
379 |
380 | public function resolve($root, $args)
381 | {
382 | $user = User::find($args['id']);
383 |
384 | if (!$user) {
385 | return null;
386 | }
387 |
388 | $user->password = bcrypt($args['password']);
389 | $user->save();
390 |
391 | return $user;
392 | }
393 | }
394 | ```
395 |
396 | As you can see in the `resolve` method, you use the arguments to update your model and return it.
397 |
398 | You then add the mutation to the `config/graphql.php` configuration file
399 |
400 | ```php
401 | 'schema' => [
402 | 'default' => [
403 | 'mutation' => [
404 | 'updateUserPassword' => 'App\GraphQL\Mutation\UpdateUserPasswordMutation'
405 | ],
406 | // ...
407 | ]
408 | ]
409 | ```
410 |
411 | You should then be able to use the following query on your endpoint to do the mutation.
412 |
413 | ```
414 | mutation users {
415 | updateUserPassword(id: "1", password: "newpassword") {
416 | id
417 | email
418 | }
419 | }
420 | ```
421 |
422 | if you use homestead:
423 | ```
424 | http://homestead.app/graphql?query=mutation+users{updateUserPassword(id: "1", password: "newpassword"){id,email}}
425 | ```
426 |
427 | #### Adding validation to mutation
428 |
429 | It is possible to add validation rules to mutation. It uses the laravel `Validator` to performs validation against the `args`.
430 |
431 | When creating a mutation, you can add a method to define the validation rules that apply by doing the following:
432 |
433 | ```php
434 | namespace App\GraphQL\Mutation;
435 |
436 | use GraphQL;
437 | use GraphQL\Type\Definition\Type;
438 | use Folklore\GraphQL\Support\Mutation;
439 | use App\User;
440 |
441 | class UpdateUserEmailMutation extends Mutation
442 | {
443 | protected $attributes = [
444 | 'name' => 'UpdateUserEmail'
445 | ];
446 |
447 | public function type()
448 | {
449 | return GraphQL::type('User');
450 | }
451 |
452 | public function args()
453 | {
454 | return [
455 | 'id' => ['name' => 'id', 'type' => Type::string()],
456 | 'email' => ['name' => 'email', 'type' => Type::string()]
457 | ];
458 | }
459 |
460 | public function rules()
461 | {
462 | return [
463 | 'id' => ['required'],
464 | 'email' => ['required', 'email']
465 | ];
466 | }
467 |
468 | public function resolve($root, $args)
469 | {
470 | $user = User::find($args['id']);
471 |
472 | if (!$user) {
473 | return null;
474 | }
475 |
476 | $user->email = $args['email'];
477 | $user->save();
478 |
479 | return $user;
480 | }
481 | }
482 | ```
483 |
484 | Alternatively you can define rules with each args
485 |
486 | ```php
487 | class UpdateUserEmailMutation extends Mutation
488 | {
489 | //...
490 |
491 | public function args()
492 | {
493 | return [
494 | 'id' => [
495 | 'name' => 'id',
496 | 'type' => Type::string(),
497 | 'rules' => ['required']
498 | ],
499 | 'email' => [
500 | 'name' => 'email',
501 | 'type' => Type::string(),
502 | 'rules' => ['required', 'email']
503 | ]
504 | ];
505 | }
506 |
507 | //...
508 | }
509 | ```
510 |
511 | When you execute a mutation, it will returns the validation errors. Since GraphQL specifications define a certain format for errors, the validation errors messages are added to the error object as a extra `validation` attribute. To find the validation error, you should check for the error with a `message` equals to `'validation'`, then the `validation` attribute will contain the normal errors messages returned by the Laravel Validator.
512 |
513 | ```json
514 | {
515 | "data": {
516 | "updateUserEmail": null
517 | },
518 | "errors": [
519 | {
520 | "message": "validation",
521 | "locations": [
522 | {
523 | "line": 1,
524 | "column": 20
525 | }
526 | ],
527 | "validation": {
528 | "email": [
529 | "The email is invalid."
530 | ]
531 | }
532 | }
533 | ]
534 | }
535 | ```
536 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "folklore/graphql",
3 | "description": "Facebook GraphQL for Laravel",
4 | "keywords": ["framework", "laravel", "graphql", "react"],
5 | "authors": [
6 | {
7 | "name": "Folklore",
8 | "email": "info@atelierfolklore.ca",
9 | "homepage": "http://atelierfolklore.ca"
10 | },
11 | {
12 | "name": "David Mongeau-Petitpas",
13 | "email": "dmp@atelierfolklore.ca",
14 | "homepage": "http://mongo.ca",
15 | "role": "Developer"
16 | }
17 | ],
18 | "license": "MIT",
19 | "type": "library",
20 | "require": {
21 | "php": ">=5.5.9",
22 | "illuminate/support": "5.1.*|5.2.*|5.3.*|5.4.*|5.5.*|5.6.*|5.7.*",
23 | "webonyx/graphql-php": "~0.10.2"
24 | },
25 | "require-dev": {
26 | "orchestra/testbench": "3.1.*|3.2.*|3.3.*|3.4.*|3.5.*",
27 | "fzaninotto/faker": "~1.4",
28 | "mockery/mockery": "0.9.*",
29 | "satooshi/php-coveralls": "^1.0",
30 | "phpunit/phpunit": "~4.0|~5.0|~5.7|~6.0"
31 | },
32 | "autoload": {
33 | "psr-0": {
34 | "Folklore\\GraphQL\\": "src/"
35 | }
36 | },
37 | "autoload-dev": {
38 | "classmap": [
39 | "tests/"
40 | ]
41 | },
42 | "suggest": {
43 | "rebing/graphql-laravel": "folklore/graphql is no longuer maintained. Please use rebing/graphql-laravel"
44 | },
45 | "extra": {
46 | "laravel": {
47 | "providers": [
48 | "Folklore\\GraphQL\\ServiceProvider"
49 | ],
50 | "aliases": {
51 | "GraphQL": "Folklore\\GraphQL\\Support\\Facades\\GraphQL"
52 | }
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/docs/advanced.md:
--------------------------------------------------------------------------------
1 | # Advanced Usage
2 |
3 | - [Query variables](#query-variables)
4 | - [Query nested resource](#query-nested-resource)
5 | - [Enums](#enums)
6 | - [Interfaces](#interfaces)
7 | - [Custom field](#custom-field)
8 | - [Eager loading relationships](#eager-loading-relationships)
9 |
10 | ### Query Variables
11 |
12 | GraphQL offer you the possibility to use variables in your query so you don't need to "hardcode" value. This is done like that:
13 |
14 | ```
15 | query FetchUserByID($id: String) {
16 | user(id: $id) {
17 | id
18 | email
19 | }
20 | }
21 | ```
22 |
23 | When you query the GraphQL endpoint, you can pass a `variables` parameter.
24 |
25 | ```
26 | http://homestead.app/graphql?query=query+FetchUserByID($id:String){user(id:$id){id,email}}&variables={"id":"1"}
27 | ```
28 |
29 | ### Query nested resource
30 |
31 | If you want to query nested resource like that :
32 |
33 | ```
34 | query FetchUser{
35 | user(id: 123456789) {
36 | id
37 | posts(id: 987654321) {
38 | id
39 | }
40 | }
41 | }
42 | ```
43 |
44 | you need to add post field and implement resolveField method in UserType:
45 |
46 | ```
47 | public function fields()
48 | {
49 | return [
50 | 'id' => [
51 | 'type' => Type::nonNull(Type::string()),
52 | 'description' => 'Id of user',
53 | ],
54 | 'posts' => [
55 | 'args' => [
56 | 'id' => [
57 | 'type' => Type::string(),
58 | 'description' => 'id of the post',
59 | ],
60 | ],
61 | 'type' => Type::listOf(GraphQL::type('Post')),
62 | 'description' => 'post description',
63 | ],
64 | ];
65 | }
66 |
67 | public function resolvePostsField($root, $args)
68 | {
69 | if (isset($args['id'])) {
70 | return $root->posts->where('id', $args['id']);
71 | }
72 |
73 | return $root->posts;
74 | }
75 | ```
76 |
77 | ### Enums
78 |
79 | Enumeration types are a special kind of scalar that is restricted to a particular set of allowed values.
80 | Read more about Enums [here](http://graphql.org/learn/schema/#enumeration-types)
81 |
82 | First create an Enum as an extention of the GraphQLType class:
83 | ```php
84 | 'Episode',
95 | 'description' => 'The types of demographic elements',
96 | 'values' => [
97 | 'NEWHOPE' => 'NEWHOPE',
98 | 'EMPIRE' => 'EMPIRE',
99 | 'JEDI' => 'JEDI',
100 | ],
101 | ];
102 | }
103 |
104 | ```
105 | Register the Enum in the 'types' array of the graphql.php config file:
106 |
107 | ```php
108 | // config/graphql.php
109 | 'types' => [TestEnum' => TestEnumType::class ];
110 | ```
111 |
112 | Then use it like:
113 | ```php
114 | [
121 | 'type' => GraphQL::type('TestEnum')
122 | ]
123 | ]
124 | }
125 | }
126 | ```
127 | ### Interfaces
128 |
129 | You can use interfaces to abstract a set of fields. Read more about interfaces [here](http://graphql.org/learn/schema/#interfaces).
130 |
131 | An implementation of an interface:
132 |
133 | ```php
134 | 'Character',
145 | 'description' => 'Character interface.',
146 | ];
147 |
148 | public function fields() {
149 | return [
150 | 'id' => [
151 | 'type' => Type::nonNull(Type::int()),
152 | 'description' => 'The id of the character.'
153 | ],
154 | 'appearsIn' => [
155 | 'type' => Type::nonNull(Type::listOf(GraphQL::type('Episode'))),
156 | 'description' => 'A list of episodes in which the character has an appearance.'
157 | ],
158 | ];
159 | }
160 |
161 | public function resolveType($root) {
162 | // Use the resolveType to resolve the Type which is implemented trough this interface
163 | $type = $root['type'];
164 | if ($type === 'human') {
165 | return GraphQL::type('Human');
166 | } else if ($type === 'droid') {
167 | return GraphQL::type('Droid');
168 | }
169 | }
170 | }
171 | ```
172 |
173 | A Type that implements an interface:
174 |
175 | ```php
176 | 'Human',
188 | 'description' => 'A human.'
189 | ];
190 |
191 | public function fields() {
192 | return [
193 | 'id' => [
194 | 'type' => Type::nonNull(Type::int()),
195 | 'description' => 'The id of the human.',
196 | ],
197 | 'appearsIn' => [
198 | 'type' => Type::nonNull(Type::listOf(GraphQL::type('Episode'))),
199 | 'description' => 'A list of episodes in which the human has an appearance.'
200 | ],
201 | 'totalCredits' => [
202 | 'type' => Type::nonNull(Type::int()),
203 | 'description' => 'The total amount of credits this human owns.'
204 | ]
205 | ];
206 | }
207 |
208 | public function interfaces() {
209 | return [
210 | GraphQL::type('Character')
211 | ];
212 | }
213 | }
214 |
215 | ```
216 |
217 |
218 | ### Custom field
219 |
220 | You can also define a field as a class if you want to reuse it in multiple types.
221 |
222 | ```php
223 |
224 | namespace App\GraphQL\Fields;
225 |
226 | use GraphQL\Type\Definition\Type;
227 | use Folklore\GraphQL\Support\Field;
228 |
229 | class PictureField extends Field {
230 |
231 | protected $attributes = [
232 | 'description' => 'A picture'
233 | ];
234 |
235 | public function type(){
236 | return Type::string();
237 | }
238 |
239 | public function args()
240 | {
241 | return [
242 | 'width' => [
243 | 'type' => Type::int(),
244 | 'description' => 'The width of the picture'
245 | ],
246 | 'height' => [
247 | 'type' => Type::int(),
248 | 'description' => 'The height of the picture'
249 | ]
250 | ];
251 | }
252 |
253 | protected function resolve($root, $args)
254 | {
255 | $width = isset($args['width']) ? $args['width']:100;
256 | $height = isset($args['height']) ? $args['height']:100;
257 | return 'http://placehold.it/'.$width.'x'.$height;
258 | }
259 |
260 | }
261 |
262 | ```
263 |
264 | You can then use it in your type declaration
265 |
266 | ```php
267 |
268 | namespace App\GraphQL\Type;
269 |
270 | use GraphQL\Type\Definition\Type;
271 | use Folklore\GraphQL\Support\Type as GraphQLType;
272 |
273 | use App\GraphQL\Fields\PictureField;
274 |
275 | class UserType extends GraphQLType {
276 |
277 | protected $attributes = [
278 | 'name' => 'User',
279 | 'description' => 'A user'
280 | ];
281 |
282 | public function fields()
283 | {
284 | return [
285 | 'id' => [
286 | 'type' => Type::nonNull(Type::string()),
287 | 'description' => 'The id of the user'
288 | ],
289 | 'email' => [
290 | 'type' => Type::string(),
291 | 'description' => 'The email of user'
292 | ],
293 | //Instead of passing an array, you pass a class path to your custom field
294 | 'picture' => PictureField::class
295 | ];
296 | }
297 |
298 | }
299 |
300 | ```
301 |
302 | ### Eager loading relationships
303 |
304 | The third argument passed to a query's resolve method is an instance of `GraphQL\Type\Definition\ResolveInfo` which you can use to retrieve keys from the request. The following is an example of using this information to eager load related Eloquent models.
305 |
306 | Your Query would look like
307 |
308 | ```php
309 | namespace App\GraphQL\Query;
310 |
311 | use GraphQL;
312 | use GraphQL\Type\Definition\Type;
313 | use GraphQL\Type\Definition\ResolveInfo;
314 | use Folklore\GraphQL\Support\Query;
315 |
316 | use App\User;
317 |
318 | class UsersQuery extends Query
319 | {
320 | protected $attributes = [
321 | 'name' => 'Users query'
322 | ];
323 |
324 | public function type()
325 | {
326 | return Type::listOf(GraphQL::type('user'));
327 | }
328 |
329 | public function args()
330 | {
331 | return [
332 | 'id' => ['name' => 'id', 'type' => Type::string()],
333 | 'email' => ['name' => 'email', 'type' => Type::string()]
334 | ];
335 | }
336 |
337 | public function resolve($root, $args, $context, ResolveInfo $info)
338 | {
339 | $fields = $info->getFieldSelection($depth = 3);
340 |
341 | $users = User::query();
342 |
343 | foreach ($fields as $field => $keys) {
344 | if ($field === 'profile') {
345 | $users->with('profile');
346 | }
347 |
348 | if ($field === 'posts') {
349 | $users->with('posts');
350 | }
351 | }
352 |
353 | return $users->get();
354 | }
355 | }
356 | ```
357 |
358 | Your Type for User would look like
359 |
360 | ```php
361 | 'User',
376 | 'description' => 'A user',
377 | ];
378 |
379 | /**
380 | * @return array
381 | */
382 | public function fields()
383 | {
384 | return [
385 | 'uuid' => [
386 | 'type' => Type::nonNull(Type::string()),
387 | 'description' => 'The uuid of the user'
388 | ],
389 | 'email' => [
390 | 'type' => Type::nonNull(Type::string()),
391 | 'description' => 'The email of user'
392 | ],
393 | 'profile' => [
394 | 'type' => GraphQL::type('Profile'),
395 | 'description' => 'The user profile',
396 | ],
397 | 'posts' => [
398 | 'type' => Type::listOf(GraphQL::type('Post')),
399 | 'description' => 'The user posts',
400 | ]
401 | ];
402 | }
403 | }
404 |
405 | ```
406 |
407 | At this point we have a profile and a post type as expected for any model
408 |
409 | ```php
410 | class ProfileType extends GraphQLType
411 | {
412 | protected $attributes = [
413 | 'name' => 'Profile',
414 | 'description' => 'A user profile',
415 | ];
416 |
417 | public function fields()
418 | {
419 | return [
420 | 'name' => [
421 | 'type' => Type::string(),
422 | 'description' => 'The name of user'
423 | ]
424 | ];
425 | }
426 | }
427 | ```
428 |
429 | ```php
430 | class PostType extends GraphQLType
431 | {
432 | protected $attributes = [
433 | 'name' => 'Post',
434 | 'description' => 'A post',
435 | ];
436 |
437 | public function fields()
438 | {
439 | return [
440 | 'title' => [
441 | 'type' => Type::nonNull(Type::string()),
442 | 'description' => 'The title of the post'
443 | ],
444 | 'body' => [
445 | 'type' => Type::string(),
446 | 'description' => 'The body the post'
447 | ]
448 | ];
449 | }
450 | }
451 | ```
452 |
453 |
454 | Lastly your query would look like, if using Homestead
455 |
456 | For example, if you use homestead:
457 |
458 | ```
459 | http://homestead.app/graphql?query=query+FetchUsers{users{uuid, email, team{name}}}
460 | ```
--------------------------------------------------------------------------------
/docs/upgrade.md:
--------------------------------------------------------------------------------
1 | ## Upgrade to 1.0
2 |
3 | ### Multiple schemas
4 | The main difference between versions prior 1.0 and 1.0 is the use of multiple schemas. You will need to update your config to have the following structure:
5 |
6 | ```php
7 |
8 | 'schema' => 'default',
9 |
10 | 'schemas' => [
11 | 'default' => [
12 | 'query' => [
13 | // Your queries
14 | ],
15 | 'mutation' => [
16 | // Your mutations
17 | ]
18 | ]
19 | ]
20 |
21 | ```
22 |
23 | ### Routes
24 | If you want to use routes that can accept schema name, you need to change `routes` to the following:
25 |
26 | ```php
27 |
28 | 'routes' => '{graphql_schema?}',
29 |
30 | // or if you use different routes for query and mutation
31 |
32 | 'routes' => [
33 | 'query' => 'query/{graphql_schema?}',
34 | 'mutation' => 'mutation/{graphql_schema?}'
35 | ],
36 |
37 | ```
38 |
39 | ### Facade methods
40 | The method `GraphQL::addQuery` and `GraphQL::addMutation` has been removed since it doesn't make sense with multiple schemas. You can use the new `GraphQL::addSchema` method to add new schemas.
41 |
42 | ### Context
43 | Since graphql-php v0.7 the arguments passed to the `resolve` method has changed. There is a third argument called `context`.
44 |
45 | ```php
46 | public function resolve($root, $args, $context, ResolveInfo $info)
47 | {
48 |
49 | }
50 | ```
51 |
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | The coding standard for laravel package
4 |
5 | src
6 |
7 | *.json
8 | *.xml
9 |
10 |
11 |
12 | */tests/*
13 |
14 |
15 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 | ./tests
16 |
17 |
18 |
19 |
20 | ./src/Folklore/GraphQL
21 |
22 | ./src/Folklore/GraphQL/routes.php
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/release.sh:
--------------------------------------------------------------------------------
1 | #!/bin/zsh
2 |
3 | #Fetch remote tags
4 | git fetch origin 'refs/tags/*:refs/tags/*'
5 |
6 | #Variables
7 | LAST_VERSION=$(git tag -l | sort -t. -k 1,1n -k 2,2n -k 3,3n -k 4,4n | tail -n 1)
8 | NEXT_VERSION=$(echo $LAST_VERSION | awk -F. -v OFS=. 'NF==1{print ++$NF}; NF>1{if(length($NF+1)>length($NF))$(NF-1)++; $NF=sprintf("%0*d", length($NF), ($NF+1)%(10^length($NF))); print}')
9 | VERSION=${1-${NEXT_VERSION}}
10 | DEFAULT_MESSAGE="Release"
11 | MESSAGE=${2-${DEFAULT_MESSAGE}}
12 | RELEASE_BRANCH="release/$VERSION"
13 |
14 | # Commit uncommited changes
15 | git add .
16 | git commit -am $MESSAGE
17 | git push origin develop
18 |
19 | # Merge develop branch in master
20 | git checkout master
21 | git merge develop
22 |
23 | # Tag and push master
24 | git tag $VERSION
25 | git push origin master --tags
26 |
27 | # Return to develop
28 | git checkout develop
29 |
--------------------------------------------------------------------------------
/src/Folklore/GraphQL/Console/EnumMakeCommand.php:
--------------------------------------------------------------------------------
1 | replaceType($stub, $name);
63 | }
64 |
65 | /**
66 | * Replace the namespace for the given stub.
67 | *
68 | * @param string $stub
69 | * @param string $name
70 | * @return $this
71 | */
72 | protected function replaceType($stub, $name)
73 | {
74 | preg_match('/([^\\\]+)$/', $name, $matches);
75 | $stub = str_replace(
76 | 'DummyType',
77 | $matches[1],
78 | $stub
79 | );
80 |
81 | return $stub;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/Folklore/GraphQL/Console/FieldMakeCommand.php:
--------------------------------------------------------------------------------
1 | replaceType($stub, $name);
64 | }
65 |
66 | /**
67 | * Replace the namespace for the given stub.
68 | *
69 | * @param string $stub
70 | * @param string $name
71 | * @return $this
72 | */
73 | protected function replaceType($stub, $name)
74 | {
75 | preg_match('/([^\\\]+)$/', $name, $matches);
76 | $stub = str_replace(
77 | 'DummyType',
78 | $matches[1],
79 | $stub
80 | );
81 |
82 | return $stub;
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/Folklore/GraphQL/Console/InterfaceMakeCommand.php:
--------------------------------------------------------------------------------
1 | replaceType($stub, $name);
64 | }
65 |
66 | /**
67 | * Replace the namespace for the given stub.
68 | *
69 | * @param string $stub
70 | * @param string $name
71 | * @return $this
72 | */
73 | protected function replaceType($stub, $name)
74 | {
75 | preg_match('/([^\\\]+)$/', $name, $matches);
76 | $stub = str_replace(
77 | 'DummyType',
78 | $matches[1],
79 | $stub
80 | );
81 |
82 | return $stub;
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/Folklore/GraphQL/Console/MutationMakeCommand.php:
--------------------------------------------------------------------------------
1 | replaceType($stub, $name);
63 | }
64 |
65 | /**
66 | * Replace the namespace for the given stub.
67 | *
68 | * @param string $stub
69 | * @param string $name
70 | * @return $this
71 | */
72 | protected function replaceType($stub, $name)
73 | {
74 | preg_match('/([^\\\]+)$/', $name, $matches);
75 | $stub = str_replace(
76 | 'DummyMutation',
77 | $matches[1],
78 | $stub
79 | );
80 |
81 | return $stub;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/Folklore/GraphQL/Console/PublishCommand.php:
--------------------------------------------------------------------------------
1 | destination)
33 | *
34 | * @var array
35 | */
36 | protected $fileMap = [];
37 |
38 | public function __construct(Filesystem $files)
39 | {
40 | parent::__construct();
41 | $this->files = $files;
42 |
43 | $fromPath = __DIR__ . '/../../..';
44 | $this->fileMap = [
45 | $fromPath.'/config/config.php' => app()->basePath('config/graphql.php'),
46 | $fromPath.'/resources/views/graphiql.php' => app()->basePath('resources/views/vendor/graphql/graphiql.php')
47 | ];
48 | }
49 |
50 | /**
51 | * Execute the console command.
52 | *
53 | * @return mixed
54 | */
55 | public function handle()
56 | {
57 | foreach ($this->fileMap as $from => $to) {
58 | if ($this->files->exists($to) && !$this->option('force')) {
59 | continue;
60 | }
61 | $this->createParentDirectory(dirname($to));
62 | $this->files->copy($from, $to);
63 | $this->status($from, $to, 'File');
64 | }
65 | }
66 |
67 | /**
68 | * Create the directory to house the published files if needed.
69 | *
70 | * @param string $directory
71 | * @return void
72 | */
73 | protected function createParentDirectory($directory)
74 | {
75 | if (!$this->files->isDirectory($directory)) {
76 | $this->files->makeDirectory($directory, 0755, true);
77 | }
78 | }
79 |
80 | /**
81 | * Write a status message to the console.
82 | *
83 | * @param string $from
84 | * @param string $to
85 | * @return void
86 | */
87 | protected function status($from, $to)
88 | {
89 | $from = str_replace(base_path(), '', realpath($from));
90 | $to = str_replace(base_path(), '', realpath($to));
91 | $this->line("Copied File [{$from}] To [{$to}]");
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/src/Folklore/GraphQL/Console/QueryMakeCommand.php:
--------------------------------------------------------------------------------
1 | replaceType($stub, $name);
63 | }
64 |
65 | /**
66 | * Replace the namespace for the given stub.
67 | *
68 | * @param string $stub
69 | * @param string $name
70 | * @return $this
71 | */
72 | protected function replaceType($stub, $name)
73 | {
74 | preg_match('/([^\\\]+)$/', $name, $matches);
75 | $stub = str_replace(
76 | 'DummyQuery',
77 | $matches[1],
78 | $stub
79 | );
80 |
81 | return $stub;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/Folklore/GraphQL/Console/ScalarMakeCommand.php:
--------------------------------------------------------------------------------
1 | replaceType($stub, $name);
64 | }
65 |
66 | /**
67 | * Replace the namespace for the given stub.
68 | *
69 | * @param string $stub
70 | * @param string $name
71 | * @return $this
72 | */
73 | protected function replaceType($stub, $name)
74 | {
75 | preg_match('/([^\\\]+)$/', $name, $matches);
76 | $stub = str_replace(
77 | 'DummyType',
78 | $matches[1],
79 | $stub
80 | );
81 |
82 | return $stub;
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/Folklore/GraphQL/Console/TypeMakeCommand.php:
--------------------------------------------------------------------------------
1 | replaceType($stub, $name);
63 | }
64 |
65 | /**
66 | * Replace the namespace for the given stub.
67 | *
68 | * @param string $stub
69 | * @param string $name
70 | * @return $this
71 | */
72 | protected function replaceType($stub, $name)
73 | {
74 | preg_match('/([^\\\]+)$/', $name, $matches);
75 | $stub = str_replace(
76 | 'DummyType',
77 | $matches[1],
78 | $stub
79 | );
80 |
81 | return $stub;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/Folklore/GraphQL/Console/stubs/enum.stub:
--------------------------------------------------------------------------------
1 | 'DummyType',
11 | 'description' => 'An enum'
12 | ];
13 |
14 | public function values() {
15 | return [];
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Folklore/GraphQL/Console/stubs/field.stub:
--------------------------------------------------------------------------------
1 | 'A field'
11 | ];
12 |
13 | public function type()
14 | {
15 | return Type::string();
16 | }
17 |
18 | public function args()
19 | {
20 | return [];
21 | }
22 |
23 | protected function resolve($root, $args)
24 | {
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Folklore/GraphQL/Console/stubs/interface.stub:
--------------------------------------------------------------------------------
1 | 'DummyType',
11 | 'description' => 'An interface',
12 | ];
13 |
14 | public function fields() {
15 | return [];
16 | }
17 |
18 | public function resolveType($root) {
19 | // Use the resolveType to resolve the Type which is implemented trough this interface
20 | $type = $root['type'];
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Folklore/GraphQL/Console/stubs/mutation.stub:
--------------------------------------------------------------------------------
1 | 'DummyMutation',
14 | 'description' => 'A mutation'
15 | ];
16 |
17 | public function type()
18 | {
19 | return Type::listOf(Type::string());
20 | }
21 |
22 | public function args()
23 | {
24 | return [
25 |
26 | ];
27 | }
28 |
29 | public function resolve($root, $args, $context, ResolveInfo $info)
30 | {
31 | return [];
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Folklore/GraphQL/Console/stubs/query.stub:
--------------------------------------------------------------------------------
1 | 'DummyQuery',
14 | 'description' => 'A query'
15 | ];
16 |
17 | public function type()
18 | {
19 | return Type::listOf(Type::string());
20 | }
21 |
22 | public function args()
23 | {
24 | return [
25 |
26 | ];
27 | }
28 |
29 | public function resolve($root, $args, $context, ResolveInfo $info)
30 | {
31 | return [];
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Folklore/GraphQL/Console/stubs/scalar.stub:
--------------------------------------------------------------------------------
1 | 'DummyType',
13 | 'description' => 'A type'
14 | ];
15 |
16 | public function fields()
17 | {
18 | return [
19 |
20 | ];
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Folklore/GraphQL/Controller.php:
--------------------------------------------------------------------------------
1 | validator = $validator;
13 |
14 | return $this;
15 | }
16 |
17 | public function getValidator()
18 | {
19 | return $this->validator;
20 | }
21 |
22 | public function getValidatorMessages()
23 | {
24 | return $this->validator ? $this->validator->messages():[];
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Folklore/GraphQL/Events/SchemaAdded.php:
--------------------------------------------------------------------------------
1 | schema = $schema;
11 | $this->name = $name;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Folklore/GraphQL/Events/TypeAdded.php:
--------------------------------------------------------------------------------
1 | type = $type;
11 | $this->name = $name;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Folklore/GraphQL/Exception/SchemaNotFound.php:
--------------------------------------------------------------------------------
1 | app = $app;
33 | }
34 |
35 | public function schema($schema = null)
36 | {
37 | if ($schema instanceof Schema) {
38 | return $schema;
39 | }
40 |
41 | $this->clearTypeInstances();
42 |
43 | $schemaName = is_string($schema) ? $schema:config('graphql.schema', 'default');
44 |
45 | if (!is_array($schema) && !isset($this->schemas[$schemaName])) {
46 | throw new SchemaNotFound('Type '.$schemaName.' not found.');
47 | }
48 |
49 | $schema = is_array($schema) ? $schema : $this->schemas[$schemaName];
50 |
51 | if ($schema instanceof Schema) {
52 | return $schema;
53 | }
54 |
55 | $schemaQuery = array_get($schema, 'query', []);
56 | $schemaMutation = array_get($schema, 'mutation', []);
57 | $schemaSubscription = array_get($schema, 'subscription', []);
58 | $schemaTypes = array_get($schema, 'types', []);
59 |
60 | //Get the types either from the schema, or the global types.
61 | $types = [];
62 | if (sizeof($schemaTypes)) {
63 | foreach ($schemaTypes as $name => $type) {
64 | $objectType = $this->objectType($type, is_numeric($name) ? []:[
65 | 'name' => $name
66 | ]);
67 | $this->typesInstances[$name] = $objectType;
68 | $types[] = $objectType;
69 |
70 | $this->addType($type, $name);
71 | }
72 | } else {
73 | foreach ($this->types as $name => $type) {
74 | $types[] = $this->type($name);
75 | }
76 | }
77 |
78 | $query = $this->objectType($schemaQuery, [
79 | 'name' => 'Query'
80 | ]);
81 |
82 | $mutation = $this->objectType($schemaMutation, [
83 | 'name' => 'Mutation'
84 | ]);
85 |
86 | $subscription = $this->objectType($schemaSubscription, [
87 | 'name' => 'Subscription'
88 | ]);
89 |
90 | return new Schema([
91 | 'query' => $query,
92 | 'mutation' => !empty($schemaMutation) ? $mutation : null,
93 | 'subscription' => !empty($schemaSubscription) ? $subscription : null,
94 | 'types' => $types
95 | ]);
96 | }
97 |
98 | public function type($name, $fresh = false)
99 | {
100 | if (!isset($this->types[$name])) {
101 | throw new TypeNotFound('Type '.$name.' not found.');
102 | }
103 |
104 | if (!$fresh && isset($this->typesInstances[$name])) {
105 | return $this->typesInstances[$name];
106 | }
107 |
108 | $class = $this->types[$name];
109 | $type = $this->objectType($class, [
110 | 'name' => $name
111 | ]);
112 | $this->typesInstances[$name] = $type;
113 |
114 | return $type;
115 | }
116 |
117 | public function objectType($type, $opts = [])
118 | {
119 | // If it's already an ObjectType, just update properties and return it.
120 | // If it's an array, assume it's an array of fields and build ObjectType
121 | // from it. Otherwise, build it from a string or an instance.
122 | $objectType = null;
123 | if ($type instanceof ObjectType) {
124 | $objectType = $type;
125 | foreach ($opts as $key => $value) {
126 | if (property_exists($objectType, $key)) {
127 | $objectType->{$key} = $value;
128 | }
129 | if (isset($objectType->config[$key])) {
130 | $objectType->config[$key] = $value;
131 | }
132 | }
133 | } elseif (is_array($type)) {
134 | $objectType = $this->buildObjectTypeFromFields($type, $opts);
135 | } else {
136 | $objectType = $this->buildObjectTypeFromClass($type, $opts);
137 | }
138 |
139 | return $objectType;
140 | }
141 |
142 | public function query($query, $variables = [], $opts = [])
143 | {
144 | $result = $this->queryAndReturnResult($query, $variables, $opts);
145 |
146 | if (!empty($result->errors)) {
147 | $errorFormatter = config('graphql.error_formatter', [self::class, 'formatError']);
148 |
149 | return [
150 | 'data' => $result->data,
151 | 'errors' => array_map($errorFormatter, $result->errors)
152 | ];
153 | } else {
154 | return [
155 | 'data' => $result->data
156 | ];
157 | }
158 | }
159 |
160 | public function queryAndReturnResult($query, $variables = [], $opts = [])
161 | {
162 | $context = array_get($opts, 'context', null);
163 | $schemaName = array_get($opts, 'schema', null);
164 | $operationName = array_get($opts, 'operationName', null);
165 | $defaultFieldResolver = config('graphql.defaultFieldResolver', null);
166 |
167 | $additionalResolversSchemaName = is_string($schemaName) ? $schemaName : config('graphql.schema', 'default');
168 | $additionalResolvers = config('graphql.resolvers.' . $additionalResolversSchemaName, []);
169 | $root = is_array($additionalResolvers) ? array_merge(array_get($opts, 'root', []), $additionalResolvers) : $additionalResolvers;
170 |
171 | $schema = $this->schema($schemaName);
172 |
173 | $result = GraphQLBase::executeQuery($schema, $query, $root, $context, $variables, $operationName, $defaultFieldResolver);
174 |
175 | return $result;
176 | }
177 |
178 | public function addTypes($types)
179 | {
180 | foreach ($types as $name => $type) {
181 | $this->addType($type, is_numeric($name) ? null:$name);
182 | }
183 | }
184 |
185 | public function addType($class, $name = null)
186 | {
187 | $name = $this->getTypeName($class, $name);
188 | $this->types[$name] = $class;
189 |
190 | event(new TypeAdded($class, $name));
191 | }
192 |
193 | public function addSchema($name, $schema)
194 | {
195 | $this->schemas[$name] = $schema;
196 |
197 | event(new SchemaAdded($schema, $name));
198 | }
199 |
200 | public function clearType($name)
201 | {
202 | if (isset($this->types[$name])) {
203 | unset($this->types[$name]);
204 | }
205 | }
206 |
207 | public function clearSchema($name)
208 | {
209 | if (isset($this->schemas[$name])) {
210 | unset($this->schemas[$name]);
211 | }
212 | }
213 |
214 | public function clearTypes()
215 | {
216 | $this->types = [];
217 | }
218 |
219 | public function clearSchemas()
220 | {
221 | $this->schemas = [];
222 | }
223 |
224 | public function getTypes()
225 | {
226 | return $this->types;
227 | }
228 |
229 | public function getSchemas()
230 | {
231 | return $this->schemas;
232 | }
233 |
234 | protected function clearTypeInstances()
235 | {
236 | $this->typesInstances = [];
237 | }
238 |
239 | protected function buildObjectTypeFromClass($type, $opts = [])
240 | {
241 | if (!is_object($type)) {
242 | $type = $this->app->make($type);
243 | }
244 |
245 | if (!$type instanceof TypeConvertible) {
246 | throw new TypeNotFound(sprintf('Unable to convert %s to a GraphQL type', get_class($type)));
247 | }
248 |
249 | foreach ($opts as $key => $value) {
250 | $type->{$key} = $value;
251 | }
252 |
253 | return $type->toType();
254 | }
255 |
256 | protected function buildObjectTypeFromFields($fields, $opts = [])
257 | {
258 | $typeFields = [];
259 | foreach ($fields as $name => $field) {
260 | if (is_string($field)) {
261 | $field = $this->app->make($field);
262 | $name = is_numeric($name) ? $field->name:$name;
263 | $field->name = $name;
264 | $field = $field->toArray();
265 | } else {
266 | $name = is_numeric($name) ? $field['name']:$name;
267 | $field['name'] = $name;
268 | }
269 | $typeFields[$name] = $field;
270 | }
271 |
272 | return new ObjectType(array_merge([
273 | 'fields' => $typeFields
274 | ], $opts));
275 | }
276 |
277 | protected function getTypeName($class, $name = null)
278 | {
279 | if ($name) {
280 | return $name;
281 | }
282 |
283 | $type = is_object($class) ? $class:$this->app->make($class);
284 | return $type->name;
285 | }
286 |
287 | public static function formatError(Error $e)
288 | {
289 | $error = [
290 | 'message' => $e->getMessage()
291 | ];
292 |
293 | $locations = $e->getLocations();
294 | if (!empty($locations)) {
295 | $error['locations'] = array_map(function ($loc) {
296 | return $loc->toArray();
297 | }, $locations);
298 | }
299 |
300 | $previous = $e->getPrevious();
301 | if ($previous && $previous instanceof ValidationError) {
302 | $error['validation'] = $previous->getValidatorMessages();
303 | }
304 |
305 | return $error;
306 | }
307 |
308 | public function pagination(ObjectType $type)
309 | {
310 | // Only add the PaginationCursor when there is a pagination defined.
311 | if (!isset($this->types['PaginationCursor'])) {
312 | $this->types['PaginationCursor'] = new PaginationCursorType();
313 | }
314 |
315 | // If the instace type of the given pagination does not exists, create a new one!
316 | if (!isset($this->typesInstances[$type->name . 'Pagination'])) {
317 | $this->typesInstances[$type->name . 'Pagination'] = new PaginationType($type->name);
318 | }
319 |
320 | return $this->typesInstances[$type->name . 'Pagination'];
321 | }
322 | }
323 |
--------------------------------------------------------------------------------
/src/Folklore/GraphQL/GraphQLController.php:
--------------------------------------------------------------------------------
1 | route();
11 |
12 | /**
13 | * Prevent schema middlewares to be applied to graphiql routes
14 | *
15 | * Be careful !! For Lumen < 5.6, Request->route() returns an array with
16 | * 'as' key for named routes
17 | *
18 | * @see https://github.com/laravel/lumen-framework/issues/119
19 | * @see https://laravel.com/api/5.5/Illuminate/Http/Request.html#method_route
20 | */
21 |
22 | $prefix = config('graphql.prefix');
23 |
24 | $routeName = is_object($route)
25 | ? $route->getName()
26 | : (is_array($route) && isset($route['as'])
27 | ? $route['as']
28 | : null);
29 |
30 | if (!is_null($routeName) && preg_match('/^graphql\.graphiql/', $routeName)) {
31 | return;
32 | }
33 |
34 | $defaultSchema = config('graphql.schema');
35 | if (is_array($route)) {
36 | $schema = array_get($route, '2.'.$prefix.'_schema', $defaultSchema);
37 | } elseif (is_object($route)) {
38 | $schema = $route->parameter($prefix.'_schema', $defaultSchema);
39 | } else {
40 | $schema = $defaultSchema;
41 | }
42 |
43 | $middleware = config('graphql.middleware_schema.' . $schema, null);
44 |
45 | if ($middleware) {
46 | $this->middleware($middleware);
47 | }
48 | }
49 |
50 | public function query(Request $request, $graphql_schema = null)
51 | {
52 | $isBatch = !$request->has('query');
53 | $inputs = $request->all();
54 |
55 | if (is_null($graphql_schema)) {
56 | $graphql_schema = config('graphql.schema');
57 | }
58 |
59 | if (!$isBatch) {
60 | $data = $this->executeQuery($graphql_schema, $inputs);
61 | } else {
62 | $data = [];
63 | foreach ($inputs as $input) {
64 | $data[] = $this->executeQuery($graphql_schema, $input);
65 | }
66 | }
67 |
68 | $headers = config('graphql.headers', []);
69 | $options = config('graphql.json_encoding_options', 0);
70 |
71 | $errors = !$isBatch ? array_get($data, 'errors', []) : [];
72 | $authorized = array_reduce($errors, function ($authorized, $error) {
73 | return !$authorized || array_get($error, 'message') === 'Unauthorized' ? false : true;
74 | }, true);
75 | if (!$authorized) {
76 | return response()->json($data, 403, $headers, $options);
77 | }
78 |
79 | return response()->json($data, 200, $headers, $options);
80 | }
81 |
82 | public function graphiql(Request $request, $graphql_schema = null)
83 | {
84 | $view = config('graphql.graphiql.view', 'graphql::graphiql');
85 | return view($view, [
86 | 'graphql_schema' => $graphql_schema,
87 | ]);
88 | }
89 |
90 | protected function executeQuery($schema, $input)
91 | {
92 | $variablesInputName = config('graphql.variables_input_name', 'variables');
93 | $query = array_get($input, 'query');
94 | $variables = array_get($input, $variablesInputName);
95 | if (is_string($variables)) {
96 | $variables = json_decode($variables, true);
97 | }
98 | $operationName = array_get($input, 'operationName');
99 | $context = $this->queryContext($query, $variables, $schema);
100 | return app('graphql')->query($query, $variables, [
101 | 'context' => $context,
102 | 'schema' => $schema,
103 | 'operationName' => $operationName
104 | ]);
105 | }
106 |
107 | protected function queryContext($query, $variables, $schema)
108 | {
109 | try {
110 | return app('auth')->user();
111 | } catch (\Exception $e) {
112 | return null;
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/Folklore/GraphQL/LumenServiceProvider.php:
--------------------------------------------------------------------------------
1 | app, 'router') ? $this->app->router : $this->app;
15 | }
16 |
17 | /**
18 | * Bootstrap publishes
19 | *
20 | * @return void
21 | */
22 | protected function bootPublishes()
23 | {
24 | $configPath = __DIR__ . '/../../config';
25 | $viewsPath = __DIR__.'/../../resources/views';
26 | $this->mergeConfigFrom($configPath . '/config.php', 'graphql');
27 | $this->loadViewsFrom($viewsPath, 'graphql');
28 | }
29 |
30 | /**
31 | * Bootstrap router
32 | *
33 | * @return void
34 | */
35 | protected function bootRouter()
36 | {
37 | if ($this->app['config']->get('graphql.routes')) {
38 | $router = $this->getRouter();
39 | include __DIR__.'/routes.php';
40 | }
41 | }
42 |
43 | /**
44 | * Register facade
45 | *
46 | * @return void
47 | */
48 | public function registerGraphQL()
49 | {
50 | static $registred = false;
51 | // Check if facades are activated
52 | if (Facade::getFacadeApplication() == $this->app && !$registred) {
53 | class_alias(\Folklore\GraphQL\Support\Facades\GraphQL::class, 'GraphQL');
54 | $registred = true;
55 | }
56 |
57 | parent::registerGraphQL();
58 | }
59 |
60 | /**
61 | * Register the helper command to publish the config file
62 | */
63 | public function registerConsole()
64 | {
65 | parent::registerConsole();
66 |
67 | $this->commands(\Folklore\GraphQL\Console\PublishCommand::class);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Folklore/GraphQL/ServiceProvider.php:
--------------------------------------------------------------------------------
1 | app['router'];
14 | }
15 |
16 | /**
17 | * Bootstrap any application services.
18 | *
19 | * @return void
20 | */
21 | public function boot()
22 | {
23 | $this->bootPublishes();
24 |
25 | $this->bootRouter();
26 |
27 | $this->bootViews();
28 | }
29 |
30 | /**
31 | * Bootstrap router
32 | *
33 | * @return void
34 | */
35 | protected function bootRouter()
36 | {
37 | if ($this->app['config']->get('graphql.routes') && !$this->app->routesAreCached()) {
38 | $router = $this->getRouter();
39 | include __DIR__.'/routes.php';
40 | }
41 | }
42 |
43 | /**
44 | * Bootstrap events
45 | *
46 | * @param GraphQL $graphql
47 | * @return void
48 | */
49 | protected function registerEventListeners(GraphQL $graphql)
50 | {
51 | // Update the schema route pattern when schema is added
52 | $this->app['events']->listen(Events\SchemaAdded::class, function () use ($graphql) {
53 | $router = $this->getRouter();
54 | if (method_exists($router, 'pattern')) {
55 | $schemaNames = array_keys($graphql->getSchemas());
56 | $router->pattern('graphql_schema', '('.implode('|', $schemaNames).')');
57 | }
58 | });
59 | }
60 |
61 | /**
62 | * Bootstrap publishes
63 | *
64 | * @return void
65 | */
66 | protected function bootPublishes()
67 | {
68 | $configPath = __DIR__.'/../../config';
69 | $viewsPath = __DIR__.'/../../resources/views';
70 |
71 | $this->mergeConfigFrom($configPath.'/config.php', 'graphql');
72 |
73 | $this->loadViewsFrom($viewsPath, 'graphql');
74 |
75 | $this->publishes([
76 | $configPath.'/config.php' => config_path('graphql.php'),
77 | ], 'config');
78 |
79 | $this->publishes([
80 | $viewsPath => base_path('resources/views/vendor/graphql'),
81 | ], 'views');
82 | }
83 |
84 | /**
85 | * Add types from config
86 | *
87 | * @param GraphQL $graphql
88 | * @return void
89 | */
90 | protected function addTypes(GraphQL $graphql)
91 | {
92 | $types = $this->app['config']->get('graphql.types', []);
93 |
94 | foreach ($types as $name => $type) {
95 | $graphql->addType($type, is_numeric($name) ? null : $name);
96 | }
97 | }
98 |
99 | /**
100 | * Add schemas from config
101 | *
102 | * @param GraphQL $graphql
103 | * @return void
104 | */
105 | protected function addSchemas(GraphQL $graphql)
106 | {
107 | $schemas = $this->app['config']->get('graphql.schemas', []);
108 |
109 | foreach ($schemas as $name => $schema) {
110 | $graphql->addSchema($name, $schema);
111 | }
112 | }
113 |
114 | /**
115 | * Bootstrap Views
116 | *
117 | * @return void
118 | */
119 | protected function bootViews()
120 | {
121 | $config = $this->app['config'];
122 |
123 | if ($config->get('graphql.graphiql', true)) {
124 | $view = $config->get('graphql.graphiql.view', 'graphql::graphiql');
125 | $composer = $config->get('graphql.graphiql.composer', View\GraphiQLComposer::class);
126 | $this->app['view']->composer($view, $composer);
127 | }
128 | }
129 |
130 | /**
131 | * Configure security from config
132 | *
133 | * @return void
134 | */
135 | protected function applySecurityRules()
136 | {
137 | $maxQueryComplexity = config('graphql.security.query_max_complexity');
138 | if ($maxQueryComplexity !== null) {
139 | /** @var QueryComplexity $queryComplexity */
140 | $queryComplexity = DocumentValidator::getRule('QueryComplexity');
141 | $queryComplexity->setMaxQueryComplexity($maxQueryComplexity);
142 | }
143 |
144 | $maxQueryDepth = config('graphql.security.query_max_depth');
145 | if ($maxQueryDepth !== null) {
146 | /** @var QueryDepth $queryDepth */
147 | $queryDepth = DocumentValidator::getRule('QueryDepth');
148 | $queryDepth->setMaxQueryDepth($maxQueryDepth);
149 | }
150 |
151 | $disableIntrospection = config('graphql.security.disable_introspection');
152 | if ($disableIntrospection === true) {
153 | /** @var DisableIntrospection $disableIntrospection */
154 | $disableIntrospection = DocumentValidator::getRule('DisableIntrospection');
155 | $disableIntrospection->setEnabled(DisableIntrospection::ENABLED);
156 | }
157 | }
158 |
159 | /**
160 | * Register any application services.
161 | *
162 | * @return void
163 | */
164 | public function register()
165 | {
166 | $this->registerGraphQL();
167 |
168 | $this->registerConsole();
169 | }
170 |
171 | /**
172 | * Register GraphQL facade
173 | *
174 | * @return void
175 | */
176 | protected function registerGraphQL()
177 | {
178 | $this->app->singleton('graphql', function ($app) {
179 |
180 | $graphql = new GraphQL($app);
181 |
182 | $this->addTypes($graphql);
183 |
184 | $this->addSchemas($graphql);
185 |
186 | $this->registerEventListeners($graphql);
187 |
188 | $this->applySecurityRules();
189 |
190 | return $graphql;
191 | });
192 | }
193 |
194 | /**
195 | * Register console commands
196 | *
197 | * @return void
198 | */
199 | protected function registerConsole()
200 | {
201 | $this->commands(Console\TypeMakeCommand::class);
202 | $this->commands(Console\QueryMakeCommand::class);
203 | $this->commands(Console\MutationMakeCommand::class);
204 | $this->commands(Console\EnumMakeCommand::class);
205 | $this->commands(Console\FieldMakeCommand::class);
206 | $this->commands(Console\InterfaceMakeCommand::class);
207 | $this->commands(Console\ScalarMakeCommand::class);
208 | }
209 |
210 | /**
211 | * Get the services provided by the provider.
212 | *
213 | * @return array
214 | */
215 | public function provides()
216 | {
217 | return ['graphql'];
218 | }
219 | }
220 |
--------------------------------------------------------------------------------
/src/Folklore/GraphQL/Support/Contracts/TypeConvertible.php:
--------------------------------------------------------------------------------
1 | values();
17 | $attributesValues = array_get($this->attributes, 'values', []);
18 | return sizeof($attributesValues) ? $attributesValues : $values;
19 | }
20 |
21 | /**
22 | * Get the attributes from the container.
23 | *
24 | * @return array
25 | */
26 | public function getAttributes()
27 | {
28 | $attributes = parent::getAttributes();
29 |
30 | $values = $this->getValues();
31 | if (isset($values)) {
32 | $attributes['values'] = $values;
33 | }
34 |
35 | return $attributes;
36 | }
37 |
38 | public function toType()
39 | {
40 | return new EnumObjectType($this->toArray());
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Folklore/GraphQL/Support/Facades/GraphQL.php:
--------------------------------------------------------------------------------
1 | attributes();
79 | $args = $this->args();
80 |
81 | $attributes = array_merge($this->attributes, [
82 | 'args' => $args
83 | ], $attributes);
84 |
85 | $type = $this->type();
86 | if (isset($type)) {
87 | $attributes['type'] = $type;
88 | }
89 |
90 | $resolver = $this->getResolver();
91 | if (isset($resolver)) {
92 | $attributes['resolve'] = $resolver;
93 | }
94 |
95 | return $attributes;
96 | }
97 |
98 | /**
99 | * Convert the Fluent instance to an array.
100 | *
101 | * @return array
102 | */
103 | public function toArray()
104 | {
105 | return $this->getAttributes();
106 | }
107 |
108 | /**
109 | * Dynamically retrieve the value of an attribute.
110 | *
111 | * @param string $key
112 | * @return mixed
113 | */
114 | public function __get($key)
115 | {
116 | $attributes = $this->getAttributes();
117 | return isset($attributes[$key]) ? $attributes[$key]:null;
118 | }
119 |
120 | /**
121 | * Dynamically check if an attribute is set.
122 | *
123 | * @param string $key
124 | * @return void
125 | */
126 | public function __isset($key)
127 | {
128 | $attributes = $this->getAttributes();
129 | return isset($attributes[$key]);
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/src/Folklore/GraphQL/Support/InputType.php:
--------------------------------------------------------------------------------
1 | toArray());
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Folklore/GraphQL/Support/InterfaceType.php:
--------------------------------------------------------------------------------
1 | getTypeResolver();
32 | if (isset($resolver)) {
33 | $attributes['resolveType'] = $resolver;
34 | }
35 |
36 | return $attributes;
37 | }
38 |
39 | public function toType()
40 | {
41 | return new BaseInterfaceType($this->toArray());
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Folklore/GraphQL/Support/Mutation.php:
--------------------------------------------------------------------------------
1 | 'PaginationCursor',
17 | 'fields' => [
18 | 'total' => [
19 | 'type' => GraphQLType::nonNull(GraphQLType::int()),
20 | 'resolve' => function (LengthAwarePaginator $paginator) {
21 | return $paginator->total();
22 | },
23 | ],
24 | 'perPage' => [
25 | 'type' => GraphQLType::nonNull(GraphQLType::int()),
26 | 'resolve' => function (LengthAwarePaginator $paginator) {
27 | return $paginator->perPage();
28 | },
29 | ],
30 | 'currentPage' => [
31 | 'type' => GraphQLType::nonNull(GraphQLType::int()),
32 | 'resolve' => function (LengthAwarePaginator $paginator) {
33 | return $paginator->currentPage();
34 | },
35 | ],
36 | 'hasPages' => [
37 | 'type' => GraphQLType::nonNull(GraphQLType::boolean()),
38 | 'resolve' => function (LengthAwarePaginator $paginator) {
39 | return $paginator->hasPages();
40 | },
41 | ],
42 | ],
43 | ]);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Folklore/GraphQL/Support/PaginationType.php:
--------------------------------------------------------------------------------
1 | $type . 'Pagination',
16 | 'fields' => [
17 | 'items' => [
18 | 'type' => GraphQLType::listOf(GraphQL::type($type)),
19 | 'resolve' => function (LengthAwarePaginator $paginator) {
20 | return $paginator->getCollection();
21 | },
22 | ],
23 | 'cursor' => [
24 | 'type' => GraphQLType::nonNull(GraphQL::type('PaginationCursor')),
25 | 'resolve' => function (LengthAwarePaginator $paginator) {
26 | return $paginator;
27 | },
28 | ],
29 | ],
30 | ]);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Folklore/GraphQL/Support/Query.php:
--------------------------------------------------------------------------------
1 | args() as $name => $arg) {
34 | if (isset($arg['rules'])) {
35 | $argsRules[$name] = $this->resolveRules($arg['rules'], $arguments);
36 | }
37 |
38 | if (isset($arg['type'])) {
39 | $argsRules = array_merge($argsRules, $this->inferRulesFromType($arg['type'], $name, $arguments));
40 | }
41 | }
42 |
43 | return array_merge($rules, $argsRules);
44 | }
45 |
46 | public function resolveRules($rules, $arguments)
47 | {
48 | if (is_callable($rules)) {
49 | return call_user_func_array($rules, $arguments);
50 | }
51 |
52 | return $rules;
53 | }
54 |
55 | public function inferRulesFromType($type, $prefix, $resolutionArguments)
56 | {
57 | $rules = [];
58 |
59 | // if it is an array type, add an array validation component
60 | if ($type instanceof ListOfType) {
61 | $prefix = "{$prefix}.*";
62 | }
63 |
64 | // make sure we are dealing with the actual type
65 | if ($type instanceof WrappingType) {
66 | $type = $type->getWrappedType();
67 | }
68 |
69 | // if it is an input object type - the only type we care about here...
70 | if ($type instanceof InputObjectType) {
71 | // merge in the input type's rules
72 | $rules = array_merge($rules, $this->getInputTypeRules($type, $prefix, $resolutionArguments));
73 | }
74 |
75 | // Ignore scalar types
76 |
77 | return $rules;
78 | }
79 |
80 | public function getInputTypeRules(InputObjectType $input, $prefix, $resolutionArguments)
81 | {
82 | $rules = [];
83 |
84 | foreach ($input->getFields() as $name => $field) {
85 | $key = "{$prefix}.{$name}";
86 |
87 | // get any explicitly set rules
88 | if (isset($field->rules)) {
89 | $rules[$key] = $this->resolveRules($field->rules, $resolutionArguments);
90 | }
91 |
92 | // then recursively call the parent method to see if this is an
93 | // input object, passing in the new prefix
94 | $rules = array_merge($rules, $this->inferRulesFromType($field->type, $key, $resolutionArguments));
95 | }
96 |
97 | return $rules;
98 | }
99 |
100 | protected function getValidator($args, $rules, $messages = [])
101 | {
102 | $validator = app('validator')->make($args, $rules, $messages);
103 | if (method_exists($this, 'withValidator')) {
104 | $this->withValidator($validator, $args);
105 | }
106 |
107 | return $validator;
108 | }
109 |
110 | protected function getResolver()
111 | {
112 | $resolver = parent::getResolver();
113 | if (!$resolver) {
114 | return null;
115 | }
116 |
117 | return function () use ($resolver) {
118 | $arguments = func_get_args();
119 |
120 | $rules = call_user_func_array([$this, 'getRules'], $arguments);
121 | $validationErrorMessages = call_user_func_array([$this, 'validationErrorMessages'], $arguments);
122 | if (sizeof($rules)) {
123 | $args = array_get($arguments, 1, []);
124 | $validator = $this->getValidator($args, $rules, $validationErrorMessages);
125 | if ($validator->fails()) {
126 | throw with(new ValidationError('validation'))->setValidator($validator);
127 | }
128 | }
129 |
130 | return call_user_func_array($resolver, $arguments);
131 | };
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/Folklore/GraphQL/Support/Type.php:
--------------------------------------------------------------------------------
1 | fields();
53 | $allFields = [];
54 | foreach ($fields as $name => $field) {
55 | if (is_string($field)) {
56 | $field = app($field);
57 | $field->name = $name;
58 | $allFields[$name] = $field->toArray();
59 | } else {
60 | $resolver = $this->getFieldResolver($name, $field);
61 |
62 | if (isset($field['class'])) {
63 | $field = $field['class'];
64 |
65 | if (is_string($field)) {
66 | $field = app($field);
67 | }
68 |
69 | $field->name = $name;
70 | $field = $field->toArray();
71 | }
72 |
73 | if ($resolver) {
74 | $field['resolve'] = $resolver;
75 | }
76 |
77 | $allFields[$name] = $field;
78 | }
79 | }
80 |
81 | return $allFields;
82 | }
83 |
84 | /**
85 | * Get the attributes from the container.
86 | *
87 | * @return array
88 | */
89 | public function getAttributes()
90 | {
91 | $attributes = $this->attributes();
92 | $interfaces = $this->interfaces();
93 |
94 | $attributes = array_merge($this->attributes, [
95 | 'fields' => function () {
96 | return $this->getFields();
97 | }
98 | ], $attributes);
99 |
100 | if (sizeof($interfaces)) {
101 | $attributes['interfaces'] = $interfaces;
102 | }
103 |
104 | return $attributes;
105 | }
106 |
107 | /**
108 | * Convert the Fluent instance to an array.
109 | *
110 | * @return array
111 | */
112 | public function toArray()
113 | {
114 | return $this->getAttributes();
115 | }
116 |
117 | public function toType()
118 | {
119 | if ($this->inputObject) {
120 | return new InputObjectType($this->toArray());
121 | }
122 | if ($this->enumObject) {
123 | return new EnumType($this->toArray());
124 | }
125 | return new ObjectType($this->toArray());
126 | }
127 |
128 | /**
129 | * Dynamically retrieve the value of an attribute.
130 | *
131 | * @param string $key
132 | * @return mixed
133 | */
134 | public function __get($key)
135 | {
136 | $attributes = $this->getAttributes();
137 | return isset($attributes[$key]) ? $attributes[$key]:null;
138 | }
139 |
140 | /**
141 | * Dynamically check if an attribute is set.
142 | *
143 | * @param string $key
144 | * @return void
145 | */
146 | public function __isset($key)
147 | {
148 | $attributes = $this->getAttributes();
149 | return isset($attributes[$key]);
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/src/Folklore/GraphQL/Support/UnionType.php:
--------------------------------------------------------------------------------
1 | attributes, 'types', []);
17 | return sizeof($attributesTypes) ? $attributesTypes : $this->types();
18 | }
19 |
20 | /**
21 | * Get the attributes from the container.
22 | *
23 | * @return array
24 | */
25 | public function getAttributes()
26 | {
27 | $attributes = parent::getAttributes();
28 |
29 | $types = $this->getTypes();
30 | if (isset($types)) {
31 | $attributes['types'] = $types;
32 | }
33 |
34 | return $attributes;
35 | }
36 |
37 | public function toType()
38 | {
39 | return new UnionObjectType($this->toArray());
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Folklore/GraphQL/View/GraphiQLComposer.php:
--------------------------------------------------------------------------------
1 | graphql_schema;
19 |
20 | if (! empty($schema)) {
21 | $view->graphqlPath = $hasRoute ? route('graphql.query', ['graphql_schema' => $schema]) : url('/graphql/' . $schema);
22 | } else {
23 | $view->graphqlPath = $hasRoute ? route('graphql.query') : url('/graphql');
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Folklore/GraphQL/routes.php:
--------------------------------------------------------------------------------
1 | group(array(
8 | 'prefix' => config('graphql.prefix'),
9 | 'domain' => config('graphql.domain'),
10 | 'middleware' => config('graphql.middleware', [])
11 | ), function ($router) use ($schemaParameterPattern) {
12 | //Get routes from config
13 | $routes = config('graphql.routes');
14 | $queryRoute = null;
15 | $mutationRoute = null;
16 | if (is_array($routes)) {
17 | $queryRoute = array_get($routes, 'query', null);
18 | $mutationRoute = array_get($routes, 'mutation', null);
19 | } else {
20 | $queryRoute = $routes;
21 | $mutationRoute = $routes;
22 | }
23 |
24 | //Get controllers from config
25 | $controllers = config('graphql.controllers', '\Folklore\GraphQL\GraphQLController@query');
26 | $queryController = null;
27 | $mutationController = null;
28 | if (is_array($controllers)) {
29 | $queryController = array_get($controllers, 'query', null);
30 | $mutationController = array_get($controllers, 'mutation', null);
31 | } else {
32 | $queryController = $controllers;
33 | $mutationController = $controllers;
34 | }
35 |
36 | //Query
37 | if ($queryRoute) {
38 | // Remove optional parameter in Lumen. Instead, creates two routes.
39 | if (!$router instanceof \Illuminate\Routing\Router &&
40 | preg_match($schemaParameterPattern, $queryRoute)
41 | ) {
42 | $router->get(preg_replace($schemaParameterPattern, '', $queryRoute), array(
43 | 'as' => 'graphql.query',
44 | 'uses' => $queryController
45 | ));
46 | $router->get(preg_replace($schemaParameterPattern, '{graphql_schema}', $queryRoute), array(
47 | 'as' => 'graphql.query.with_schema',
48 | 'uses' => $queryController
49 | ));
50 | $router->post(preg_replace($schemaParameterPattern, '', $queryRoute), array(
51 | 'as' => 'graphql.query.post',
52 | 'uses' => $queryController
53 | ));
54 | $router->post(preg_replace($schemaParameterPattern, '{graphql_schema}', $queryRoute), array(
55 | 'as' => 'graphql.query.post.with_schema',
56 | 'uses' => $queryController
57 | ));
58 | } else {
59 | $router->get($queryRoute, array(
60 | 'as' => 'graphql.query',
61 | 'uses' => $queryController
62 | ));
63 | $router->post($queryRoute, array(
64 | 'as' => 'graphql.query.post',
65 | 'uses' => $queryController
66 | ));
67 | }
68 | }
69 |
70 | //Mutation routes (define only if different than query)
71 | if ($mutationRoute && $mutationRoute !== $queryRoute) {
72 | // Remove optional parameter in Lumen. Instead, creates two routes.
73 | if (!$router instanceof \Illuminate\Routing\Router &&
74 | preg_match($schemaParameterPattern, $mutationRoute)
75 | ) {
76 | $router->post(preg_replace($schemaParameterPattern, '', $mutationRoute), array(
77 | 'as' => 'graphql.mutation',
78 | 'uses' => $mutationController
79 | ));
80 | $router->post(preg_replace($schemaParameterPattern, '{graphql_schema}', $mutationRoute), array(
81 | 'as' => 'graphql.mutation.with_schema',
82 | 'uses' => $mutationController
83 | ));
84 | $router->get(preg_replace($schemaParameterPattern, '', $mutationRoute), array(
85 | 'as' => 'graphql.mutation.get',
86 | 'uses' => $mutationController
87 | ));
88 | $router->get(preg_replace($schemaParameterPattern, '{graphql_schema}', $mutationRoute), array(
89 | 'as' => 'graphql.mutation.get.with_schema',
90 | 'uses' => $mutationController
91 | ));
92 | } else {
93 | $router->post($mutationRoute, array(
94 | 'as' => 'graphql.mutation',
95 | 'uses' => $mutationController
96 | ));
97 | $router->get($mutationRoute, array(
98 | 'as' => 'graphql.mutation.get',
99 | 'uses' => $mutationController
100 | ));
101 | }
102 | }
103 | });
104 |
105 | //GraphiQL
106 | $graphiQL = config('graphql.graphiql', true);
107 | if ($graphiQL) {
108 | $graphiQLRoute = config('graphql.graphiql.routes', 'graphiql');
109 | $graphiQLController = config('graphql.graphiql.controller', '\Folklore\GraphQL\GraphQLController@graphiql');
110 | if (!$router instanceof \Illuminate\Routing\Router &&
111 | preg_match($schemaParameterPattern, $graphiQLRoute)
112 | ) {
113 | $router->get(preg_replace($schemaParameterPattern, '', $graphiQLRoute), [
114 | 'as' => 'graphql.graphiql',
115 | 'middleware' => config('graphql.graphiql.middleware', []),
116 | 'uses' => $graphiQLController
117 | ]);
118 | $router->get(preg_replace($schemaParameterPattern, '{graphql_schema}', $graphiQLRoute), [
119 | 'as' => 'graphql.graphiql.with_schema',
120 | 'middleware' => config('graphql.graphiql.middleware', []),
121 | 'uses' => $graphiQLController
122 | ]);
123 | } else {
124 | $router->get($graphiQLRoute, [
125 | 'as' => 'graphql.graphiql',
126 | 'middleware' => config('graphql.graphiql.middleware', []),
127 | 'uses' => $graphiQLController
128 | ]);
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/src/config/config.php:
--------------------------------------------------------------------------------
1 | 'graphql',
10 |
11 | /*
12 | * The domain for routes
13 | */
14 | 'domain' => null,
15 |
16 | /*
17 | * The routes to make GraphQL request. Either a string that will apply
18 | * to both query and mutation or an array containing the key 'query' and/or
19 | * 'mutation' with the according Route
20 | *
21 | * Example:
22 | *
23 | * Same route for both query and mutation
24 | *
25 | * 'routes' => [
26 | * 'query' => 'query/{graphql_schema?}',
27 | * 'mutation' => 'mutation/{graphql_schema?}',
28 | * mutation' => 'graphiql'
29 | * ]
30 | *
31 | * you can also disable routes by setting routes to null
32 | *
33 | * 'routes' => null,
34 | */
35 | 'routes' => '{graphql_schema?}',
36 |
37 | /*
38 | * The controller to use in GraphQL requests. Either a string that will apply
39 | * to both query and mutation or an array containing the key 'query' and/or
40 | * 'mutation' with the according Controller and method
41 | *
42 | * Example:
43 | *
44 | * 'controllers' => [
45 | * 'query' => '\Folklore\GraphQL\GraphQLController@query',
46 | * 'mutation' => '\Folklore\GraphQL\GraphQLController@mutation'
47 | * ]
48 | */
49 | 'controllers' => \Folklore\GraphQL\GraphQLController::class.'@query',
50 |
51 | /*
52 | * The name of the input variable that contain variables when you query the
53 | * endpoint. Most libraries use "variables", you can change it here in case you need it.
54 | * In previous versions, the default used to be "params"
55 | */
56 | 'variables_input_name' => 'variables',
57 |
58 | /*
59 | * Any middleware for the 'graphql' route group
60 | */
61 | 'middleware' => [],
62 |
63 | /**
64 | * Any middleware for a specific 'graphql' schema
65 | */
66 | 'middleware_schema' => [
67 | 'default' => [],
68 | ],
69 |
70 | /*
71 | * Any headers that will be added to the response returned by the default controller
72 | */
73 | 'headers' => [],
74 |
75 | /*
76 | * Any JSON encoding options when returning a response from the default controller
77 | * See http://php.net/manual/function.json-encode.php for the full list of options
78 | */
79 | 'json_encoding_options' => 0,
80 |
81 | /*
82 | * Config for GraphiQL (see (https://github.com/graphql/graphiql).
83 | * To disable GraphiQL, set this to null
84 | */
85 | 'graphiql' => [
86 | 'routes' => '/graphiql/{graphql_schema?}',
87 | 'controller' => \Folklore\GraphQL\GraphQLController::class.'@graphiql',
88 | 'middleware' => [],
89 | 'view' => 'graphql::graphiql',
90 | 'composer' => \Folklore\GraphQL\View\GraphiQLComposer::class,
91 | ],
92 |
93 | /*
94 | * The name of the default schema used when no arguments are provided
95 | * to GraphQL::schema() or when the route is used without the graphql_schema
96 | * parameter
97 | */
98 | 'schema' => 'default',
99 |
100 | /*
101 | * The schemas for query and/or mutation. It expects an array to provide
102 | * both the 'query' fields and the 'mutation' fields. You can also
103 | * provide an GraphQL\Type\Schema object directly.
104 | *
105 | * Example:
106 | *
107 | * 'schemas' => [
108 | * 'default' => new Schema($config)
109 | * ]
110 | *
111 | * or
112 | *
113 | * 'schemas' => [
114 | * 'default' => [
115 | * 'query' => [
116 | * 'users' => 'App\GraphQL\Query\UsersQuery'
117 | * ],
118 | * 'mutation' => [
119 | *
120 | * ]
121 | * ]
122 | * ]
123 | */
124 | 'schemas' => [
125 | 'default' => [
126 | 'query' => [
127 |
128 | ],
129 | 'mutation' => [
130 |
131 | ]
132 | ]
133 | ],
134 |
135 | /*
136 | * Additional resolvers which can also be used with shorthand building of the schema
137 | * using \GraphQL\Utils::BuildSchema feature
138 | *
139 | * Example:
140 | *
141 | * 'resolvers' => [
142 | * 'default' => [
143 | * 'echo' => function ($root, $args, $context) {
144 | * return 'Echo: ' . $args['message'];
145 | * },
146 | * ],
147 | * ],
148 | */
149 | 'resolvers' => [
150 | 'default' => [
151 | ],
152 | ],
153 |
154 | /*
155 | * Overrides the default field resolver
156 | * Useful to setup default loading of eager relationships
157 | *
158 | * Example:
159 | *
160 | * 'defaultFieldResolver' => function ($root, $args, $context, $info) {
161 | * // take a look at the defaultFieldResolver in
162 | * // https://github.com/webonyx/graphql-php/blob/master/src/Executor/Executor.php
163 | * },
164 | */
165 | 'defaultFieldResolver' => null,
166 |
167 | /*
168 | * The types available in the application. You can access them from the
169 | * facade like this: GraphQL::type('user')
170 | *
171 | * Example:
172 | *
173 | * 'types' => [
174 | * 'user' => 'App\GraphQL\Type\UserType'
175 | * ]
176 | *
177 | * or without specifying a key (it will use the ->name property of your type)
178 | *
179 | * 'types' =>
180 | * 'App\GraphQL\Type\UserType'
181 | * ]
182 | */
183 | 'types' => [
184 |
185 | ],
186 |
187 | /*
188 | * This callable will receive all the Exception objects that are caught by GraphQL.
189 | * The method should return an array representing the error.
190 | *
191 | * Typically:
192 | *
193 | * [
194 | * 'message' => '',
195 | * 'locations' => []
196 | * ]
197 | */
198 | 'error_formatter' => [\Folklore\GraphQL\GraphQL::class, 'formatError'],
199 |
200 | /*
201 | * Options to limit the query complexity and depth. See the doc
202 | * @ https://github.com/webonyx/graphql-php#security
203 | * for details. Disabled by default.
204 | */
205 | 'security' => [
206 | 'query_max_complexity' => null,
207 | 'query_max_depth' => null,
208 | 'disable_introspection' => false
209 | ]
210 | ];
211 |
--------------------------------------------------------------------------------
/src/resources/views/graphiql.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | Loading...
24 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/tests/ConfigTest.php:
--------------------------------------------------------------------------------
1 | set('graphql', [
13 |
14 | 'prefix' => 'graphql_test',
15 |
16 | 'routes' => [
17 | 'query' => 'query/{graphql_schema?}',
18 | 'mutation' => 'mutation/{graphql_schema?}'
19 | ],
20 |
21 | 'variables_input_name' => 'params',
22 |
23 | 'schema' => 'custom',
24 |
25 | 'schemas' => [
26 | 'default' => [
27 | 'query' => [
28 | 'examples' => ExamplesQuery::class,
29 | 'examplesContext' => ExamplesContextQuery::class,
30 | 'examplesRoot' => ExamplesRootQuery::class
31 | ],
32 | 'mutation' => [
33 | 'updateExample' => UpdateExampleMutation::class
34 | ]
35 | ],
36 | 'custom' => [
37 | 'query' => [
38 | 'examplesCustom' => ExamplesQuery::class
39 | ],
40 | 'mutation' => [
41 | 'updateExampleCustom' => UpdateExampleMutation::class
42 | ]
43 | ],
44 | 'shorthand' => BuildSchema::build('
45 | schema {
46 | query: ShorthandExample
47 | }
48 |
49 | type ShorthandExample {
50 | echo(message: String!): String!
51 | }
52 | '),
53 | ],
54 |
55 | 'resolvers' => [
56 | 'shorthand' => [
57 | 'echo' => function ($root, $args, $context) {
58 | return 'Echo: ' . $args['message'];
59 | },
60 | ],
61 | ],
62 |
63 | 'types' => [
64 | 'Example' => ExampleType::class,
65 | CustomExampleType::class
66 | ],
67 |
68 | 'security' => [
69 | 'query_max_complexity' => 1000,
70 | 'query_max_depth' => 10,
71 | ],
72 |
73 | ]);
74 | }
75 |
76 | public function testRouteQuery()
77 | {
78 | $response = $this->call('GET', '/graphql_test/query', [
79 | 'query' => $this->queries['examplesCustom']
80 | ]);
81 |
82 | $this->assertEquals($response->getStatusCode(), 200);
83 |
84 | $content = $response->getData(true);
85 | $this->assertArrayHasKey('data', $content);
86 | }
87 |
88 | public function testRouteMutation()
89 | {
90 | $response = $this->call('POST', '/graphql_test/mutation', [
91 | 'query' => $this->queries['updateExampleCustom']
92 | ]);
93 |
94 | $this->assertEquals($response->getStatusCode(), 200);
95 |
96 | $content = $response->getData(true);
97 | $this->assertArrayHasKey('data', $content);
98 | }
99 |
100 | public function testTypes()
101 | {
102 | $types = GraphQL::getTypes();
103 | $this->assertArrayHasKey('Example', $types);
104 | $this->assertArrayHasKey('CustomExample', $types);
105 | }
106 |
107 | public function testSchema()
108 | {
109 | $schema = GraphQL::schema();
110 | $schemaCustom = GraphQL::schema('custom');
111 |
112 | $this->assertEquals($schema, $schemaCustom);
113 | }
114 |
115 | public function testSchemas()
116 | {
117 | $schemas = GraphQL::getSchemas();
118 |
119 | $this->assertArrayHasKey('default', $schemas);
120 | $this->assertArrayHasKey('custom', $schemas);
121 | $this->assertArrayHasKey('shorthand', $schemas);
122 | }
123 |
124 | public function testVariablesInputName()
125 | {
126 | $response = $this->call('GET', '/graphql_test/query/default', [
127 | 'query' => $this->queries['examplesWithVariables'],
128 | 'params' => [
129 | 'index' => 0
130 | ]
131 | ]);
132 |
133 | $this->assertEquals($response->getStatusCode(), 200);
134 |
135 | $content = $response->getData(true);
136 | $this->assertArrayHasKey('data', $content);
137 | $this->assertEquals($content['data'], [
138 | 'examples' => [
139 | $this->data[0]
140 | ]
141 | ]);
142 | }
143 |
144 | public function testVariablesInputNameForShorthandResolver()
145 | {
146 | $response = $this->call('GET', '/graphql_test/query/shorthand', [
147 | 'query' => $this->queries['shorthandExamplesWithVariables'],
148 | 'params' => [
149 | 'message' => 'Hello World!',
150 | ],
151 | ]);
152 |
153 | $this->assertEquals($response->getStatusCode(), 200);
154 |
155 | $content = $response->getData(true);
156 | $this->assertArrayHasKey('data', $content);
157 | $this->assertEquals($content['data'], [
158 | 'echo' => 'Echo: Hello World!',
159 | ]);
160 | }
161 |
162 | public function testSecurity()
163 | {
164 | $queryComplexity = DocumentValidator::getRule('QueryComplexity');
165 | $this->assertEquals(1000, $queryComplexity->getMaxQueryComplexity());
166 |
167 | $queryDepth = DocumentValidator::getRule('QueryDepth');
168 | $this->assertEquals(10, $queryDepth->getMaxQueryDepth());
169 | }
170 |
171 | public function testErrorFormatter()
172 | {
173 | $error = $this->getMockBuilder(ErrorFormatter::class)
174 | ->setMethods(['formatError'])
175 | ->getMock();
176 |
177 | $error->expects($this->once())
178 | ->method('formatError');
179 |
180 | config([
181 | 'graphql.error_formatter' => [$error, 'formatError']
182 | ]);
183 |
184 | $result = GraphQL::query($this->queries['examplesWithError']);
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/tests/EndpointTest.php:
--------------------------------------------------------------------------------
1 | call('GET', '/graphql', [
16 | 'query' => $this->queries['examples']
17 | ]);
18 |
19 | $this->assertEquals($response->getStatusCode(), 200);
20 |
21 | $content = $response->getData(true);
22 | $this->assertArrayHasKey('data', $content);
23 | $this->assertEquals($content['data'], [
24 | 'examples' => $this->data
25 | ]);
26 | }
27 |
28 | /**
29 | * Test get with custom schema
30 | *
31 | * @test
32 | */
33 | public function testGetCustom()
34 | {
35 | $response = $this->call('GET', '/graphql/custom', [
36 | 'query' => $this->queries['examplesCustom']
37 | ]);
38 |
39 | $content = $response->getData(true);
40 | $this->assertArrayHasKey('data', $content);
41 | $this->assertEquals($content['data'], [
42 | 'examplesCustom' => $this->data
43 | ]);
44 | }
45 |
46 | /**
47 | * Test get with variables
48 | *
49 | * @test
50 | */
51 | public function testGetWithVariables()
52 | {
53 | $response = $this->call('GET', '/graphql', [
54 | 'query' => $this->queries['examplesWithVariables'],
55 | 'variables' => [
56 | 'index' => 0
57 | ]
58 | ]);
59 |
60 | $this->assertEquals($response->getStatusCode(), 200);
61 |
62 | $content = $response->getData(true);
63 | $this->assertArrayHasKey('data', $content);
64 | $this->assertEquals($content['data'], [
65 | 'examples' => [
66 | $this->data[0]
67 | ]
68 | ]);
69 | }
70 |
71 | /**
72 | * Test get with unauthorized query
73 | *
74 | * @test
75 | */
76 | public function testGetUnauthorized()
77 | {
78 | $response = $this->call('GET', '/graphql', [
79 | 'query' => $this->queries['examplesWithAuthorize']
80 | ]);
81 |
82 | $this->assertEquals($response->getStatusCode(), 403);
83 |
84 | $content = $response->getData(true);
85 | $this->assertArrayHasKey('data', $content);
86 | $this->assertArrayHasKey('errors', $content);
87 | $this->assertNull($content['data']['examplesAuthorize']);
88 | }
89 |
90 | /**
91 | * Test support batched queries
92 | *
93 | * @test
94 | */
95 | public function testBatchedQueries() {
96 | $response = $this->call('GET', '/graphql', [
97 | [
98 | 'query' => $this->queries['examplesWithVariables'],
99 | 'variables' => [
100 | 'index' => 0
101 | ]
102 | ],
103 | [
104 | 'query' => $this->queries['examplesWithVariables'],
105 | 'variables' => [
106 | 'index' => 0
107 | ]
108 | ]
109 | ]);
110 |
111 | $this->assertEquals($response->getStatusCode(), 200);
112 |
113 | $content = $response->getData(true);
114 | $this->assertArrayHasKey(0, $content);
115 | $this->assertArrayHasKey(1, $content);
116 | $this->assertEquals($content[0]['data'], [
117 | 'examples' => [
118 | $this->data[0]
119 | ]
120 | ]);
121 | $this->assertEquals($content[1]['data'], [
122 | 'examples' => [
123 | $this->data[0]
124 | ]
125 | ]);
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/tests/EnumTypeTest.php:
--------------------------------------------------------------------------------
1 | toType();
18 |
19 | $this->assertInstanceOf(EnumType::class, $objectType);
20 |
21 | $this->assertEquals($objectType->name, $type->name);
22 |
23 | $typeValues = $type->getValues();
24 | $values = $objectType->getValues();
25 | $this->assertEquals(array_keys($typeValues)[0], $values[0]->name);
26 | $this->assertEquals($typeValues['TEST']['value'], $values[0]->value);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tests/FieldTest.php:
--------------------------------------------------------------------------------
1 | getFieldClass();
22 | $field = new $class();
23 | $attributes = $field->getAttributes();
24 |
25 | $this->assertArrayHasKey('name', $attributes);
26 | $this->assertArrayHasKey('type', $attributes);
27 | $this->assertArrayHasKey('args', $attributes);
28 | $this->assertArrayHasKey('resolve', $attributes);
29 | $this->assertInternalType('array', $attributes['args']);
30 | $this->assertInstanceOf(Closure::class, $attributes['resolve']);
31 | $this->assertInstanceOf(get_class($field->type()), $attributes['type']);
32 | }
33 |
34 | /**
35 | * Test resolve closure
36 | *
37 | * @test
38 | */
39 | public function testResolve()
40 | {
41 | $class = $this->getFieldClass();
42 | $field = $this->getMockBuilder($class)
43 | ->setMethods(['resolve'])
44 | ->getMock();
45 |
46 | $field->expects($this->once())
47 | ->method('resolve');
48 |
49 | $attributes = $field->getAttributes();
50 | $attributes['resolve'](null, [], [], null);
51 | }
52 |
53 | /**
54 | * Test to array
55 | *
56 | * @test
57 | */
58 | public function testToArray()
59 | {
60 | $class = $this->getFieldClass();
61 | $field = new $class();
62 | $array = $field->toArray();
63 |
64 | $this->assertInternalType('array', $array);
65 |
66 | $attributes = $field->getAttributes();
67 | $this->assertEquals($attributes, $array);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/tests/GraphQLQueryTest.php:
--------------------------------------------------------------------------------
1 | queries['examples']);
19 |
20 | $this->assertObjectHasAttribute('data', $result);
21 |
22 | $this->assertEquals($result->data, [
23 | 'examples' => $this->data
24 | ]);
25 | }
26 |
27 | /**
28 | * Test query methods
29 | *
30 | * @test
31 | */
32 | public function testQuery()
33 | {
34 | $resultArray = GraphQL::query($this->queries['examples']);
35 | $result = GraphQL::queryAndReturnResult($this->queries['examples']);
36 |
37 | $this->assertInternalType('array', $resultArray);
38 | $this->assertArrayHasKey('data', $resultArray);
39 | $this->assertEquals($resultArray['data'], $result->data);
40 | }
41 |
42 | /**
43 | * Test query with variables
44 | *
45 | * @test
46 | */
47 | public function testQueryAndReturnResultWithVariables()
48 | {
49 | $result = GraphQL::queryAndReturnResult($this->queries['examplesWithVariables'], [
50 | 'index' => 0
51 | ]);
52 |
53 | $this->assertObjectHasAttribute('data', $result);
54 | $this->assertCount(0, $result->errors);
55 | $this->assertEquals($result->data, [
56 | 'examples' => [
57 | $this->data[0]
58 | ]
59 | ]);
60 | }
61 |
62 | /**
63 | * Test query with initial root
64 | *
65 | * @test
66 | */
67 | public function testQueryAndReturnResultWithRoot()
68 | {
69 | $result = GraphQL::queryAndReturnResult($this->queries['examplesWithRoot'], null, [
70 | 'root' => [
71 | 'test' => 'root'
72 | ]
73 | ]);
74 |
75 | $this->assertObjectHasAttribute('data', $result);
76 | $this->assertCount(0, $result->errors);
77 | $this->assertEquals($result->data, [
78 | 'examplesRoot' => [
79 | 'test' => 'root'
80 | ]
81 | ]);
82 | }
83 |
84 | /**
85 | * Test query with context
86 | *
87 | * @test
88 | */
89 | public function testQueryAndReturnResultWithContext()
90 | {
91 | $result = GraphQL::queryAndReturnResult($this->queries['examplesWithContext'], null, [
92 | 'context' => [
93 | 'test' => 'context'
94 | ]
95 | ]);
96 | $this->assertObjectHasAttribute('data', $result);
97 | $this->assertCount(0, $result->errors);
98 | $this->assertEquals($result->data, [
99 | 'examplesContext' => [
100 | 'test' => 'context'
101 | ]
102 | ]);
103 | }
104 |
105 | /**
106 | * Test query with authorize
107 | *
108 | * @test
109 | */
110 | public function testQueryAndReturnResultWithAuthorize()
111 | {
112 | $result = GraphQL::query($this->queries['examplesWithAuthorize']);
113 | $this->assertNull($result['data']['examplesAuthorize']);
114 | $this->assertEquals('Unauthorized', $result['errors'][0]['message']);
115 | }
116 |
117 | /**
118 | * Test query with authorize
119 | *
120 | * @test
121 | */
122 | public function testQueryAndReturnResultWithAuthenticated()
123 | {
124 | $result = GraphQL::query($this->queries['examplesWithAuthenticated']);
125 | $this->assertNull($result['data']['examplesAuthenticated']);
126 | $this->assertEquals('Unauthenticated', $result['errors'][0]['message']);
127 | }
128 |
129 | /**
130 | * Test query with schema
131 | *
132 | * @test
133 | */
134 | public function testQueryAndReturnResultWithSchema()
135 | {
136 | $result = GraphQL::queryAndReturnResult($this->queries['examplesCustom'], null, [
137 | 'schema' => [
138 | 'query' => [
139 | 'examplesCustom' => ExamplesQuery::class
140 | ]
141 | ]
142 | ]);
143 |
144 | $this->assertObjectHasAttribute('data', $result);
145 | $this->assertCount(0, $result->errors);
146 | $this->assertEquals($result->data, [
147 | 'examplesCustom' => $this->data
148 | ]);
149 | }
150 |
151 | /**
152 | * Test query with error
153 | *
154 | * @test
155 | */
156 | public function testQueryWithError()
157 | {
158 | $result = GraphQL::query($this->queries['examplesWithError']);
159 |
160 | $this->assertArrayHasKey('data', $result);
161 | $this->assertArrayHasKey('errors', $result);
162 | $this->assertNull($result['data']);
163 | $this->assertCount(1, $result['errors']);
164 | $this->assertArrayHasKey('message', $result['errors'][0]);
165 | $this->assertArrayHasKey('locations', $result['errors'][0]);
166 | }
167 |
168 | /**
169 | * Test query with validation error
170 | *
171 | * @test
172 | */
173 | public function testQueryWithValidationError()
174 | {
175 | $result = GraphQL::query($this->queries['examplesWithValidation']);
176 |
177 | $this->assertArrayHasKey('data', $result);
178 | $this->assertArrayHasKey('errors', $result);
179 | $this->assertArrayHasKey('validation', $result['errors'][0]);
180 | $this->assertTrue($result['errors'][0]['validation']->has('index'));
181 | }
182 |
183 | /**
184 | * Test query with validation without error
185 | *
186 | * @test
187 | */
188 | public function testQueryWithValidation()
189 | {
190 | $result = GraphQL::query($this->queries['examplesWithValidation'], [
191 | 'index' => 0
192 | ]);
193 |
194 | $this->assertArrayHasKey('data', $result);
195 | $this->assertArrayNotHasKey('errors', $result);
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/tests/GraphQLTest.php:
--------------------------------------------------------------------------------
1 | assertGraphQLSchema($schema);
23 | $this->assertGraphQLSchemaHasQuery($schema, 'examples');
24 | $this->assertGraphQLSchemaHasMutation($schema, 'updateExample');
25 | $this->assertArrayHasKey('Example', $schema->getTypeMap());
26 | }
27 |
28 | /**
29 | * Test schema with object
30 | *
31 | * @test
32 | */
33 | public function testSchemaWithSchemaObject()
34 | {
35 | $schemaObject = new Schema([
36 | 'query' => new ObjectType([
37 | 'name' => 'Query'
38 | ]),
39 | 'mutation' => new ObjectType([
40 | 'name' => 'Mutation'
41 | ]),
42 | 'types' => []
43 | ]);
44 | $schema = GraphQL::schema($schemaObject);
45 |
46 | $this->assertGraphQLSchema($schema);
47 | $this->assertEquals($schemaObject, $schema);
48 | }
49 |
50 | /**
51 | * Test schema with name
52 | *
53 | * @test
54 | */
55 | public function testSchemaWithName()
56 | {
57 | $schema = GraphQL::schema('custom');
58 |
59 | $this->assertGraphQLSchema($schema);
60 | $this->assertGraphQLSchemaHasQuery($schema, 'examplesCustom');
61 | $this->assertGraphQLSchemaHasMutation($schema, 'updateExampleCustom');
62 | $this->assertArrayHasKey('Example', $schema->getTypeMap());
63 | }
64 |
65 | /**
66 | * Test schema custom
67 | *
68 | * @test
69 | */
70 | public function testSchemaWithArray()
71 | {
72 | $schema = GraphQL::schema([
73 | 'query' => [
74 | 'examplesCustom' => ExamplesQuery::class
75 | ],
76 | 'mutation' => [
77 | 'updateExampleCustom' => UpdateExampleMutation::class
78 | ],
79 | 'types' => [
80 | CustomExampleType::class
81 | ]
82 | ]);
83 |
84 | $this->assertGraphQLSchema($schema);
85 | $this->assertGraphQLSchemaHasQuery($schema, 'examplesCustom');
86 | $this->assertGraphQLSchemaHasMutation($schema, 'updateExampleCustom');
87 | $this->assertArrayHasKey('CustomExample', $schema->getTypeMap());
88 | }
89 |
90 | /**
91 | * Test schema with wrong name
92 | *
93 | * @test
94 | * @expectedException \Folklore\GraphQL\Exception\SchemaNotFound
95 | */
96 | public function testSchemaWithWrongName()
97 | {
98 | $schema = GraphQL::schema('wrong');
99 | }
100 |
101 | /**
102 | * Test type
103 | *
104 | * @test
105 | */
106 | public function testType()
107 | {
108 | $type = GraphQL::type('Example');
109 | $this->assertInstanceOf(\GraphQL\Type\Definition\ObjectType::class, $type);
110 |
111 | $typeOther = GraphQL::type('Example');
112 | $this->assertTrue($type === $typeOther);
113 |
114 | $typeOther = GraphQL::type('Example', true);
115 | $this->assertFalse($type === $typeOther);
116 | }
117 |
118 | /**
119 | * Test wrong type
120 | *
121 | * @test
122 | * @expectedException \Folklore\GraphQL\Exception\TypeNotFound
123 | */
124 | public function testWrongType()
125 | {
126 | $typeWrong = GraphQL::type('ExampleWrong');
127 | }
128 |
129 | /**
130 | * Test objectType
131 | *
132 | * @test
133 | */
134 | public function testObjectType()
135 | {
136 | $objectType = new ObjectType([
137 | 'name' => 'ObjectType'
138 | ]);
139 | $type = GraphQL::objectType($objectType, [
140 | 'name' => 'ExampleType'
141 | ]);
142 |
143 | $this->assertInstanceOf(\GraphQL\Type\Definition\ObjectType::class, $type);
144 | $this->assertEquals($objectType, $type);
145 | $this->assertEquals($type->name, 'ExampleType');
146 | }
147 |
148 | public function testObjectTypeFromFields()
149 | {
150 | $type = GraphQL::objectType([
151 | 'test' => [
152 | 'type' => Type::string(),
153 | 'description' => 'A test field'
154 | ]
155 | ], [
156 | 'name' => 'ExampleType'
157 | ]);
158 |
159 | $this->assertInstanceOf(\GraphQL\Type\Definition\ObjectType::class, $type);
160 | $this->assertEquals($type->name, 'ExampleType');
161 | $fields = $type->getFields();
162 | $this->assertArrayHasKey('test', $fields);
163 | }
164 |
165 | public function testObjectTypeClass()
166 | {
167 | $type = GraphQL::objectType(ExampleType::class, [
168 | 'name' => 'ExampleType'
169 | ]);
170 |
171 | $this->assertInstanceOf(\GraphQL\Type\Definition\ObjectType::class, $type);
172 | $this->assertEquals($type->name, 'ExampleType');
173 | $fields = $type->getFields();
174 | $this->assertArrayHasKey('test', $fields);
175 | }
176 |
177 | public function testFormatError()
178 | {
179 | $result = GraphQL::queryAndReturnResult($this->queries['examplesWithError']);
180 | $error = GraphQL::formatError($result->errors[0]);
181 |
182 | $this->assertInternalType('array', $error);
183 | $this->assertArrayHasKey('message', $error);
184 | $this->assertArrayHasKey('locations', $error);
185 | $this->assertEquals($error, [
186 | 'message' => 'Cannot query field "examplesQueryNotFound" on type "Query".',
187 | 'locations' => [
188 | [
189 | 'line' => 3,
190 | 'column' => 13
191 | ]
192 | ]
193 | ]);
194 | }
195 |
196 | public function testFormatValidationError()
197 | {
198 | $validator = Validator::make([], [
199 | 'test' => 'required'
200 | ]);
201 | $validator->fails();
202 | $validationError = with(new ValidationError('validation'))->setValidator($validator);
203 | $error = new Error('error', null, null, null, null, $validationError);
204 | $error = GraphQL::formatError($error);
205 |
206 | $this->assertInternalType('array', $error);
207 | $this->assertArrayHasKey('validation', $error);
208 | $this->assertTrue($error['validation']->has('test'));
209 | }
210 |
211 | /**
212 | * Test add type
213 | *
214 | * @test
215 | */
216 | public function testAddType()
217 | {
218 | $this->expectsEvents(TypeAdded::class);
219 |
220 | $this->app['events']->shouldReceive('listen');
221 |
222 | GraphQL::addType(CustomExampleType::class);
223 |
224 | $types = GraphQL::getTypes();
225 | $this->assertArrayHasKey('CustomExample', $types);
226 |
227 | $type = app($types['CustomExample']);
228 | $this->assertInstanceOf(CustomExampleType::class, $type);
229 |
230 | $type = GraphQL::type('CustomExample');
231 | $this->assertInstanceOf(\GraphQL\Type\Definition\ObjectType::class, $type);
232 | }
233 |
234 | /**
235 | * Test add type with a name
236 | *
237 | * @test
238 | */
239 | public function testAddTypeWithName()
240 | {
241 | GraphQL::addType(ExampleType::class, 'CustomExample');
242 |
243 | $types = GraphQL::getTypes();
244 | $this->assertArrayHasKey('CustomExample', $types);
245 |
246 | $type = app($types['CustomExample']);
247 | $this->assertInstanceOf(ExampleType::class, $type);
248 |
249 | $type = GraphQL::type('CustomExample');
250 | $this->assertInstanceOf(\GraphQL\Type\Definition\ObjectType::class, $type);
251 | }
252 |
253 | /**
254 | * Test get types
255 | *
256 | * @test
257 | */
258 | public function testGetTypes()
259 | {
260 | $types = GraphQL::getTypes();
261 | $this->assertArrayHasKey('Example', $types);
262 |
263 | $type = app($types['Example']);
264 | $this->assertInstanceOf(\Folklore\GraphQL\Support\Type::class, $type);
265 | }
266 |
267 | /**
268 | * Test add schema
269 | *
270 | * @test
271 | */
272 | public function testAddSchema()
273 | {
274 | $this->expectsEvents(SchemaAdded::class);
275 |
276 | $this->app['events']->shouldReceive('listen')->with(SchemaAdded::class, Closure::class)->once();
277 |
278 | GraphQL::addSchema('custom_add', [
279 | 'query' => [
280 | 'examplesCustom' => ExamplesQuery::class
281 | ],
282 | 'mutation' => [
283 | 'updateExampleCustom' => UpdateExampleMutation::class
284 | ],
285 | 'types' => [
286 | CustomExampleType::class
287 | ]
288 | ]);
289 |
290 | $schemas = GraphQL::getSchemas();
291 | $this->assertArrayHasKey('custom_add', $schemas);
292 | }
293 |
294 | /**
295 | * Test get schemas
296 | *
297 | * @test
298 | */
299 | public function testGetSchemas()
300 | {
301 | $schemas = GraphQL::getSchemas();
302 | $this->assertArrayHasKey('default', $schemas);
303 | $this->assertArrayHasKey('custom', $schemas);
304 | $this->assertInternalType('array', $schemas['default']);
305 | }
306 | }
307 |
--------------------------------------------------------------------------------
/tests/GraphiQLTest.php:
--------------------------------------------------------------------------------
1 | assertTrue(app('view')->exists('graphql::graphiql'));
15 | }
16 |
17 | /**
18 | * Test endpoint
19 | *
20 | * @test
21 | */
22 | public function testEndpoint()
23 | {
24 | $queryPath = route('graphql.query');
25 |
26 | $response = $this->call('GET', route('graphql.graphiql'));
27 | $this->assertEquals(200, $response->status());
28 | $this->assertEquals($queryPath, $response->original->graphqlPath);
29 | $content = $response->getContent();
30 | $this->assertContains($queryPath, $content);
31 | }
32 |
33 | /**
34 | * Test endpoint with custom schema
35 | *
36 | * @test
37 | */
38 | public function testEndpointWithSchema()
39 | {
40 | $queryPath = route('graphql.query', ['custom']);
41 | $response = $this->call('GET', route('graphql.graphiql', ['custom']));
42 | $this->assertEquals(200, $response->status());
43 | $this->assertEquals($queryPath, $response->original->graphqlPath);
44 | $content = $response->getContent();
45 | $this->assertContains($queryPath, $content);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/tests/InputTypeTest.php:
--------------------------------------------------------------------------------
1 | toType();
18 |
19 | $this->assertInstanceOf(InputObjectType::class, $objectType);
20 |
21 | $this->assertEquals($objectType->name, $type->name);
22 |
23 | $fields = $objectType->getFields();
24 | $this->assertArrayHasKey('test', $fields);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests/InterfaceTypeTest.php:
--------------------------------------------------------------------------------
1 | getAttributes();
18 |
19 | $this->assertArrayHasKey('resolveType', $attributes);
20 | $this->assertInstanceOf(Closure::class, $attributes['resolveType']);
21 | }
22 |
23 | /**
24 | * Test get attributes resolve type
25 | *
26 | * @test
27 | */
28 | public function testGetAttributesResolveType()
29 | {
30 | $type = $this->getMockBuilder(ExampleInterfaceType::class)
31 | ->setMethods(['resolveType'])
32 | ->getMock();
33 |
34 | $type->expects($this->once())
35 | ->method('resolveType');
36 |
37 | $attributes = $type->getAttributes();
38 | $attributes['resolveType'](null);
39 | }
40 |
41 | /**
42 | * Test to type
43 | *
44 | * @test
45 | */
46 | public function testToType()
47 | {
48 | $type = new ExampleInterfaceType();
49 | $interfaceType = $type->toType();
50 |
51 | $this->assertInstanceOf(InterfaceType::class, $interfaceType);
52 |
53 | $this->assertEquals($interfaceType->name, $type->name);
54 |
55 | $fields = $interfaceType->getFields();
56 | $this->assertArrayHasKey('test', $fields);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/tests/MutationTest.php:
--------------------------------------------------------------------------------
1 | set('graphql.types', [
17 | 'Example' => ExampleType::class,
18 | 'ExampleValidationInputObject' => ExampleValidationInputObject::class,
19 | 'ExampleNestedValidationInputObject' => ExampleNestedValidationInputObject::class,
20 | ]);
21 | }
22 |
23 | /**
24 | * Test get rules.
25 | *
26 | * @test
27 | */
28 | public function testGetRules()
29 | {
30 | $class = $this->getFieldClass();
31 | $field = new $class();
32 | $rules = $field->getRules();
33 |
34 | $this->assertInternalType('array', $rules);
35 | $this->assertArrayHasKey('test', $rules);
36 | $this->assertArrayHasKey('test_with_rules', $rules);
37 | $this->assertArrayHasKey('test_with_rules_closure', $rules);
38 | $this->assertEquals($rules['test'], ['required']);
39 | $this->assertEquals($rules['test_with_rules'], ['required']);
40 | $this->assertEquals($rules['test_with_rules_closure'], ['required']);
41 | $this->assertEquals($rules['test_with_rules_input_object'], ['required']);
42 | $this->assertEquals(array_get($rules, 'test_with_rules_input_object.val'), ['required']);
43 | $this->assertEquals(array_get($rules, 'test_with_rules_input_object.nest'), ['required']);
44 | $this->assertEquals(array_get($rules, 'test_with_rules_input_object.nest.email'), ['email']);
45 | $this->assertEquals(array_get($rules, 'test_with_rules_input_object.list'), ['required']);
46 | $this->assertEquals(array_get($rules, 'test_with_rules_input_object.list.*.email'), ['email']);
47 | }
48 |
49 | /**
50 | * Test resolve.
51 | *
52 | * @test
53 | */
54 | public function testResolve()
55 | {
56 | $class = $this->getFieldClass();
57 | $field = $this->getMockBuilder($class)
58 | ->setMethods(['resolve'])
59 | ->getMock();
60 |
61 | $field->expects($this->once())
62 | ->method('resolve');
63 |
64 | $attributes = $field->getAttributes();
65 | $attributes['resolve'](null, [
66 | 'test' => 'test',
67 | 'test_with_rules' => 'test',
68 | 'test_with_rules_closure' => 'test',
69 | 'test_with_rules_input_object' => [
70 | 'val' => 'test',
71 | 'nest' => ['email' => 'test@test.com'],
72 | 'list' => [
73 | ['email' => 'test@test.com'],
74 | ],
75 | ],
76 | ], [], null);
77 | }
78 |
79 | /**
80 | * Test resolve throw validation error.
81 | *
82 | * @test
83 | * @expectedException \Folklore\GraphQL\Error\ValidationError
84 | */
85 | public function testResolveThrowValidationError()
86 | {
87 | $class = $this->getFieldClass();
88 | $field = new $class();
89 |
90 | $attributes = $field->getAttributes();
91 | $attributes['resolve'](null, [], [], null);
92 | }
93 |
94 | /**
95 | * Test validation error.
96 | *
97 | * @test
98 | */
99 | public function testValidationError()
100 | {
101 | $class = $this->getFieldClass();
102 | $field = new $class();
103 |
104 | $attributes = $field->getAttributes();
105 |
106 | try {
107 | $attributes['resolve'](null, [], [], null);
108 | } catch (\Folklore\GraphQL\Error\ValidationError $e) {
109 | $validator = $e->getValidator();
110 |
111 | $this->assertInstanceOf(Validator::class, $validator);
112 |
113 | $messages = $e->getValidatorMessages();
114 | $this->assertTrue($messages->has('test'));
115 | $this->assertTrue($messages->has('test_with_rules'));
116 | $this->assertTrue($messages->has('test_with_rules_closure'));
117 | $this->assertTrue($messages->has('test_with_rules_input_object.val'));
118 | $this->assertTrue($messages->has('test_with_rules_input_object.nest'));
119 | $this->assertTrue($messages->has('test_with_rules_input_object.list'));
120 | }
121 | }
122 |
123 | /**
124 | * Test custom validation error messages.
125 | *
126 | * @test
127 | */
128 | public function testCustomValidationErrorMessages()
129 | {
130 | $class = $this->getFieldClass();
131 | $field = new $class();
132 | $rules = $field->getRules();
133 | $attributes = $field->getAttributes();
134 | try {
135 | $attributes['resolve'](null, [
136 | 'test_with_rules_input_object' => [
137 | 'nest' => ['email' => 'invalidTestEmail.com'],
138 | ],
139 | ], [], null);
140 | } catch (\Folklore\GraphQL\Error\ValidationError $e) {
141 | $messages = $e->getValidatorMessages();
142 |
143 | $this->assertEquals($messages->first('test'), 'A test is required.');
144 | $this->assertEquals($messages->first('test_with_rules_input_object.nest.email'), 'Invalid your email : invalidTestEmail.com');
145 | }
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/tests/Objects/CustomExampleType.php:
--------------------------------------------------------------------------------
1 | 'CustomExample',
11 | 'description' => 'An example'
12 | ];
13 |
14 | public function fields()
15 | {
16 | return [
17 | 'test' => [
18 | 'type' => Type::string(),
19 | 'description' => 'A test field'
20 | ]
21 | ];
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/tests/Objects/ErrorFormatter.php:
--------------------------------------------------------------------------------
1 | $e->getMessage()
12 | ];
13 |
14 | $locations = $e->getLocations();
15 | if (!empty($locations)) {
16 | $error['locations'] = array_map(function ($loc) {
17 | return $loc->toArray();
18 | }, $locations);
19 | }
20 |
21 | $previous = $e->getPrevious();
22 | if ($previous && $previous instanceof ValidationError) {
23 | $error['validation'] = $previous->getValidatorMessages();
24 | }
25 |
26 | return $error;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tests/Objects/ExampleEnumType.php:
--------------------------------------------------------------------------------
1 | 'ExampleEnum',
10 | 'description' => 'An example enum'
11 | ];
12 |
13 | public function values()
14 | {
15 | return [
16 | 'TEST' => [
17 | 'value' => 1,
18 | 'description' => 'test'
19 | ]
20 | ];
21 | }
22 |
23 | public function fields()
24 | {
25 | return [
26 | 'test' => [
27 | 'type' => Type::string(),
28 | 'description' => 'A test field'
29 | ],
30 | 'test_validation' => ExampleValidationField::class
31 | ];
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tests/Objects/ExampleField.php:
--------------------------------------------------------------------------------
1 | 'example'
10 | ];
11 |
12 | public function type()
13 | {
14 | return Type::listOf(Type::string());
15 | }
16 |
17 | public function args()
18 | {
19 | return [
20 | 'index' => [
21 | 'name' => 'index',
22 | 'type' => Type::int()
23 | ]
24 | ];
25 | }
26 |
27 | public function resolve($root, $args)
28 | {
29 | return ['test'];
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/Objects/ExampleInputType.php:
--------------------------------------------------------------------------------
1 | 'ExampleInput',
11 | 'description' => 'An example input'
12 | ];
13 |
14 | public function fields()
15 | {
16 | return [
17 | 'test' => [
18 | 'type' => Type::string(),
19 | 'description' => 'A test field'
20 | ],
21 | 'test_validation' => ExampleValidationField::class
22 | ];
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tests/Objects/ExampleInterfaceType.php:
--------------------------------------------------------------------------------
1 | 'ExampleInterface',
11 | 'description' => 'An example interface'
12 | ];
13 |
14 | public function resolveType($root)
15 | {
16 | return Type::string();
17 | }
18 |
19 | public function fields()
20 | {
21 | return [
22 | 'test' => [
23 | 'type' => Type::string(),
24 | 'description' => 'A test field'
25 | ]
26 | ];
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tests/Objects/ExampleNestedValidationInputObject.php:
--------------------------------------------------------------------------------
1 | 'ExampleNestedValidationInputObject'
13 | ];
14 |
15 | public function type()
16 | {
17 | return Type::listOf(Type::string());
18 | }
19 |
20 | public function fields()
21 | {
22 | return [
23 | 'email' => [
24 | 'name' => 'email',
25 | 'type' => Type::string(),
26 | 'rules' => ['email']
27 | ],
28 | ];
29 | }
30 |
31 | public function resolve($root, $args)
32 | {
33 | return ['test'];
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/tests/Objects/ExampleType.php:
--------------------------------------------------------------------------------
1 | 'Example',
11 | 'description' => 'An example'
12 | ];
13 |
14 | public function fields()
15 | {
16 | return [
17 | 'test' => [
18 | 'type' => Type::string(),
19 | 'description' => 'A test field'
20 | ],
21 | 'test_validation' => ExampleValidationField::class
22 | ];
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tests/Objects/ExampleUnionType.php:
--------------------------------------------------------------------------------
1 | 'ExampleUnion',
10 | 'description' => 'An example union'
11 | ];
12 |
13 | public function types()
14 | {
15 | return [
16 | GraphQL::type('Example')
17 | ];
18 | }
19 |
20 | public function resolveType($root)
21 | {
22 | return GraphQL::type('Example');
23 | }
24 |
25 | public function fields()
26 | {
27 | return [
28 | 'test' => [
29 | 'type' => Type::string(),
30 | 'description' => 'A test field'
31 | ],
32 | 'test_validation' => ExampleValidationField::class
33 | ];
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/tests/Objects/ExampleValidationField.php:
--------------------------------------------------------------------------------
1 | 'example_validation'
13 | ];
14 |
15 | public function type()
16 | {
17 | return Type::listOf(Type::string());
18 | }
19 |
20 | public function args()
21 | {
22 | return [
23 | 'index' => [
24 | 'name' => 'index',
25 | 'type' => Type::int(),
26 | 'rules' => ['required']
27 | ]
28 | ];
29 | }
30 |
31 | public function resolve($root, $args)
32 | {
33 | return ['test'];
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/tests/Objects/ExampleValidationInputObject.php:
--------------------------------------------------------------------------------
1 | 'ExampleValidationInputObject'
13 | ];
14 |
15 | public function type()
16 | {
17 | return Type::listOf(Type::string());
18 | }
19 |
20 | public function fields()
21 | {
22 | return [
23 | 'val' => [
24 | 'name' => 'val',
25 | 'type' => Type::int(),
26 | 'rules' => ['required']
27 | ],
28 | 'nest' => [
29 | 'name' => 'nest',
30 | 'type' => GraphQL::type('ExampleNestedValidationInputObject'),
31 | 'rules' => ['required']
32 | ],
33 | 'list' => [
34 | 'name' => 'list',
35 | 'type' => Type::listOf(GraphQL::type('ExampleNestedValidationInputObject')),
36 | 'rules' => ['required']
37 | ],
38 | ];
39 | }
40 |
41 | public function resolve($root, $args)
42 | {
43 | return ['test'];
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/tests/Objects/ExamplesAuthenticatedQuery.php:
--------------------------------------------------------------------------------
1 | 'Examples authenticate query'
10 | ];
11 |
12 | public function authenticated($root, $args, $context)
13 | {
14 | return false;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/Objects/ExamplesAuthorizeQuery.php:
--------------------------------------------------------------------------------
1 | 'Examples authorize query'
10 | ];
11 |
12 | public function authorize($root, $args)
13 | {
14 | return false;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/Objects/ExamplesContextQuery.php:
--------------------------------------------------------------------------------
1 | 'Examples context query'
11 | ];
12 |
13 | public function type()
14 | {
15 | return GraphQL::type('Example');
16 | }
17 |
18 | public function resolve($root, $args, $context)
19 | {
20 | return $context;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/Objects/ExamplesPaginationQuery.php:
--------------------------------------------------------------------------------
1 | 'Examples with pagination',
11 | ];
12 |
13 | public function type()
14 | {
15 | return GraphQL::pagination(GraphQL::type('Example'));
16 | }
17 |
18 | public function args()
19 | {
20 | return [
21 | 'take' => [
22 | 'type' => Type::nonNull(Type::int()),
23 | ],
24 | 'page' => [
25 | 'type' => Type::nonNull(Type::int()),
26 | ],
27 | ];
28 | }
29 |
30 | public function resolve($root, $args)
31 | {
32 | $data = include(__DIR__.'/data.php');
33 |
34 | $take = $args['take'];
35 | $page = $args['page'] - 1;
36 |
37 | return new LengthAwarePaginator(
38 | collect($data)->slice($page * $take, $take),
39 | count($data),
40 | $take,
41 | $page
42 | );
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/tests/Objects/ExamplesQuery.php:
--------------------------------------------------------------------------------
1 | 'examples'
10 | ];
11 |
12 | public function type()
13 | {
14 | return Type::listOf(GraphQL::type('Example'));
15 | }
16 |
17 | public function args()
18 | {
19 | return [
20 | 'index' => ['name' => 'index', 'type' => Type::int()]
21 | ];
22 | }
23 |
24 | public function resolve($root, $args)
25 | {
26 | $data = include(__DIR__.'/data.php');
27 |
28 | if (isset($args['index'])) {
29 | return [
30 | $data[$args['index']]
31 | ];
32 | }
33 |
34 | return $data;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/tests/Objects/ExamplesRootQuery.php:
--------------------------------------------------------------------------------
1 | 'Examples root query'
11 | ];
12 |
13 | public function type()
14 | {
15 | return GraphQL::type('Example');
16 | }
17 |
18 | public function resolve($root, $args, $context)
19 | {
20 | return $root;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/Objects/UpdateExampleMutation.php:
--------------------------------------------------------------------------------
1 | 'updateExample'
12 | ];
13 |
14 | public function type()
15 | {
16 | return GraphQL::type('Example');
17 | }
18 |
19 | public function rules()
20 | {
21 | return [
22 | 'test' => ['required']
23 | ];
24 | }
25 |
26 | public function args()
27 | {
28 | return [
29 | 'test' => [
30 | 'name' => 'test',
31 | 'type' => Type::string()
32 | ],
33 |
34 | 'test_with_rules' => [
35 | 'name' => 'test',
36 | 'type' => Type::string(),
37 | 'rules' => ['required']
38 | ],
39 |
40 | 'test_with_rules_closure' => [
41 | 'name' => 'test',
42 | 'type' => Type::string(),
43 | 'rules' => function () {
44 | return ['required'];
45 | }
46 | ],
47 | ];
48 | }
49 |
50 | public function resolve($root, $args)
51 | {
52 | return [
53 | 'test' => array_get($args, 'test')
54 | ];
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/tests/Objects/UpdateExampleMutationWithInputType.php:
--------------------------------------------------------------------------------
1 | 'updateExample',
11 | ];
12 |
13 | public function type()
14 | {
15 | return GraphQL::type('Example');
16 | }
17 |
18 | public function rules()
19 | {
20 | return [
21 | 'test' => ['required'],
22 | ];
23 | }
24 |
25 | public function validationErrorMessages($root, $args, $context)
26 | {
27 | $inavlidEmail = array_get($args, 'test_with_rules_input_object.nest.email');
28 |
29 | return [
30 | 'test.required' => 'A test is required.',
31 | 'test_with_rules_input_object.nest.email.email' => 'Invalid your email : '.$inavlidEmail,
32 | ];
33 | }
34 |
35 | public function args()
36 | {
37 | return [
38 | 'test' => [
39 | 'name' => 'test',
40 | 'type' => Type::string(),
41 | ],
42 |
43 | 'test_with_rules' => [
44 | 'name' => 'test',
45 | 'type' => Type::string(),
46 | 'rules' => ['required'],
47 | ],
48 |
49 | 'test_with_rules_closure' => [
50 | 'name' => 'test',
51 | 'type' => Type::string(),
52 | 'rules' => function () {
53 | return ['required'];
54 | },
55 | ],
56 |
57 | 'test_with_rules_input_object' => [
58 | 'name' => 'test',
59 | 'type' => GraphQL::type('ExampleValidationInputObject'),
60 | 'rules' => ['required'],
61 | ],
62 | ];
63 | }
64 |
65 | public function resolve($root, $args)
66 | {
67 | return [
68 | 'test' => array_get($args, 'test'),
69 | ];
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/tests/Objects/data.php:
--------------------------------------------------------------------------------
1 | 'Example 1'
6 | ],
7 | [
8 | 'test' => 'Example 2'
9 | ],
10 | [
11 | 'test' => 'Example 3'
12 | ]
13 | ];
14 |
--------------------------------------------------------------------------------
/tests/Objects/queries.php:
--------------------------------------------------------------------------------
1 | "
7 | query QueryExamples {
8 | examples {
9 | test
10 | }
11 | }
12 | ",
13 |
14 | 'examplesCustom' => "
15 | query QueryExamplesCustom {
16 | examplesCustom {
17 | test
18 | }
19 | }
20 | ",
21 |
22 | 'examplesWithVariables' => "
23 | query QueryExamplesVariables(\$index: Int) {
24 | examples(index: \$index) {
25 | test
26 | }
27 | }
28 | ",
29 |
30 | 'shorthandExamplesWithVariables' => "
31 | query QueryShorthandExamplesVariables(\$message: String!) {
32 | echo(message: \$message)
33 | }
34 | ",
35 |
36 | 'examplesWithContext' => "
37 | query QueryExamplesContext {
38 | examplesContext {
39 | test
40 | }
41 | }
42 | ",
43 |
44 | 'examplesWithAuthorize' => "
45 | query QueryExamplesAuthorize {
46 | examplesAuthorize {
47 | test
48 | }
49 | }
50 | ",
51 |
52 | 'examplesWithAuthenticated' => "
53 | query QueryExamplesAuthenticated {
54 | examplesAuthenticated {
55 | test
56 | }
57 | }
58 | ",
59 |
60 | 'examplesWithRoot' => "
61 | query QueryExamplesRoot {
62 | examplesRoot {
63 | test
64 | }
65 | }
66 | ",
67 |
68 | 'examplesWithError' => "
69 | query QueryExamplesWithError {
70 | examplesQueryNotFound {
71 | test
72 | }
73 | }
74 | ",
75 |
76 | 'examplesWithValidation' => "
77 | query QueryExamplesWithValidation(\$index: Int) {
78 | examples {
79 | test_validation(index: \$index)
80 | }
81 | }
82 | ",
83 |
84 | 'updateExampleCustom' => "
85 | mutation UpdateExampleCustom(\$test: String) {
86 | updateExampleCustom(test: \$test) {
87 | test
88 | }
89 | }
90 | ",
91 |
92 | 'examplePagination' => "
93 | query Items(\$take: Int!, \$page: Int!) {
94 | examplesPagination(take: \$take, page: \$page) {
95 | items {
96 | test
97 | }
98 | cursor {
99 | total
100 | perPage
101 | currentPage
102 | hasPages
103 | }
104 | }
105 | }
106 | ",
107 |
108 | ];
109 |
--------------------------------------------------------------------------------
/tests/PaginationTest.php:
--------------------------------------------------------------------------------
1 | queries['examplePagination'], [
19 | 'take' => $take,
20 | 'page' => $page,
21 | ]);
22 |
23 | // Assert
24 | $items = $result['data']['examplesPagination']['items'];
25 | $cursor = $result['data']['examplesPagination']['cursor'];
26 |
27 | $this->assertEquals($cursor['total'], count($this->data));
28 | $this->assertEquals($cursor['perPage'], $take);
29 | $this->assertEquals($cursor['currentPage'], $page);
30 | $this->assertEquals($cursor['hasPages'], count($this->data) > $take);
31 |
32 | $this->assertEquals(count($items), $take);
33 | $this->assertEquals($items[0]['test'], $this->data[0]['test']);
34 | $this->assertEquals($items[1]['test'], $this->data[1]['test']);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/tests/QueryTest.php:
--------------------------------------------------------------------------------
1 | set('graphql.types', [
19 | 'Example' => ExampleType::class
20 | ]);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | queries = include(__DIR__.'/Objects/queries.php');
18 | $this->data = include(__DIR__.'/Objects/data.php');
19 | }
20 |
21 | protected function getEnvironmentSetUp($app)
22 | {
23 | $app['config']->set('graphql.schemas.default', [
24 | 'query' => [
25 | 'examples' => ExamplesQuery::class,
26 | 'examplesContext' => ExamplesContextQuery::class,
27 | 'examplesRoot' => ExamplesRootQuery::class,
28 | 'examplesAuthorize' => ExamplesAuthorizeQuery::class,
29 | 'examplesAuthenticated' => ExamplesAuthenticatedQuery::class,
30 | 'examplesPagination' => ExamplesPaginationQuery::class,
31 | ],
32 | 'mutation' => [
33 | 'updateExample' => UpdateExampleMutation::class
34 | ]
35 | ]);
36 |
37 | $app['config']->set('graphql.schemas.custom', [
38 | 'query' => [
39 | 'examplesCustom' => ExamplesQuery::class,
40 | ],
41 | 'mutation' => [
42 | 'updateExampleCustom' => UpdateExampleMutation::class
43 | ]
44 | ]);
45 |
46 | $app['config']->set('graphql.types', [
47 | 'Example' => ExampleType::class
48 | ]);
49 | }
50 |
51 | protected function assertGraphQLSchema($schema)
52 | {
53 | $this->assertInstanceOf('GraphQL\Type\Schema', $schema);
54 | }
55 |
56 | protected function assertGraphQLSchemaHasQuery($schema, $key)
57 | {
58 | //Query
59 | $query = $schema->getQueryType();
60 | $queryFields = $query->getFields();
61 | $this->assertArrayHasKey($key, $queryFields);
62 |
63 | $queryField = $queryFields[$key];
64 | $queryListType = $queryField->getType();
65 | $queryType = $queryListType->getWrappedType();
66 | $this->assertInstanceOf('GraphQL\Type\Definition\FieldDefinition', $queryField);
67 | $this->assertInstanceOf('GraphQL\Type\Definition\ListOfType', $queryListType);
68 | $this->assertInstanceOf('GraphQL\Type\Definition\ObjectType', $queryType);
69 | }
70 |
71 | protected function assertGraphQLSchemaHasMutation($schema, $key)
72 | {
73 | //Mutation
74 | $mutation = $schema->getMutationType();
75 | $mutationFields = $mutation->getFields();
76 | $this->assertArrayHasKey($key, $mutationFields);
77 |
78 | $mutationField = $mutationFields[$key];
79 | $mutationType = $mutationField->getType();
80 | $this->assertInstanceOf('GraphQL\Type\Definition\FieldDefinition', $mutationField);
81 | $this->assertInstanceOf('GraphQL\Type\Definition\ObjectType', $mutationType);
82 | }
83 |
84 | protected function getPackageProviders($app)
85 | {
86 | return [
87 | \Folklore\GraphQL\ServiceProvider::class
88 | ];
89 | }
90 |
91 | protected function getPackageAliases($app)
92 | {
93 | return [
94 | 'GraphQL' => \Folklore\GraphQL\Support\Facades\GraphQL::class
95 | ];
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/tests/TypeTest.php:
--------------------------------------------------------------------------------
1 | getFields();
18 |
19 | $this->assertArrayHasKey('test', $fields);
20 | $this->assertEquals($fields['test'], [
21 | 'type' => Type::string(),
22 | 'description' => 'A test field'
23 | ]);
24 | }
25 |
26 | /**
27 | * Test get attributes
28 | *
29 | * @test
30 | */
31 | public function testGetAttributes()
32 | {
33 | $type = new ExampleType();
34 | $attributes = $type->getAttributes();
35 |
36 | $this->assertArrayHasKey('name', $attributes);
37 | $this->assertArrayHasKey('fields', $attributes);
38 | $this->assertInstanceOf(Closure::class, $attributes['fields']);
39 | $this->assertInternalType('array', $attributes['fields']());
40 | }
41 |
42 | /**
43 | * Test get attributes fields closure
44 | *
45 | * @test
46 | */
47 | public function testGetAttributesFields()
48 | {
49 | $type = $this->getMockBuilder(ExampleType::class)
50 | ->setMethods(['getFields'])
51 | ->getMock();
52 |
53 | $type->expects($this->once())
54 | ->method('getFields');
55 |
56 | $attributes = $type->getAttributes();
57 | $attributes['fields']();
58 | }
59 |
60 | /**
61 | * Test to array
62 | *
63 | * @test
64 | */
65 | public function testToArray()
66 | {
67 | $type = new ExampleType();
68 | $array = $type->toArray();
69 |
70 | $this->assertInternalType('array', $array);
71 |
72 | $attributes = $type->getAttributes();
73 | $this->assertEquals($attributes, $array);
74 | }
75 |
76 | /**
77 | * Test to type
78 | *
79 | * @test
80 | */
81 | public function testToType()
82 | {
83 | $type = new ExampleType();
84 | $objectType = $type->toType();
85 |
86 | $this->assertInstanceOf(ObjectType::class, $objectType);
87 |
88 | $this->assertEquals($objectType->name, $type->name);
89 |
90 | $fields = $objectType->getFields();
91 | $this->assertArrayHasKey('test', $fields);
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/tests/UnionTypeTest.php:
--------------------------------------------------------------------------------
1 | set('graphql.types', [
14 | 'Example' => ExampleType::class,
15 | 'CustomExample' => ExampleType::class
16 | ]);
17 | }
18 |
19 | /**
20 | * Test get attributes
21 | *
22 | * @test
23 | */
24 | public function testGetAttributes()
25 | {
26 | $type = new ExampleUnionType();
27 | $attributes = $type->getAttributes();
28 |
29 | $this->assertArrayHasKey('resolveType', $attributes);
30 | $this->assertInstanceOf(Closure::class, $attributes['resolveType']);
31 | }
32 |
33 | /**
34 | * Test get attributes resolve type
35 | *
36 | * @test
37 | */
38 | public function testGetAttributesResolveType()
39 | {
40 | $type = $this->getMockBuilder(ExampleUnionType::class)
41 | ->setMethods(['resolveType'])
42 | ->getMock();
43 |
44 | $type->expects($this->once())
45 | ->method('resolveType');
46 |
47 | $attributes = $type->getAttributes();
48 | $attributes['resolveType'](null);
49 | }
50 |
51 | /**
52 | * Test to type
53 | *
54 | * @test
55 | */
56 | public function testGetTypes()
57 | {
58 | $type = new ExampleUnionType();
59 | $typeTypes = $type->getTypes();
60 | $types = [
61 | GraphQL::type('Example')
62 | ];
63 | $this->assertEquals($typeTypes, $types);
64 |
65 | $type = new ExampleUnionType();
66 | $types = [
67 | GraphQL::type('CustomExample')
68 | ];
69 | $type->types = $types;
70 | $typeTypes = $type->getTypes();
71 | $this->assertEquals($typeTypes, $types);
72 | }
73 |
74 | /**
75 | * Test to type
76 | *
77 | * @test
78 | */
79 | public function testToType()
80 | {
81 | $type = new ExampleUnionType();
82 | $objectType = $type->toType();
83 |
84 | $this->assertInstanceOf(UnionType::class, $objectType);
85 |
86 | $this->assertEquals($objectType->name, $type->name);
87 |
88 | $typeTypes = $type->getTypes();
89 | $types = $objectType->getTypes();
90 | $this->assertEquals($typeTypes, $types);
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/tests/fixture/app/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/tests/fixture/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "laravel/laravel",
3 | "description": "The Laravel Framework.",
4 | "keywords": ["framework", "laravel"],
5 | "license": "MIT",
6 | "type": "project",
7 | "require": {
8 | "laravel/framework": "~5.0"
9 | },
10 | "require-dev": {
11 | "phpunit/phpunit": "~4.0"
12 | },
13 | "autoload": {
14 | "classmap": [
15 | "database",
16 | "tests/TestCase.php"
17 | ],
18 | "psr-4": {
19 | "App\\": "app/"
20 | }
21 | },
22 | "minimum-stability": "dev"
23 | }
24 |
--------------------------------------------------------------------------------
/tests/logs/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------