├── .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 | [![Latest Stable Version](https://poser.pugx.org/folklore/graphql/v/stable.svg)](https://packagist.org/packages/folklore/graphql) 14 | [![Build Status](https://travis-ci.org/Folkloreatelier/laravel-graphql.png?branch=master)](https://travis-ci.org/Folkloreatelier/laravel-graphql) 15 | [![Total Downloads](https://poser.pugx.org/folklore/graphql/downloads.svg)](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 | --------------------------------------------------------------------------------