Compare commits

...

158 commits

Author SHA1 Message Date
Vladimir Razuvaev
b72ba3c93a
Merge pull request #518 from simPod/fix-internal-directives
Fix internal directives
2019-07-13 21:12:53 +07:00
Simon Podlipsky
1ac5af1d8b Fix internal directives 2019-07-12 21:01:10 +03:00
Vladimir Razuvaev
a01b089058
Merge pull request #517 from simPod/upgrade-phpstan
Upgrade PHPStan to 0.11.12
2019-07-12 16:35:34 +07:00
Vladimir Razuvaev
4401f4dd18
Merge pull request #504 from simPod/fielddef-types
Add FieldDefinition return type
2019-07-12 16:32:45 +07:00
Vladimir Razuvaev
005b1a38c5
Merge pull request #502 from simPod/drop-deprecated-schema
Drop deprecated GraphQL\Schema
2019-07-12 16:31:50 +07:00
Vladimir Razuvaev
974258b352
Merge pull request #503 from simPod/fix-reference-executor
Fix ReferenceExecutor
2019-07-12 16:31:29 +07:00
Simon Podlipsky
a502c33254 Upgrade PHPStan to 0.11.12 2019-07-10 18:05:47 +03:00
Vladimir Razuvaev
22a0da9b98
Merge pull request #505 from spawnia/root-value-consistent-naming
Consistently name the first resolver argument and the root value
2019-07-03 17:33:20 +07:00
Benedikt Franke
8c4e7b178d Fix up some overly eager renamings in the docs 2019-07-01 12:17:04 +02:00
Benedikt Franke
3b33167c87 Rename $rootValue where applicable 2019-07-01 12:12:01 +02:00
Benedikt Franke
d037ab7ec3 Merge branch 'master' into root-value-consistent-naming
# Conflicts:
#	docs/data-fetching.md
#	src/Executor/Executor.php
#	tests/Executor/DeferredFieldsTest.php
2019-07-01 08:23:00 +02:00
Vladimir Razuvaev
54064b37b3
Merge pull request #511 from spawnia/scalar-coercion
Resolve todo in Boolean coercion, add explanation, update test names to match reference implementation
2019-07-01 13:03:03 +07:00
Vladimir Razuvaev
f7443b6f0c
Merge pull request #510 from spawnia/split-into-multiline
Split some long lines into multiples
2019-07-01 12:56:45 +07:00
Vladimir Razuvaev
9704baf422
Merge pull request #508 from spawnia/consistency-and-clarity
Nonfunctional changes to improve consistency and clarity
2019-07-01 12:52:21 +07:00
Vladimir Razuvaev
ed1d835bd5
Merge pull request #507 from spawnia/resolver-arguments-order
Fix misleading comments and docs about resolver arguments: Context is 3rd, ResolveInfo is 4th
2019-07-01 12:50:01 +07:00
Vladimir Razuvaev
3b27abafca
Merge pull request #506 from spawnia/multiline-ternary
Spread ternary expressions across multiple lines
2019-07-01 12:49:03 +07:00
spawnia
c069d20ca7 And a few more in tests 2019-06-30 23:09:32 +02:00
spawnia
19a37609f4 Add a few more 2019-06-30 23:08:24 +02:00
spawnia
99453076b5 Remove &$ in phpdoc 2019-06-30 20:57:08 +02:00
spawnia
24f236403a More consistent naming 2019-06-30 20:54:56 +02:00
spawnia
8da3043702 Rename some test variable names 2019-06-23 21:26:38 +02:00
spawnia
a222cc9137 Reword a comment 2019-06-23 21:08:49 +02:00
spawnia
e704f8cc5c Split some long lines into multiples 2019-06-23 21:05:58 +02:00
spawnia
a34bb68d65 Resolve todo in Boolean coercion, add explanation, update test names to match reference implementation 2019-06-23 21:03:17 +02:00
spawnia
218e02a88c Revert composer.json changes 2019-06-23 18:46:27 +02:00
spawnia
9ca7bb6ea1 Expand one letter variable names 2019-06-23 18:42:25 +02:00
spawnia
65e4488ce8 Reformat some comments 2019-06-23 18:41:47 +02:00
spawnia
bc66034f40 Rename parameters and private fields to match what they contain 2019-06-23 18:40:56 +02:00
spawnia
65a3a8d13e Add missing renaming in ReferenceExecutor 2019-06-23 18:36:07 +02:00
spawnia
8381f67bd8 Add one more 2019-06-23 18:34:19 +02:00
spawnia
91b72f145d Context is the 3rd, ResolveInfo the 4th resolver argument 2019-06-23 18:25:02 +02:00
spawnia
6e91e2181c Spread ternary expressions across multiple lines 2019-06-23 18:04:30 +02:00
spawnia
03c33c9dc2 Consistently name the $rootValue argument 2019-06-23 17:30:57 +02:00
Vladimir Razuvaev
93ccd7351d Array in variables in place of object shouldn't cause fatal error (fixes #467) 2019-06-19 19:29:03 +07:00
Vladimir Razuvaev
ed1746e800 Error handling and schema validation improvements (#404) 2019-06-19 18:59:51 +07:00
Vladimir Razuvaev
84a52c6c76 Provide a path with a correct list index to resolveType callback of the union and interface types (fixes #396) 2019-06-19 17:11:24 +07:00
Vladimir Razuvaev
261f8f5ebd AST: Replaced array with NodeList where missing 2019-06-19 15:52:16 +07:00
Vladimir Razuvaev
d1d4455eaa Fixed warning added by one of the previous commits 2019-06-19 15:49:22 +07:00
Vladimir Razuvaev
d5fbf1b29f Merge branch 'master' of https://github.com/webonyx/graphql-php 2019-06-19 15:09:05 +07:00
Vladimir Razuvaev
c336d01bd2 Added .idea to .gitignore 2019-06-19 15:08:51 +07:00
Vladimir Razuvaev
752010b341 Documentation fix (#499) 2019-06-19 15:06:45 +07:00
Simon Podlipsky
368a9ee2f7 Add FieldDefinition return type 2019-06-17 14:54:05 +02:00
Simon Podlipsky
6086792824 Fix ReferenceExecutor 2019-06-17 14:18:12 +02:00
Simon Podlipsky
d259a303d3 Drop deprecated GraphQL\Schema 2019-06-17 14:15:46 +02:00
Vladimir Razuvaev
0a71f9fba9
Merge pull request #496 from simPod/fixes
Another code cleanup
2019-06-17 16:48:04 +07:00
Vladimir Razuvaev
61453a4f0b
Merge pull request #495 from spyl94/query-plan-should-work-on-interfaces
QueryPlan can now be used on interfaces not only objects.
2019-06-17 16:46:45 +07:00
Simon Podlipsky
6a5325a448 Another code cleanup 2019-06-12 15:43:17 +02:00
Aurélien David
e87460880c
QueryPlan can now be used on interfaces not only objects.
It's often the case to use interfaces in queries:

interface Pet { name: String! }

Query {
 pets: [Pet]
}
2019-06-12 11:59:42 +02:00
Vladimir Razuvaev
173a4297d9
Merge pull request #494 from simPod/cleanup-warning
Cleanup Warning
2019-06-12 16:40:02 +07:00
Vladimir Razuvaev
6f6a39468c
Merge pull request #493 from simPod/upgrade-phpstan
Upgrade PHPStan
2019-06-12 16:03:40 +07:00
Simon Podlipsky
a22a083220 Cleanup Warning 2019-06-12 11:03:14 +02:00
Vladimir Razuvaev
acc0442152 Merged branch 0.13.x into master 2019-06-12 15:45:44 +07:00
Simon Podlipsky
21592f8f28 Upgrade PHPStan 2019-06-12 10:22:18 +02:00
Vladimir Razuvaev
e17f578842
Merge pull request #492 from spawnia/circular-references
Add schema validation: Input Objects must not contain non-nullable circular references
2019-06-12 14:55:05 +07:00
Vladimir Razuvaev
6d9275e6bc
Merge pull request #474 from theofidry/patch-1
Ignore examples directory
2019-06-12 14:50:02 +07:00
Vladimir Razuvaev
747cb49ae3
Merge pull request #364 from simPod/instanceof
Replace node kind checks by InstanceOf checks
2019-06-12 14:48:50 +07:00
Vladimir Razuvaev
cdcf5b4473 v0.13.5 2019-06-12 14:16:37 +07:00
spawnia
692d10c127 Add Unreleases section to the Changelog 2019-06-10 22:30:25 +02:00
spawnia
6c82b85e79 Revert "Add changelog entry"
This reverts commit e5528d14
2019-06-10 22:24:54 +02:00
spawnia
e5528d14ab Add changelog entry 2019-06-10 22:23:06 +02:00
spawnia
c9faa3489b Add schema validation: Input Objects must not contain non-nullable circular references
Spec change: https://github.com/graphql/graphql-spec/pull/445
Reference implementation: https://github.com/graphql/graphql-js/pull/1359
2019-06-10 22:15:23 +02:00
Vladimir Razuvaev
bd02ccd47e
Merge pull request #486 from mcg-web/fix-coroutine-when-using-promise
Fix coroutine executor when using with promise
2019-06-06 16:50:40 +07:00
Jeremiah VALERIE
08d9493b2c
Fix coroutine executor when using with promise 2019-06-02 20:49:10 +02:00
Jeremiah VALERIE
0b4b1485e0
Add a test to cover promises 2019-06-02 20:49:10 +02:00
Vladimir Razuvaev
387f416984
Merge pull request #483 from mfn/patch-1
Add Laravel GraphQL implementation
2019-05-29 18:06:17 +07:00
Markus Podar
cf90a8d338
Add Laravel GraphQL implementation
It's a spiritual success to the archived https://github.com/Folkloreatelier/laravel-graphql project which was previously removed in https://github.com/webonyx/graphql-php/pull/455
2019-05-28 22:15:29 +02:00
Vladimir Razuvaev
2fd21dd231
Merge pull request #479 from stefsic/stefsic-readme-typo
Update README.md
2019-05-23 16:50:35 +07:00
stefania
2173bb9696
Update README.md
Fix "LICENCE" typo
2019-05-22 15:27:40 -04:00
Vladimir Razuvaev
e55c7d72cb v0.13.4 2019-05-13 14:12:05 +07:00
Vladimir Razuvaev
ef9a24b01f
Merge pull request #477 from mcg-web/max-query-int-forced
Force int when setting max query depth
2019-05-13 14:10:33 +07:00
Jeremiah VALERIE
40ac5ed269
Force int when setting max query depth 2019-05-13 07:50:47 +02:00
Vladimir Razuvaev
27539d5af0 v0.13.3 2019-05-11 14:48:12 +07:00
Vladimir Razuvaev
58d86abaf7
Merge pull request #476 from mcg-web/fix-bc
Fix BC
2019-05-11 11:44:23 +07:00
Jeremiah VALERIE
c803c455b4
Fix bc 2019-05-10 14:22:06 +02:00
Vladimir Razuvaev
9fc4d11425 v0.13.2 2019-05-10 13:03:54 +07:00
Vladimir Razuvaev
d0ab4dc8d8 Merge branch 'master' into 0.13.x
# Conflicts:
#	composer.json
#	src/Server/OperationParams.php
#	src/Type/Definition/EnumType.php
#	src/Type/Definition/InputObjectType.php
#	src/Type/Definition/InterfaceType.php
#	src/Type/Definition/ListOfType.php
#	src/Type/Definition/ResolveInfo.php
#	src/Type/Definition/UnionType.php
#	src/Validator/Rules/ValuesOfCorrectType.php
#	tests/Utils/SchemaExtenderTest.php
2019-05-10 12:19:45 +07:00
Vladimir Razuvaev
231919fbb2 Merge branch 'master' of https://github.com/webonyx/graphql-php 2019-05-10 12:03:22 +07:00
Vladimir Razuvaev
22e3b0e981
Merge pull request #475 from mll-lab/nodelist-offsets
Use foreach for iterating over NodeList
2019-05-10 11:57:44 +07:00
Benedikt Franke
019ed04a51
Update src/Language/AST/NodeList.php
Co-Authored-By: Šimon Podlipský <simon@podlipsky.net>
2019-05-09 19:33:37 +02:00
spawnia
e5b955ecc8 Use array_keys for iterating over NodeList 2019-05-09 17:04:17 +02:00
Théo FIDRY
e94db8a045
Update .gitattributes 2019-05-08 17:28:01 +02:00
Vladimir Razuvaev
49b7aced87
Merge pull request #472 from terabaud/patch-1
Documentation fix: add missing semicolon in generated cache file
2019-05-06 12:21:57 +07:00
Vladimir Razuvaev
0bd13d1828
Merge pull request #469 from simPod/fix-batch-query
Fix invalid query in docs
2019-05-06 12:17:05 +07:00
Lea Rosema
179944495e
fix: add missing semicolon in generated cache file
The `require $cacheFilename` statement leads to a PHP error due to a missing semicolon in the generated cache file.
2019-05-03 15:15:22 +02:00
Simon Podlipsky
0308cf0c0c Fix invalid query in docs 2019-05-03 00:56:11 +02:00
Vladimir Razuvaev
11d32d4568
Merge pull request #465 from mll-lab/enum-default-values
Enum default values
2019-03-29 15:41:12 +07:00
spawnia
db915d8812 Fix codestyle 2019-03-29 09:27:33 +01:00
Vladimir Razuvaev
5821caa249
Merge pull request #466 from simPod/upgrade-doctrine-cs
Upgrade Doctrine CS
2019-03-29 15:14:04 +07:00
Vladimir Razuvaev
8435c3111e
Merge pull request #464 from mll-lab/contributing
Clarify some bits of CONTRIBUTING.md and unify formatting
2019-03-28 19:09:04 +07:00
Vladimir Razuvaev
edec095055
Update README.md 2019-03-28 19:04:50 +07:00
Simon Podlipsky
06529e1924 Upgrade Doctrine CS 2019-03-28 12:18:35 +01:00
spawnia
ddebd9a414 Add test for default enum input coercion 2019-03-28 11:26:30 +01:00
spawnia
1acddf4e22 Mention default value definition for enum args in docs 2019-03-28 11:25:57 +01:00
spawnia
569945cd37 Clarify some bits of CONTRIBUTING.md and unify formatting 2019-03-28 11:24:06 +01:00
Vladimir Razuvaev
b088720d40
Merge pull request #458 from simPod/upgrade-phpstan
Upgrade PHPStan
2019-03-23 17:57:09 +07:00
Vladimir Razuvaev
b3bb316224
Merge pull request #457 from enumag/patch-4
Fix annotations
2019-03-19 12:19:51 +07:00
Simon Podlipsky
23ece09407 Upgrade PHPStan 2019-03-18 13:02:58 +01:00
Jáchym Toušek
1864facda8
Fix annotations 2019-03-18 12:31:33 +01:00
Vladimir Razuvaev
d15a9405cd
Merge pull request #455 from spawnia/patch-1
Remove deprecated tools and improve Lighthouse description
2019-03-17 23:22:41 +07:00
Benedikt Franke
d4742a76e5
Remove deprecated tools and improve Lighthouse description
- The folklore package was archived by the owner.
- The lighthouse relay tools were integrated with the core
2019-03-14 11:16:15 +01:00
Vladimir Razuvaev
f107cc2076 Merge branch 'master' of https://github.com/webonyx/graphql-php 2019-03-09 22:35:12 +07:00
Vladimir Razuvaev
29eba82093
Merge pull request #436 from keulinho/add-queryplan
Added query plan
2019-03-09 21:37:44 +07:00
Vladimir Razuvaev
e6e9d9ea22 Merge branch 'master' of https://github.com/webonyx/graphql-php 2019-03-04 19:03:55 +07:00
Vladimir Razuvaev
f24e00f4b4
Merge pull request #449 from hughdevore/patch-1
Add WP-GraphQL to complementary tools.
2019-03-01 23:54:04 +07:00
Hughie Devore
4a39dadd0d
Add WP-GraphQL to complementary tools.
We utilized this repo for our work in WP GraphQL and wanted to let WP users know that it's available for use.
2019-02-28 13:23:23 -07:00
Vladimir Razuvaev
616fc10837
Merge pull request #431 from simPod/fix-bc-finder
Invert instance of in BCFinder
2019-02-19 10:53:03 +07:00
Vladimir Razuvaev
2f73bbe96c
Merge pull request #442 from moufmouf/patch-1
Adding GraphQLite to the list of PHP Tools
2019-02-19 10:48:52 +07:00
Vladimir Razuvaev
b667d8b3a3
Merge pull request #445 from Chrisdowson/master
change Document url
2019-02-19 10:48:01 +07:00
段小强
441d70c7e5
change Document url
change Document url
2019-02-19 11:21:25 +08:00
David Négrier
972532cf6c
Adding GraphQLite to the list of PHP Tools 2019-02-12 10:46:28 +01:00
Vladimir Razuvaev
e467f80149
Merge pull request #440 from simPod/php7.4
Test against PHP 7.4
2019-02-11 15:31:09 +07:00
Simon Podlipsky
d97fac6ab0 Invert instance of in BCFinder 2019-02-11 09:16:01 +01:00
Simon Podlipsky
dfefdf24cb Test against PHP 7.4 2019-02-11 08:37:05 +01:00
Vladimir Razuvaev
20e98aefa4
Merge pull request #438 from skodak/executioncontext
Fix incorrect array type of contextValue in PHPDocs
2019-02-11 10:56:05 +07:00
Vladimir Razuvaev
ff16350aa1
Merge pull request #439 from simPod/php8
Support PHP 8
2019-02-11 10:55:23 +07:00
Petr Skoda
d20a6a9d56 Standardise whitespace 2019-02-11 08:53:36 +13:00
Petr Skoda
77448ba623 Fix incorrect array type of rootValue in PHPDocs 2019-02-11 08:53:24 +13:00
Simon Podlipsky
bc637414e5 Support PHP 8 2019-02-08 15:08:11 +01:00
Petr Skoda
7d59811c4f Fix incorrect array type of contextValue in PHPDocs 2019-02-07 09:18:17 +13:00
Vladimir Razuvaev
31bbc416a5
Merge pull request #437 from stefanotorresi/patch-1
Update docs intro verbiage
2019-02-04 19:18:53 +07:00
Stefano Torresi
b1ab1820b6
Update docs intro verbiage
Just throwing my 2 cents: I don't think it's fair to say that "it's intended to be a replacement", given that multiple API paradigms can coexist in the same system and each of them have their trade-offs.
2019-02-04 12:38:31 +01:00
Jonas Elfering
5b3f44c7a3 add queryplan 2019-02-01 22:39:00 +01:00
Vladimir Razuvaev
1dc291b073
Merge pull request #435 from JanBukva/fix-interface-type-return
Fix return annotation of resolveType() in InterfaceType
2019-01-29 17:50:03 +07:00
Jan Bukva
edb5268583
Fix return annotation of resolveType() in InterfaceType 2019-01-28 22:46:20 +01:00
Vladimir Razuvaev
6544197ef8
Merge pull request #426 from simPod/merge-clovers
Merge clovers
2018-12-31 13:07:26 +03:00
Vladimir Razuvaev
5ac3eeab18
Merge pull request #418 from Quartz/improvement/apollo-compatible-persisted-queries
Allow extensions to be provided in GET request.
2018-12-31 13:05:09 +03:00
Vladimir Razuvaev
a80d38747f
Merge pull request #421 from JanBukva/new-executor-forces-array
Fix new executor is removing associative array when using custom scalar
2018-12-31 13:00:58 +03:00
Vladimir Razuvaev
9c1a89710e
Merge pull request #422 from simPod/fix-deferred
Fix Deferred
2018-12-31 12:40:58 +03:00
Simon Podlipsky
fd7465521a Merge clovers 2018-12-31 00:32:42 +01:00
Simon Podlipsky
828a9fb002 Fix Deferred 2018-12-27 22:50:12 +01:00
Jan Bukva
00490d289c
Use key-value foreach 2018-12-27 21:53:16 +01:00
Jan Bukva
2295b96a49
Fix CoroutineExecutor::resultToArray for associative arrays 2018-12-27 16:38:40 +01:00
Jan Bukva
31d89acfae
Failing test for CoroutineExecutor removes associative array when using custom scalar producing JSON 2018-12-27 16:01:27 +01:00
chriszarate
244ec66ecc Allow extensions to be provided in GET request. 2018-12-11 22:58:57 -05:00
Vladimir Razuvaev
f96bd2740d
Merge pull request #416 from erikgaal/master
Add NullableType interface
2018-12-07 18:26:04 +07:00
Vladimir Razuvaev
ead704022c
Merge pull request #417 from bytorsten/extension-fix
BUGFIX: ExtendSchema should be able to introduce new types
2018-12-07 16:58:05 +07:00
Torsten Blindert
9609d2ac84 TASK: Code style 2018-12-05 19:23:07 +01:00
Torsten Blindert
d5fddfd504 TASK: Added test 2018-12-05 19:13:32 +01:00
Torsten Blindert
62b0036437 BUGFIX: expect ->getType() to throw 2018-12-05 18:55:14 +01:00
Erik Gaal
9a0dbff26b Add NullableType interface 2018-12-05 11:43:27 +01:00
Vladimir Razuvaev
e22b400373
Merge pull request #413 from Quartz/improvement/apollo-compatible-persisted-queries
Apollo persisted query compatibility: Look for queryId in extensions.
2018-12-04 19:33:04 +07:00
chriszarate
f644c1a837 Add extensions to OperationParams instance. 2018-12-02 15:10:09 -05:00
chriszarate
c33e41f2bf Fix linting issue and typos. 2018-12-01 19:43:51 -05:00
chriszarate
63b4e3f0a4 Apollo server/client compatibility. Look for queryid in extensions. 2018-12-01 19:06:59 -05:00
Vladimir Razuvaev
039577d9eb
Merge pull request #403 from simPod/fix-conditions
Improve comparison in ReferenceExecutor
2018-12-01 23:04:34 +07:00
Vladimir Razuvaev
6610f4e2da
Merge pull request #406 from simPod/fix-type
Added all possible scalar types to Node constructor
2018-12-01 23:04:11 +07:00
Vladimir Razuvaev
19d9c523c5
Merge pull request #407 from simPod/document-bc
Document BC and fix types in ResolveInfo
2018-12-01 23:03:19 +07:00
Vladimir Razuvaev
99f93939db
Merge pull request #408 from yura3d/error-handling-improvements
Error handling improvements
2018-12-01 23:02:57 +07:00
Vladimir Razuvaev
8338db0480
Merge pull request #409 from simPod/php7.3
Add PHP 7.3 to Travis
2018-12-01 22:59:16 +07:00
Simon Podlipsky
f95d1e81ea Add PHP 7.3 to Travis 2018-11-29 11:10:51 +01:00
Yury
33e3c9c338
Error handling improvements 2018-11-28 17:35:21 +03:00
Simon Podlipsky
376e927505 Document BC and fix types in ResolveInfo 2018-11-27 14:05:55 +01:00
Simon Podlipsky
ead1b864bc Added all possible scalar types to Node constructor 2018-11-27 12:22:01 +01:00
Vladimir Razuvaev
b2cea8b538
Merge pull request #402 from simPod/complementary-tools
Complementary tools
2018-11-27 18:09:57 +07:00
Simon Podlipsky
e344c8a441 Improve comparison in ReferenceExecutor 2018-11-23 12:17:33 +01:00
Simon Podlipsky
90b51e1a5d Replace node kind checks by InstanceOf checks 2018-11-23 12:07:48 +01:00
Simon Podlipsky
396d3370dc Mention simPod/GraphQL-Utils
Resolves #70
Closes #393
2018-11-23 11:00:26 +01:00
Simon Podlipsky
d591eccd9f Clean complementary tools 2018-11-23 10:58:58 +01:00
132 changed files with 2530 additions and 856 deletions

1
.gitattributes vendored
View file

@ -2,6 +2,7 @@
* text eol=lf * text eol=lf
/benchmarks export-ignore /benchmarks export-ignore
/tests export-ignore /tests export-ignore
/examples export-ignore
/tools export-ignore /tools export-ignore
.gitattributes export-ignore .gitattributes export-ignore
.gitignore export-ignore .gitignore export-ignore

1
.gitignore vendored
View file

@ -4,3 +4,4 @@ composer.phar
phpcs.xml phpcs.xml
phpstan.neon phpstan.neon
vendor/ vendor/
/.idea

View file

@ -1,4 +1,21 @@
# Changelog # Changelog
## Unreleased
- Add schema validation: Input Objects must not contain non-nullable circular references (#492)
#### v0.13.5
- Fix coroutine executor when using with promise (#486)
#### v0.13.4
- Force int when setting max query depth (#477)
#### v0.13.3
- Reverted minor possible breaking change (#476)
#### v0.13.2
- Added QueryPlan support (#436)
- Fixed an issue with NodeList iteration over missing keys (#475)
#### v0.13.1 #### v0.13.1
- Better validation of field/directive arguments - Better validation of field/directive arguments
- Support for apollo client/server persisted queries - Support for apollo client/server persisted queries

View file

@ -1,7 +1,6 @@
# Contributing to GraphQL PHP # Contributing to GraphQL PHP
## Workflow ## Workflow
If your contribution requires significant or breaking changes, or if you plan to propose a major new feature, If your contribution requires significant or breaking changes, or if you plan to propose a major new feature,
we recommend you to create an issue on the [GitHub](https://github.com/webonyx/graphql-php/issues) with we recommend you to create an issue on the [GitHub](https://github.com/webonyx/graphql-php/issues) with
a brief proposal and discuss it with us first. a brief proposal and discuss it with us first.
@ -11,12 +10,14 @@ For smaller contributions just use this workflow:
* Fork the project. * Fork the project.
* Add your features and or bug fixes. * Add your features and or bug fixes.
* Add tests. Tests are important for us. * Add tests. Tests are important for us.
* Check your changes using `composer check-all` * Check your changes using `composer check-all`.
* Send a pull request * Add an entry to the [Changelog's Unreleases section](CHANGELOG.md#unreleased).
* Send a pull request.
## Setup the Development Environment
First, copy the URL of your fork and `git clone` it to your local machine.
## Using GraphQL PHP from a Git checkout
```sh ```sh
git clone https://github.com/webonyx/graphql-php.git
cd graphql-php cd graphql-php
composer install composer install
``` ```
@ -29,28 +30,26 @@ composer install
Some tests have annotation `@see it('<description>')`. It is used for reference to same tests in [graphql-js implementation](https://github.com/graphql/graphql-js) with the same description. Some tests have annotation `@see it('<description>')`. It is used for reference to same tests in [graphql-js implementation](https://github.com/graphql/graphql-js) with the same description.
## Coding Standard ## Coding Standard
Coding standard of this project is based on [Doctrine CS](https://github.com/doctrine/coding-standard). To run the inspection: The coding standard of this project is based on [Doctrine CS](https://github.com/doctrine/coding-standard).
Run the inspections:
```sh ```sh
./vendor/bin/phpcs ./vendor/bin/phpcs
``` ```
Auto-fixing: Apply automatic code style fixes:
```sh ```sh
./vendor/bin/phpcbf ./vendor/bin/phpcbf
``` ```
## Static analysis ## Static analysis
Based on [PHPStan](https://github.com/phpstan/phpstan) Based on [PHPStan](https://github.com/phpstan/phpstan).
```sh ```sh
./vendor/bin/phpstan analyse --ansi --memory-limit 256M ./vendor/bin/phpstan analyse --ansi --memory-limit 256M
``` ```
## Running benchmarks ## Running benchmarks
Benchmarks are run via [PHPBench](https://github.com/phpbench/phpbench).
Benchmarks are run via phpbench:
```sh ```sh
./vendor/bin/phpbench run . ./vendor/bin/phpbench run .
``` ```

View file

@ -14,7 +14,7 @@ composer require webonyx/graphql-php
``` ```
## Documentation ## Documentation
Full documentation is available on the [Documentation site](http://webonyx.github.io/graphql-php/) as well Full documentation is available on the [Documentation site](https://webonyx.github.io/graphql-php/) as well
as in the [docs](docs/) folder of the distribution. as in the [docs](docs/) folder of the distribution.
If you don't know what GraphQL is, visit this [official website](http://graphql.org) If you don't know what GraphQL is, visit this [official website](http://graphql.org)
@ -24,11 +24,29 @@ by the Facebook engineering team.
There are several ready examples in the [examples](examples/) folder of the distribution with specific There are several ready examples in the [examples](examples/) folder of the distribution with specific
README file per example. README file per example.
## Contribute ## Contributors
Please refer to [CONTRIBUTING.md](CONTRIBUTING.md) for information on how to contribute.
## Old README.md This project exists thanks to [all the people](https://github.com/webonyx/graphql-php/graphs/contributors) who contribute. [[Contribute](CONTRIBUTING.md)].
Here is a [link to the old README.md](https://github.com/webonyx/graphql-php/blob/v0.9.14/README.md).
Keep in mind that it relates to the version 0.9.x. It may contain outdated information for ## Backers
newer versions (even though we try to preserve backwards compatibility).
<a href="https://opencollective.com/webonyx-graphql-php#backers" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/backers.svg?width=890"></a>
## Sponsors
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/webonyx-graphql-php#sponsor)]
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/0/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/1/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/2/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/3/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/4/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/5/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/6/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/7/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/8/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/webonyx-graphql-php/sponsor/9/website" target="_blank"><img src="https://opencollective.com/webonyx-graphql-php/sponsor/9/avatar.svg"></a>
## License
See [LICENSE](LICENSE).

View file

@ -1,3 +1,8 @@
## Master
### Breaking (major): dropped deprecations
- dropped deprecated `GraphQL\Schema`. Use `GraphQL\Type\Schema`.
## Upgrade v0.12.x > v0.13.x ## Upgrade v0.12.x > v0.13.x
### Breaking (major): minimum supported version of PHP ### Breaking (major): minimum supported version of PHP

View file

@ -152,7 +152,7 @@ class SchemaGenerator
]; ];
} }
public function resolveField($value, $args, $context, $resolveInfo) public function resolveField($objectValue, $args, $context, $resolveInfo)
{ {
return $resolveInfo->fieldName . '-value'; return $resolveInfo->fieldName . '-value';
} }

View file

@ -14,11 +14,11 @@
"ext-mbstring": "*" "ext-mbstring": "*"
}, },
"require-dev": { "require-dev": {
"doctrine/coding-standard": "^5.0", "doctrine/coding-standard": "^6.0",
"phpbench/phpbench": "^0.14.0", "phpbench/phpbench": "^0.14.0",
"phpstan/phpstan": "0.10.5", "phpstan/phpstan": "^0.11.12",
"phpstan/phpstan-phpunit": "0.10.0", "phpstan/phpstan-phpunit": "^0.11.2",
"phpstan/phpstan-strict-rules": "0.10.1", "phpstan/phpstan-strict-rules": "^0.11.1",
"phpunit/phpcov": "^5.0", "phpunit/phpcov": "^5.0",
"phpunit/phpunit": "^7.2", "phpunit/phpunit": "^7.2",
"psr/http-message": "^1.0", "psr/http-message": "^1.0",

View file

@ -1,22 +1,26 @@
# Integrations # Integrations
* [Integration with Relay](https://github.com/ivome/graphql-relay-php) * [Standard Server](executing-queries.md/#using-server) Out of the box integration with any PSR-7 compatible framework (like [Slim](http://slimframework.com) or [Zend Expressive](http://zendframework.github.io/zend-expressive/)).
* [Integration with Laravel 5](https://github.com/Folkloreatelier/laravel-graphql) + [Relay Helpers for Laravel](https://github.com/nuwave/laravel-graphql-relay) + [Nuwave Lighthouse](https://github.com/nuwave/lighthouse) * [Relay Library for graphql-php](https://github.com/ivome/graphql-relay-php) Helps construct Relay related schema definitions.
* [Symfony Bundle](https://github.com/overblog/GraphQLBundle) by Overblog * [Lighthouse](https://github.com/nuwave/lighthouse) Laravel based, uses Schema Definition Language
* Out of the box integration with any PSR-7 compatible framework (like [Slim](http://slimframework.com) or [Zend Expressive](http://zendframework.github.io/zend-expressive/)) via [Standard Server](executing-queries.md/#using-server) * [Laravel GraphQL](https://github.com/rebing/graphql-laravel) - Laravel wrapper for Facebook's GraphQL
* [OverblogGraphQLBundle](https://github.com/overblog/GraphQLBundle) Bundle for Symfony
* [WP-GraphQL](https://github.com/wp-graphql/wp-graphql) - GraphQL API for WordPress
# GraphQL PHP Tools # GraphQL PHP Tools
* Define types with Doctrine ORM annotations ([for PHP7.1](https://github.com/Ecodev/graphql-doctrine), for [earlier PHP versions](https://github.com/rahuljayaraman/doctrine-graphql)) * [GraphQLite](https://graphqlite.thecodingmachine.io) Define your complete schema with annotations
* [DataLoader PHP](https://github.com/overblog/dataloader-php) - as a ready implementation for [deferred resolvers](data-fetching.md#solving-n1-problem) * [GraphQL Doctrine](https://github.com/Ecodev/graphql-doctrine) Define types with Doctrine ORM annotations
* [PSR 15 compliant middleware](https://github.com/phps-cans/psr7-middleware-graphql) for the Standard Server (experimental) * [DataLoaderPHP](https://github.com/overblog/dataloader-php) as a ready implementation for [deferred resolvers](data-fetching.md#solving-n1-problem)
* [GraphQL Uploads](https://github.com/Ecodev/graphql-upload) for the Standard Server * [GraphQL Uploads](https://github.com/Ecodev/graphql-upload) A PSR-15 middleware to support file uploads in GraphQL.
* [GraphQL Batch Processor](https://github.com/vasily-kartashov/graphql-batch-processing) - Simple library that provides a builder interface for defining collection, querying, filtering, and post-processing logic of batched data fetches. * [GraphQL Batch Processor](https://github.com/vasily-kartashov/graphql-batch-processing) Provides a builder interface for defining collection, querying, filtering, and post-processing logic of batched data fetches.
* [GraphQL Utils](https://github.com/simPod/GraphQL-Utils) Objective schema definition builders (no need for arrays anymore) and `DateTime` scalar
* [PSR 15 compliant middleware](https://github.com/phps-cans/psr7-middleware-graphql) for the Standard Server _(experimental)_
# General GraphQL Tools # General GraphQL Tools
* [GraphiQL](https://github.com/graphql/graphiql) - An in-browser IDE for exploring GraphQL * [GraphQL Playground](https://github.com/prismagraphql/graphql-playground) GraphQL IDE for better development workflows (GraphQL Subscriptions, interactive docs & collaboration).
* [GraphiQL](https://github.com/graphql/graphiql) An in-browser IDE for exploring GraphQL
* [ChromeiQL](https://chrome.google.com/webstore/detail/chromeiql/fkkiamalmpiidkljmicmjfbieiclmeij) * [ChromeiQL](https://chrome.google.com/webstore/detail/chromeiql/fkkiamalmpiidkljmicmjfbieiclmeij)
or [GraphiQL Feen](https://chrome.google.com/webstore/detail/graphiql-feen/mcbfdonlkfpbfdpimkjilhdneikhfklp) - or [GraphiQL Feen](https://chrome.google.com/webstore/detail/graphiql-feen/mcbfdonlkfpbfdpimkjilhdneikhfklp)
GraphiQL as Google Chrome extension GraphiQL as Google Chrome extension
* [GraphQL Playground](https://github.com/prismagraphql/graphql-playground) - GraphQL IDE for better development workflows (GraphQL Subscriptions, interactive docs & collaboration).

View file

@ -103,23 +103,25 @@ for a field you simply override this default resolver.
**graphql-php** provides following default field resolver: **graphql-php** provides following default field resolver:
```php ```php
<?php <?php
function defaultFieldResolver($source, $args, $context, \GraphQL\Type\Definition\ResolveInfo $info) function defaultFieldResolver($objectValue, $args, $context, \GraphQL\Type\Definition\ResolveInfo $info)
{ {
$fieldName = $info->fieldName; $fieldName = $info->fieldName;
$property = null; $property = null;
if (is_array($source) || $source instanceof \ArrayAccess) { if (is_array($objectValue) || $objectValue instanceof \ArrayAccess) {
if (isset($source[$fieldName])) { if (isset($objectValue[$fieldName])) {
$property = $source[$fieldName]; $property = $objectValue[$fieldName];
} }
} else if (is_object($source)) { } elseif (is_object($objectValue)) {
if (isset($source->{$fieldName})) { if (isset($objectValue->{$fieldName})) {
$property = $source->{$fieldName}; $property = $objectValue->{$fieldName};
}
} }
return $property instanceof Closure
? $property($objectValue, $args, $context, $info)
: $property;
} }
return $property instanceof Closure ? $property($source, $args, $context, $info) : $property;
}
``` ```
As you see it returns value by key (for arrays) or property (for objects). As you see it returns value by key (for arrays) or property (for objects).
@ -161,7 +163,6 @@ $userType = new ObjectType([
Keep in mind that **field resolver** has precedence over **default field resolver per type** which in turn Keep in mind that **field resolver** has precedence over **default field resolver per type** which in turn
has precedence over **default field resolver**. has precedence over **default field resolver**.
# Solving N+1 Problem # Solving N+1 Problem
Since: 0.9.0 Since: 0.9.0

View file

@ -136,13 +136,13 @@ So for example following batch will require single DB request (if user field is
```json ```json
[ [
{ {
"query": "{user(id: 1)} { id }" "query": "{user(id: 1) { id }}"
}, },
{ {
"query": "{user(id: 2)} { id }" "query": "{user(id: 2) { id }}"
}, },
{ {
"query": "{user(id: 3)} { id }" "query": "{user(id: 3) { id }}"
} }
] ]
``` ```

View file

@ -54,8 +54,8 @@ $queryType = new ObjectType([
'args' => [ 'args' => [
'message' => Type::nonNull(Type::string()), 'message' => Type::nonNull(Type::string()),
], ],
'resolve' => function ($root, $args) { 'resolve' => function ($rootValue, $args) {
return $root['prefix'] . $args['message']; return $rootValue['prefix'] . $args['message'];
} }
], ],
], ],

View file

@ -33,7 +33,7 @@ See [related documentation](executing-queries.md).
* fieldResolver: * fieldResolver:
* A resolver function to use when one is not provided by the schema. * A resolver function to use when one is not provided by the schema.
* If not provided, the default field resolver is used (which looks for a * If not provided, the default field resolver is used (which looks for a
* value on the source value with the field's name). * value on the object value with the field's name).
* validationRules: * validationRules:
* A set of rules for query validation step. Default value is all available rules. * A set of rules for query validation step. Default value is all available rules.
* Empty array would allow to skip query validation (may be convenient for persisted * Empty array would allow to skip query validation (may be convenient for persisted
@ -299,7 +299,7 @@ static function getNullableType($type)
``` ```
# GraphQL\Type\Definition\ResolveInfo # GraphQL\Type\Definition\ResolveInfo
Structure containing information useful for field resolution process. Structure containing information useful for field resolution process.
Passed as 3rd argument to every field resolver. See [docs on field resolving (data fetching)](data-fetching.md). Passed as 4th argument to every field resolver. See [docs on field resolving (data fetching)](data-fetching.md).
**Class Props:** **Class Props:**
```php ```php

View file

@ -158,7 +158,7 @@ $heroType = new ObjectType([
'args' => [ 'args' => [
'episode' => Type::nonNull($enumType) 'episode' => Type::nonNull($enumType)
], ],
'resolve' => function($_value, $args) { 'resolve' => function($hero, $args) {
return $args['episode'] === 5 ? true : false; return $args['episode'] === 5 ? true : false;
} }
] ]

View file

@ -114,7 +114,7 @@ Option | Type | Notes
name | `string` | **Required.** Name of the input field. When not set - inferred from **fields** array key name | `string` | **Required.** Name of the input field. When not set - inferred from **fields** array key
type | `Type` | **Required.** Instance of one of [Input Types](input-types.md) (**Scalar**, **Enum**, **InputObjectType** + any combination of those with **nonNull** and **listOf** modifiers) type | `Type` | **Required.** Instance of one of [Input Types](input-types.md) (**Scalar**, **Enum**, **InputObjectType** + any combination of those with **nonNull** and **listOf** modifiers)
description | `string` | Plain-text description of this input field for clients (e.g. used by [GraphiQL](https://github.com/graphql/graphiql) for auto-generated documentation) description | `string` | Plain-text description of this input field for clients (e.g. used by [GraphiQL](https://github.com/graphql/graphiql) for auto-generated documentation)
defaultValue | `scalar` | Default value of this input field defaultValue | `scalar` | Default value of this input field. Use the internal value if specifying a default for an **enum** type
# Using Input Object Type # Using Input Object Type
In the example above we defined our InputObjectType. Now let's use it in one of field arguments: In the example above we defined our InputObjectType. Now let's use it in one of field arguments:
@ -131,7 +131,7 @@ $queryType = new ObjectType([
'type' => Type::listOf($storyType), 'type' => Type::listOf($storyType),
'args' => [ 'args' => [
'filters' => [ 'filters' => [
'type' => Type::nonNull($filters), 'type' => $filters,
'defaultValue' => [ 'defaultValue' => [
'popular' => true 'popular' => true
] ]

View file

@ -80,7 +80,7 @@ Option | Type | Notes
name | `string` | **Required.** Name of the field. When not set - inferred from **fields** array key (read about [shorthand field definition](#shorthand-field-definitions) below) name | `string` | **Required.** Name of the field. When not set - inferred from **fields** array key (read about [shorthand field definition](#shorthand-field-definitions) below)
type | `Type` | **Required.** An instance of internal or custom type. Note: type must be represented by a single instance within one schema (see also [Type Registry](index.md#type-registry)) type | `Type` | **Required.** An instance of internal or custom type. Note: type must be represented by a single instance within one schema (see also [Type Registry](index.md#type-registry))
args | `array` | An array of possible type arguments. Each entry is expected to be an array with keys: **name**, **type**, **description**, **defaultValue**. See [Field Arguments](#field-arguments) section below. args | `array` | An array of possible type arguments. Each entry is expected to be an array with keys: **name**, **type**, **description**, **defaultValue**. See [Field Arguments](#field-arguments) section below.
resolve | `callable` | **function($value, $args, $context, [ResolveInfo](../reference.md#graphqltypedefinitionresolveinfo) $info)**<br> Given the **$value** of this type, it is expected to return actual value of the current field. See section on [Data Fetching](../data-fetching.md) for details resolve | `callable` | **function($objectValue, $args, $context, [ResolveInfo](../reference.md#graphqltypedefinitionresolveinfo) $info)**<br> Given the **$objectValue** of this type, it is expected to return actual value of the current field. See section on [Data Fetching](../data-fetching.md) for details
complexity | `callable` | **function($childrenComplexity, $args)**<br> Used to restrict query complexity. The feature is disabled by default, read about [Security](../security.md#query-complexity-analysis) to use it. complexity | `callable` | **function($childrenComplexity, $args)**<br> Used to restrict query complexity. The feature is disabled by default, read about [Security](../security.md#query-complexity-analysis) to use it.
description | `string` | Plain-text description of this field for clients (e.g. used by [GraphiQL](https://github.com/graphql/graphiql) for auto-generated documentation) description | `string` | Plain-text description of this field for clients (e.g. used by [GraphiQL](https://github.com/graphql/graphiql) for auto-generated documentation)
deprecationReason | `string` | Text describing why this field is deprecated. When not empty - field will not be returned by introspection queries (unless forced) deprecationReason | `string` | Text describing why this field is deprecated. When not empty - field will not be returned by introspection queries (unless forced)
@ -94,7 +94,7 @@ Option | Type | Notes
name | `string` | **Required.** Name of the argument. When not set - inferred from **args** array key name | `string` | **Required.** Name of the argument. When not set - inferred from **args** array key
type | `Type` | **Required.** Instance of one of [Input Types](input-types.md) (**scalar**, **enum**, **InputObjectType** + any combination of those with **nonNull** and **listOf** modifiers) type | `Type` | **Required.** Instance of one of [Input Types](input-types.md) (**scalar**, **enum**, **InputObjectType** + any combination of those with **nonNull** and **listOf** modifiers)
description | `string` | Plain-text description of this argument for clients (e.g. used by [GraphiQL](https://github.com/graphql/graphiql) for auto-generated documentation) description | `string` | Plain-text description of this argument for clients (e.g. used by [GraphiQL](https://github.com/graphql/graphiql) for auto-generated documentation)
defaultValue | `scalar` | Default value for this argument defaultValue | `scalar` | Default value for this argument. Use the internal value if specifying a default for an **enum** type
# Shorthand field definitions # Shorthand field definitions
Fields can be also defined in **shorthand** notation (with only **name** and **type** options): Fields can be also defined in **shorthand** notation (with only **name** and **type** options):

View file

@ -62,7 +62,7 @@ $mutationType = new ObjectType([
'episode' => $episodeEnum, 'episode' => $episodeEnum,
'review' => $reviewInputObject 'review' => $reviewInputObject
], ],
'resolve' => function($val, $args) { 'resolve' => function($rootValue, $args) {
// TODOC // TODOC
} }
] ]

View file

@ -81,7 +81,7 @@ $cacheFilename = 'cached_schema.php';
if (!file_exists($cacheFilename)) { if (!file_exists($cacheFilename)) {
$document = Parser::parse(file_get_contents('./schema.graphql')); $document = Parser::parse(file_get_contents('./schema.graphql'));
file_put_contents($cacheFilename, "<?php\nreturn " . var_export(AST::toArray($document), true)); file_put_contents($cacheFilename, "<?php\nreturn " . var_export(AST::toArray($document), true) . ";\n");
} else { } else {
$document = AST::fromArray(require $cacheFilename); // fromArray() is a lazy operation as well $document = AST::fromArray(require $cacheFilename); // fromArray() is a lazy operation as well
} }

View file

@ -19,8 +19,8 @@ try {
'args' => [ 'args' => [
'message' => ['type' => Type::string()], 'message' => ['type' => Type::string()],
], ],
'resolve' => function ($root, $args) { 'resolve' => function ($rootValue, $args) {
return $root['prefix'] . $args['message']; return $rootValue['prefix'] . $args['message'];
} }
], ],
], ],
@ -35,7 +35,7 @@ try {
'x' => ['type' => Type::int()], 'x' => ['type' => Type::int()],
'y' => ['type' => Type::int()], 'y' => ['type' => Type::int()],
], ],
'resolve' => function ($root, $args) { 'resolve' => function ($calc, $args) {
return $args['x'] + $args['y']; return $args['x'] + $args['y'];
}, },
], ],

View file

@ -35,12 +35,12 @@ class CommentType extends ObjectType
Types::htmlField('body') Types::htmlField('body')
]; ];
}, },
'resolveField' => function($value, $args, $context, ResolveInfo $info) { 'resolveField' => function($comment, $args, $context, ResolveInfo $info) {
$method = 'resolve' . ucfirst($info->fieldName); $method = 'resolve' . ucfirst($info->fieldName);
if (method_exists($this, $method)) { if (method_exists($this, $method)) {
return $this->{$method}($value, $args, $context, $info); return $this->{$method}($comment, $args, $context, $info);
} else { } else {
return $value->{$info->fieldName}; return $comment->{$info->fieldName};
} }
} }
]; ];

View file

@ -57,8 +57,8 @@ class QueryType extends ObjectType
], ],
'hello' => Type::string() 'hello' => Type::string()
], ],
'resolveField' => function($val, $args, $context, ResolveInfo $info) { 'resolveField' => function($rootValue, $args, $context, ResolveInfo $info) {
return $this->{$info->fieldName}($val, $args, $context, $info); return $this->{$info->fieldName}($rootValue, $args, $context, $info);
} }
]; ];
parent::__construct($config); parent::__construct($config);

View file

@ -75,12 +75,12 @@ class StoryType extends ObjectType
'interfaces' => [ 'interfaces' => [
Types::node() Types::node()
], ],
'resolveField' => function($value, $args, $context, ResolveInfo $info) { 'resolveField' => function($story, $args, $context, ResolveInfo $info) {
$method = 'resolve' . ucfirst($info->fieldName); $method = 'resolve' . ucfirst($info->fieldName);
if (method_exists($this, $method)) { if (method_exists($this, $method)) {
return $this->{$method}($value, $args, $context, $info); return $this->{$method}($story, $args, $context, $info);
} else { } else {
return $value->{$info->fieldName}; return $story->{$info->fieldName};
} }
} }
]; ];

View file

@ -44,12 +44,12 @@ class UserType extends ObjectType
'interfaces' => [ 'interfaces' => [
Types::node() Types::node()
], ],
'resolveField' => function($value, $args, $context, ResolveInfo $info) { 'resolveField' => function($user, $args, $context, ResolveInfo $info) {
$method = 'resolve' . ucfirst($info->fieldName); $method = 'resolve' . ucfirst($info->fieldName);
if (method_exists($this, $method)) { if (method_exists($this, $method)) {
return $this->{$method}($value, $args, $context, $info); return $this->{$method}($user, $args, $context, $info);
} else { } else {
return $value->{$info->fieldName}; return $user->{$info->fieldName};
} }
} }
]; ];

View file

@ -1,12 +1,12 @@
<?php <?php
interface Resolver { interface Resolver {
public function resolve($root, $args, $context); public function resolve($rootValue, $args, $context);
} }
class Addition implements Resolver class Addition implements Resolver
{ {
public function resolve($root, $args, $context) public function resolve($rootValue, $args, $context)
{ {
return $args['x'] + $args['y']; return $args['x'] + $args['y'];
} }
@ -14,22 +14,22 @@ class Addition implements Resolver
class Echoer implements Resolver class Echoer implements Resolver
{ {
public function resolve($root, $args, $context) public function resolve($rootValue, $args, $context)
{ {
return $root['prefix'].$args['message']; return $rootValue['prefix'].$args['message'];
} }
} }
return [ return [
'sum' => function($root, $args, $context) { 'sum' => function($rootValue, $args, $context) {
$sum = new Addition(); $sum = new Addition();
return $sum->resolve($root, $args, $context); return $sum->resolve($rootValue, $args, $context);
}, },
'echo' => function($root, $args, $context) { 'echo' => function($rootValue, $args, $context) {
$echo = new Echoer(); $echo = new Echoer();
return $echo->resolve($root, $args, $context); return $echo->resolve($rootValue, $args, $context);
}, },
'prefix' => 'You said: ', 'prefix' => 'You said: ',
]; ];

View file

@ -19,8 +19,8 @@ try {
'args' => [ 'args' => [
'message' => ['type' => Type::string()], 'message' => ['type' => Type::string()],
], ],
'resolve' => function ($root, $args) { 'resolve' => function ($rootValue, $args) {
return $root['prefix'] . $args['message']; return $rootValue['prefix'] . $args['message'];
} }
], ],
], ],
@ -35,7 +35,7 @@ try {
'x' => ['type' => Type::int()], 'x' => ['type' => Type::int()],
'y' => ['type' => Type::int()], 'y' => ['type' => Type::int()],
], ],
'resolve' => function ($root, $args) { 'resolve' => function ($calc, $args) {
return $args['x'] + $args['y']; return $args['x'] + $args['y'];
}, },
], ],

View file

@ -8,6 +8,8 @@ parameters:
ignoreErrors: ignoreErrors:
- "~Construct empty\\(\\) is not allowed\\. Use more strict comparison~" - "~Construct empty\\(\\) is not allowed\\. Use more strict comparison~"
- "~(Method|Property) .+::.+(\\(\\))? (has parameter \\$\\w+ with no|has no return|has no) typehint specified~" - "~(Method|Property) .+::.+(\\(\\))? (has parameter \\$\\w+ with no|has no return|has no) typehint specified~"
- "~Variable property access on .+~"
- "~Variable method call on static\\(GraphQL\\\\Server\\\\ServerConfig\\)~" # TODO get rid of
includes: includes:
- vendor/phpstan/phpstan-phpunit/extension.neon - vendor/phpstan/phpstan-phpunit/extension.neon

View file

@ -122,13 +122,13 @@ class Error extends Exception implements JsonSerializable, ClientAware
if ($previous instanceof ClientAware) { if ($previous instanceof ClientAware) {
$this->isClientSafe = $previous->isClientSafe(); $this->isClientSafe = $previous->isClientSafe();
$this->category = $previous->getCategory() ?: static::CATEGORY_INTERNAL; $this->category = $previous->getCategory() ?: self::CATEGORY_INTERNAL;
} elseif ($previous) { } elseif ($previous) {
$this->isClientSafe = false; $this->isClientSafe = false;
$this->category = static::CATEGORY_INTERNAL; $this->category = self::CATEGORY_INTERNAL;
} else { } else {
$this->isClientSafe = true; $this->isClientSafe = true;
$this->category = static::CATEGORY_GRAPHQL; $this->category = self::CATEGORY_GRAPHQL;
} }
} }
@ -148,10 +148,10 @@ class Error extends Exception implements JsonSerializable, ClientAware
if ($error instanceof self) { if ($error instanceof self) {
if ($error->path && $error->nodes) { if ($error->path && $error->nodes) {
return $error; return $error;
} else {
$nodes = $nodes ?: $error->nodes;
$path = $path ?: $error->path;
} }
$nodes = $nodes ?: $error->nodes;
$path = $path ?: $error->path;
} }
$source = $positions = $originalError = null; $source = $positions = $originalError = null;

View file

@ -231,7 +231,7 @@ class FormattedError
* *
* @param mixed[] $formattedError * @param mixed[] $formattedError
* @param Throwable $e * @param Throwable $e
* @param bool $debug * @param bool|int $debug
* *
* @return mixed[] * @return mixed[]
* *
@ -297,7 +297,7 @@ class FormattedError
* Prepares final error formatter taking in account $debug flags. * Prepares final error formatter taking in account $debug flags.
* If initial formatter is not set, FormattedError::createFromException is used * If initial formatter is not set, FormattedError::createFromException is used
* *
* @param bool $debug * @param bool|int $debug
* *
* @return callable|callable * @return callable|callable
*/ */

View file

@ -7,8 +7,6 @@ namespace GraphQL\Error;
use LogicException; use LogicException;
/** /**
* Class InvariantVoilation
*
* Note: * Note:
* This exception should not inherit base Error exception as it is raised when there is an error somewhere in * This exception should not inherit base Error exception as it is raised when there is an error somewhere in
* user-land code * user-land code

View file

@ -7,8 +7,6 @@ namespace GraphQL\Error;
use RuntimeException; use RuntimeException;
/** /**
* Class UserError
*
* Error caused by actions of GraphQL clients. Can be safely displayed to a client... * Error caused by actions of GraphQL clients. Can be safely displayed to a client...
*/ */
class UserError extends RuntimeException implements ClientAware class UserError extends RuntimeException implements ClientAware

View file

@ -4,6 +4,8 @@ declare(strict_types=1);
namespace GraphQL\Error; namespace GraphQL\Error;
use GraphQL\Exception\InvalidArgument;
use function is_int;
use function trigger_error; use function trigger_error;
use const E_USER_WARNING; use const E_USER_WARNING;
@ -15,12 +17,12 @@ use const E_USER_WARNING;
*/ */
final class Warning final class Warning
{ {
const WARNING_ASSIGN = 2; public const WARNING_ASSIGN = 2;
const WARNING_CONFIG = 4; public const WARNING_CONFIG = 4;
const WARNING_FULL_SCHEMA_SCAN = 8; public const WARNING_FULL_SCHEMA_SCAN = 8;
const WARNING_CONFIG_DEPRECATION = 16; public const WARNING_CONFIG_DEPRECATION = 16;
const WARNING_NOT_A_TYPE = 32; public const WARNING_NOT_A_TYPE = 32;
const ALL = 63; public const ALL = 63;
/** @var int */ /** @var int */
private static $enableWarnings = self::ALL; private static $enableWarnings = self::ALL;
@ -37,7 +39,7 @@ final class Warning
* *
* @api * @api
*/ */
public static function setWarningHandler(?callable $warningHandler = null) public static function setWarningHandler(?callable $warningHandler = null) : void
{ {
self::$warningHandler = $warningHandler; self::$warningHandler = $warningHandler;
} }
@ -54,16 +56,16 @@ final class Warning
* *
* @api * @api
*/ */
public static function suppress($suppress = true) public static function suppress($suppress = true) : void
{ {
if ($suppress === true) { if ($suppress === true) {
self::$enableWarnings = 0; self::$enableWarnings = 0;
} elseif ($suppress === false) { } elseif ($suppress === false) {
self::$enableWarnings = self::ALL; self::$enableWarnings = self::ALL;
} else { } elseif (is_int($suppress)) {
$suppress = (int) $suppress;
self::$enableWarnings &= ~$suppress; self::$enableWarnings &= ~$suppress;
} else {
throw InvalidArgument::fromExpectedTypeAndArgument('bool|int', $suppress);
} }
} }
@ -79,22 +81,22 @@ final class Warning
* *
* @api * @api
*/ */
public static function enable($enable = true) public static function enable($enable = true) : void
{ {
if ($enable === true) { if ($enable === true) {
self::$enableWarnings = self::ALL; self::$enableWarnings = self::ALL;
} elseif ($enable === false) { } elseif ($enable === false) {
self::$enableWarnings = 0; self::$enableWarnings = 0;
} else { } elseif (is_int($enable)) {
$enable = (int) $enable;
self::$enableWarnings |= $enable; self::$enableWarnings |= $enable;
} else {
throw InvalidArgument::fromExpectedTypeAndArgument('bool|int', $enable);
} }
} }
public static function warnOnce($errorMessage, $warningId, $messageLevel = null) public static function warnOnce(string $errorMessage, int $warningId, ?int $messageLevel = null) : void
{ {
if (self::$warningHandler) { if (self::$warningHandler !== null) {
$fn = self::$warningHandler; $fn = self::$warningHandler;
$fn($errorMessage, $warningId); $fn($errorMessage, $warningId);
} elseif ((self::$enableWarnings & $warningId) > 0 && ! isset(self::$warned[$warningId])) { } elseif ((self::$enableWarnings & $warningId) > 0 && ! isset(self::$warned[$warningId])) {
@ -103,9 +105,9 @@ final class Warning
} }
} }
public static function warn($errorMessage, $warningId, $messageLevel = null) public static function warn(string $errorMessage, int $warningId, ?int $messageLevel = null) : void
{ {
if (self::$warningHandler) { if (self::$warningHandler !== null) {
$fn = self::$warningHandler; $fn = self::$warningHandler;
$fn($errorMessage, $warningId); $fn($errorMessage, $warningId);
} elseif ((self::$enableWarnings & $warningId) > 0) { } elseif ((self::$enableWarnings & $warningId) > 0) {

View file

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace GraphQL\Exception;
use InvalidArgumentException;
use function gettype;
use function sprintf;
final class InvalidArgument extends InvalidArgumentException
{
/**
* @param mixed $argument
*/
public static function fromExpectedTypeAndArgument(string $expectedType, $argument) : self
{
return new self(sprintf('Expected type "%s", got "%s"', $expectedType, gettype($argument)));
}
}

View file

@ -14,7 +14,7 @@ use GraphQL\Type\Schema;
* Data that must be available at all points during query execution. * Data that must be available at all points during query execution.
* *
* Namely, schema of the type system that is currently executing, * Namely, schema of the type system that is currently executing,
* and the fragments defined in the query document * and the fragments defined in the query document.
* *
* @internal * @internal
*/ */
@ -45,28 +45,28 @@ class ExecutionContext
public $errors; public $errors;
/** @var PromiseAdapter */ /** @var PromiseAdapter */
public $promises; public $promiseAdapter;
public function __construct( public function __construct(
$schema, $schema,
$fragments, $fragments,
$root, $rootValue,
$contextValue, $contextValue,
$operation, $operation,
$variables, $variableValues,
$errors, $errors,
$fieldResolver, $fieldResolver,
$promiseAdapter $promiseAdapter
) { ) {
$this->schema = $schema; $this->schema = $schema;
$this->fragments = $fragments; $this->fragments = $fragments;
$this->rootValue = $root; $this->rootValue = $rootValue;
$this->contextValue = $contextValue; $this->contextValue = $contextValue;
$this->operation = $operation; $this->operation = $operation;
$this->variableValues = $variables; $this->variableValues = $variableValues;
$this->errors = $errors ?: []; $this->errors = $errors ?: [];
$this->fieldResolver = $fieldResolver; $this->fieldResolver = $fieldResolver;
$this->promises = $promiseAdapter; $this->promiseAdapter = $promiseAdapter;
} }
public function addError(Error $error) public function addError(Error $error)

View file

@ -154,7 +154,7 @@ class ExecutionResult implements JsonSerializable
} }
if (! empty($this->extensions)) { if (! empty($this->extensions)) {
$result['extensions'] = (array) $this->extensions; $result['extensions'] = $this->extensions;
} }
return $result; return $result;

View file

@ -157,31 +157,33 @@ class Executor
/** /**
* If a resolve function is not given, then a default resolve behavior is used * If a resolve function is not given, then a default resolve behavior is used
* which takes the property of the source object of the same name as the field * which takes the property of the root value of the same name as the field
* and returns it as the result, or if it's a function, returns the result * and returns it as the result, or if it's a function, returns the result
* of calling that function while passing along args and context. * of calling that function while passing along args and context.
* *
* @param mixed $source * @param mixed $objectValue
* @param mixed[] $args * @param mixed[] $args
* @param mixed|null $context * @param mixed|null $context
* *
* @return mixed|null * @return mixed|null
*/ */
public static function defaultFieldResolver($source, $args, $context, ResolveInfo $info) public static function defaultFieldResolver($objectValue, $args, $context, ResolveInfo $info)
{ {
$fieldName = $info->fieldName; $fieldName = $info->fieldName;
$property = null; $property = null;
if (is_array($source) || $source instanceof ArrayAccess) { if (is_array($objectValue) || $objectValue instanceof ArrayAccess) {
if (isset($source[$fieldName])) { if (isset($objectValue[$fieldName])) {
$property = $source[$fieldName]; $property = $objectValue[$fieldName];
} }
} elseif (is_object($source)) { } elseif (is_object($objectValue)) {
if (isset($source->{$fieldName})) { if (isset($objectValue->{$fieldName})) {
$property = $source->{$fieldName}; $property = $objectValue->{$fieldName};
} }
} }
return $property instanceof Closure ? $property($source, $args, $context, $info) : $property; return $property instanceof Closure
? $property($objectValue, $args, $context, $info)
: $property;
} }
} }

View file

@ -13,8 +13,6 @@ use function is_object;
use function method_exists; use function method_exists;
/** /**
* Class SyncPromise
*
* Simplistic (yet full-featured) implementation of Promises A+ spec for regular PHP `sync` mode * Simplistic (yet full-featured) implementation of Promises A+ spec for regular PHP `sync` mode
* (using queue to defer promises execution) * (using queue to defer promises execution)
*/ */
@ -40,16 +38,16 @@ class SyncPromise
*/ */
private $waiting = []; private $waiting = [];
public static function runQueue() public static function runQueue() : void
{ {
$q = self::$queue; $q = self::$queue;
while ($q && ! $q->isEmpty()) { while ($q !== null && ! $q->isEmpty()) {
$task = $q->dequeue(); $task = $q->dequeue();
$task(); $task();
} }
} }
public function resolve($value) public function resolve($value) : self
{ {
switch ($this->state) { switch ($this->state) {
case self::PENDING: case self::PENDING:
@ -85,7 +83,7 @@ class SyncPromise
return $this; return $this;
} }
public function reject($reason) public function reject($reason) : self
{ {
if (! $reason instanceof Exception && ! $reason instanceof Throwable) { if (! $reason instanceof Exception && ! $reason instanceof Throwable) {
throw new Exception('SyncPromise::reject() has to be called with an instance of \Throwable'); throw new Exception('SyncPromise::reject() has to be called with an instance of \Throwable');
@ -109,7 +107,7 @@ class SyncPromise
return $this; return $this;
} }
private function enqueueWaitingPromises() private function enqueueWaitingPromises() : void
{ {
Utils::invariant( Utils::invariant(
$this->state !== self::PENDING, $this->state !== self::PENDING,
@ -118,7 +116,7 @@ class SyncPromise
foreach ($this->waiting as $descriptor) { foreach ($this->waiting as $descriptor) {
self::getQueue()->enqueue(function () use ($descriptor) { self::getQueue()->enqueue(function () use ($descriptor) {
/** @var $promise self */ /** @var self $promise */
[$promise, $onFulfilled, $onRejected] = $descriptor; [$promise, $onFulfilled, $onRejected] = $descriptor;
if ($this->state === self::FULFILLED) { if ($this->state === self::FULFILLED) {
@ -147,17 +145,17 @@ class SyncPromise
$this->waiting = []; $this->waiting = [];
} }
public static function getQueue() public static function getQueue() : SplQueue
{ {
return self::$queue ?: self::$queue = new SplQueue(); return self::$queue ?: self::$queue = new SplQueue();
} }
public function then(?callable $onFulfilled = null, ?callable $onRejected = null) public function then(?callable $onFulfilled = null, ?callable $onRejected = null)
{ {
if ($this->state === self::REJECTED && ! $onRejected) { if ($this->state === self::REJECTED && $onRejected === null) {
return $this; return $this;
} }
if ($this->state === self::FULFILLED && ! $onFulfilled) { if ($this->state === self::FULFILLED && $onFulfilled === null) {
return $this; return $this;
} }
$tmp = new self(); $tmp = new self();

View file

@ -160,7 +160,9 @@ class SyncPromiseAdapter implements PromiseAdapter
if ($syncPromise->state === SyncPromise::FULFILLED) { if ($syncPromise->state === SyncPromise::FULFILLED) {
return $syncPromise->result; return $syncPromise->result;
} elseif ($syncPromise->state === SyncPromise::REJECTED) { }
if ($syncPromise->state === SyncPromise::REJECTED) {
throw $syncPromise->result; throw $syncPromise->result;
} }

View file

@ -134,30 +134,30 @@ class ReferenceExecutor implements ExecutorImplementation
) { ) {
$errors = []; $errors = [];
$fragments = []; $fragments = [];
/** @var OperationDefinitionNode $operation */ /** @var OperationDefinitionNode|null $operation */
$operation = null; $operation = null;
$hasMultipleAssumedOperations = false; $hasMultipleAssumedOperations = false;
foreach ($documentNode->definitions as $definition) { foreach ($documentNode->definitions as $definition) {
switch ($definition->kind) { switch (true) {
case NodeKind::OPERATION_DEFINITION: case $definition instanceof OperationDefinitionNode:
if (! $operationName && $operation) { if ($operationName === null && $operation !== null) {
$hasMultipleAssumedOperations = true; $hasMultipleAssumedOperations = true;
} }
if (! $operationName || if ($operationName === null ||
(isset($definition->name) && $definition->name->value === $operationName)) { (isset($definition->name) && $definition->name->value === $operationName)) {
$operation = $definition; $operation = $definition;
} }
break; break;
case NodeKind::FRAGMENT_DEFINITION: case $definition instanceof FragmentDefinitionNode:
$fragments[$definition->name->value] = $definition; $fragments[$definition->name->value] = $definition;
break; break;
} }
} }
if (! $operation) { if ($operation === null) {
if ($operationName) { if ($operationName === null) {
$errors[] = new Error(sprintf('Unknown operation named "%s".', $operationName));
} else {
$errors[] = new Error('Must provide an operation.'); $errors[] = new Error('Must provide an operation.');
} else {
$errors[] = new Error(sprintf('Unknown operation named "%s".', $operationName));
} }
} elseif ($hasMultipleAssumedOperations) { } elseif ($hasMultipleAssumedOperations) {
$errors[] = new Error( $errors[] = new Error(
@ -165,7 +165,7 @@ class ReferenceExecutor implements ExecutorImplementation
); );
} }
$variableValues = null; $variableValues = null;
if ($operation) { if ($operation !== null) {
[$coercionErrors, $coercedVariableValues] = Values::getVariableValues( [$coercionErrors, $coercedVariableValues] = Values::getVariableValues(
$schema, $schema,
$operation->variableDefinitions ?: [], $operation->variableDefinitions ?: [],
@ -182,6 +182,7 @@ class ReferenceExecutor implements ExecutorImplementation
} }
Utils::invariant($operation, 'Has operation if no errors.'); Utils::invariant($operation, 'Has operation if no errors.');
Utils::invariant($variableValues !== null, 'Has variables if no errors.'); Utils::invariant($variableValues !== null, 'Has variables if no errors.');
return new ExecutionContext( return new ExecutionContext(
$schema, $schema,
$fragments, $fragments,
@ -198,7 +199,7 @@ class ReferenceExecutor implements ExecutorImplementation
public function doExecute() : Promise public function doExecute() : Promise
{ {
// Return a Promise that will eventually resolve to the data described by // Return a Promise that will eventually resolve to the data described by
// The "Response" section of the GraphQL specification. // the "Response" section of the GraphQL specification.
// //
// If errors are encountered while executing a GraphQL field, only that // If errors are encountered while executing a GraphQL field, only that
// field and its descendants will be omitted, and sibling fields will still // field and its descendants will be omitted, and sibling fields will still
@ -206,11 +207,12 @@ class ReferenceExecutor implements ExecutorImplementation
// resolved Promise. // resolved Promise.
$data = $this->executeOperation($this->exeContext->operation, $this->exeContext->rootValue); $data = $this->executeOperation($this->exeContext->operation, $this->exeContext->rootValue);
$result = $this->buildResponse($data); $result = $this->buildResponse($data);
// Note: we deviate here from the reference implementation a bit by always returning promise // Note: we deviate here from the reference implementation a bit by always returning promise
// But for the "sync" case it is always fulfilled // But for the "sync" case it is always fulfilled
return $this->isPromise($result) return $this->isPromise($result)
? $result ? $result
: $this->exeContext->promises->createFulfilled($result); : $this->exeContext->promiseAdapter->createFulfilled($result);
} }
/** /**
@ -228,13 +230,14 @@ class ReferenceExecutor implements ExecutorImplementation
if ($data !== null) { if ($data !== null) {
$data = (array) $data; $data = (array) $data;
} }
return new ExecutionResult($data, $this->exeContext->errors); return new ExecutionResult($data, $this->exeContext->errors);
} }
/** /**
* Implements the "Evaluating operations" section of the spec. * Implements the "Evaluating operations" section of the spec.
* *
* @param mixed[] $rootValue * @param mixed $rootValue
* *
* @return Promise|stdClass|mixed[] * @return Promise|stdClass|mixed[]
*/ */
@ -249,21 +252,26 @@ class ReferenceExecutor implements ExecutorImplementation
// //
// Similar to completeValueCatchingError. // Similar to completeValueCatchingError.
try { try {
$result = $operation->operation === 'mutation' ? $result = $operation->operation === 'mutation'
$this->executeFieldsSerially($type, $rootValue, $path, $fields) : ? $this->executeFieldsSerially($type, $rootValue, $path, $fields)
$this->executeFields($type, $rootValue, $path, $fields); : $this->executeFields($type, $rootValue, $path, $fields);
if ($this->isPromise($result)) { if ($this->isPromise($result)) {
return $result->then( return $result->then(
null, null,
function ($error) { function ($error) {
$this->exeContext->addError($error); if ($error instanceof Error) {
return $this->exeContext->promises->createFulfilled(null); $this->exeContext->addError($error);
return $this->exeContext->promiseAdapter->createFulfilled(null);
}
} }
); );
} }
return $result; return $result;
} catch (Error $error) { } catch (Error $error) {
$this->exeContext->addError($error); $this->exeContext->addError($error);
return null; return null;
} }
} }
@ -280,30 +288,33 @@ class ReferenceExecutor implements ExecutorImplementation
switch ($operation->operation) { switch ($operation->operation) {
case 'query': case 'query':
$queryType = $schema->getQueryType(); $queryType = $schema->getQueryType();
if (! $queryType) { if ($queryType === null) {
throw new Error( throw new Error(
'Schema does not define the required query root type.', 'Schema does not define the required query root type.',
[$operation] [$operation]
); );
} }
return $queryType; return $queryType;
case 'mutation': case 'mutation':
$mutationType = $schema->getMutationType(); $mutationType = $schema->getMutationType();
if (! $mutationType) { if ($mutationType === null) {
throw new Error( throw new Error(
'Schema is not configured for mutations.', 'Schema is not configured for mutations.',
[$operation] [$operation]
); );
} }
return $mutationType; return $mutationType;
case 'subscription': case 'subscription':
$subscriptionType = $schema->getSubscriptionType(); $subscriptionType = $schema->getSubscriptionType();
if (! $subscriptionType) { if ($subscriptionType === null) {
throw new Error( throw new Error(
'Schema is not configured for subscriptions.', 'Schema is not configured for subscriptions.',
[$operation] [$operation]
); );
} }
return $subscriptionType; return $subscriptionType;
default: default:
throw new Error( throw new Error(
@ -334,8 +345,8 @@ class ReferenceExecutor implements ExecutorImplementation
) { ) {
$exeContext = $this->exeContext; $exeContext = $this->exeContext;
foreach ($selectionSet->selections as $selection) { foreach ($selectionSet->selections as $selection) {
switch ($selection->kind) { switch (true) {
case NodeKind::FIELD: case $selection instanceof FieldNode:
if (! $this->shouldIncludeNode($selection)) { if (! $this->shouldIncludeNode($selection)) {
break; break;
} }
@ -345,7 +356,7 @@ class ReferenceExecutor implements ExecutorImplementation
} }
$fields[$name][] = $selection; $fields[$name][] = $selection;
break; break;
case NodeKind::INLINE_FRAGMENT: case $selection instanceof InlineFragmentNode:
if (! $this->shouldIncludeNode($selection) || if (! $this->shouldIncludeNode($selection) ||
! $this->doesFragmentConditionMatch($selection, $runtimeType) ! $this->doesFragmentConditionMatch($selection, $runtimeType)
) { ) {
@ -358,7 +369,7 @@ class ReferenceExecutor implements ExecutorImplementation
$visitedFragmentNames $visitedFragmentNames
); );
break; break;
case NodeKind::FRAGMENT_SPREAD: case $selection instanceof FragmentSpreadNode:
$fragName = $selection->name->value; $fragName = $selection->name->value;
if (! empty($visitedFragmentNames[$fragName]) || ! $this->shouldIncludeNode($selection)) { if (! empty($visitedFragmentNames[$fragName]) || ! $this->shouldIncludeNode($selection)) {
break; break;
@ -366,7 +377,7 @@ class ReferenceExecutor implements ExecutorImplementation
$visitedFragmentNames[$fragName] = true; $visitedFragmentNames[$fragName] = true;
/** @var FragmentDefinitionNode|null $fragment */ /** @var FragmentDefinitionNode|null $fragment */
$fragment = $exeContext->fragments[$fragName] ?? null; $fragment = $exeContext->fragments[$fragName] ?? null;
if (! $fragment || ! $this->doesFragmentConditionMatch($fragment, $runtimeType)) { if ($fragment === null || ! $this->doesFragmentConditionMatch($fragment, $runtimeType)) {
break; break;
} }
$this->collectFields( $this->collectFields(
@ -378,6 +389,7 @@ class ReferenceExecutor implements ExecutorImplementation
break; break;
} }
} }
return $fields; return $fields;
} }
@ -386,10 +398,8 @@ class ReferenceExecutor implements ExecutorImplementation
* directives, where @skip has higher precedence than @include. * directives, where @skip has higher precedence than @include.
* *
* @param FragmentSpreadNode|FieldNode|InlineFragmentNode $node * @param FragmentSpreadNode|FieldNode|InlineFragmentNode $node
*
* @return bool
*/ */
private function shouldIncludeNode($node) private function shouldIncludeNode($node) : bool
{ {
$variableValues = $this->exeContext->variableValues; $variableValues = $this->exeContext->variableValues;
$skipDirective = Directive::skipDirective(); $skipDirective = Directive::skipDirective();
@ -407,20 +417,16 @@ class ReferenceExecutor implements ExecutorImplementation
$node, $node,
$variableValues $variableValues
); );
if (isset($include['if']) && $include['if'] === false) {
return false; return ! isset($include['if']) || $include['if'] !== false;
}
return true;
} }
/** /**
* Implements the logic to compute the key of a given fields entry * Implements the logic to compute the key of a given fields entry
*
* @return string
*/ */
private static function getFieldEntryKey(FieldNode $node) private static function getFieldEntryKey(FieldNode $node) : string
{ {
return $node->alias ? $node->alias->value : $node->name->value; return $node->alias === null ? $node->name->value : $node->alias->value;
} }
/** /**
@ -445,6 +451,7 @@ class ReferenceExecutor implements ExecutorImplementation
if ($conditionalType instanceof AbstractType) { if ($conditionalType instanceof AbstractType) {
return $this->exeContext->schema->isPossibleType($conditionalType, $type); return $this->exeContext->schema->isPossibleType($conditionalType, $type);
} }
return false; return false;
} }
@ -452,32 +459,34 @@ class ReferenceExecutor implements ExecutorImplementation
* Implements the "Evaluating selection sets" section of the spec * Implements the "Evaluating selection sets" section of the spec
* for "write" mode. * for "write" mode.
* *
* @param mixed[] $sourceValue * @param mixed $rootValue
* @param mixed[] $path * @param mixed[] $path
* @param ArrayObject $fields * @param ArrayObject $fields
* *
* @return Promise|stdClass|mixed[] * @return Promise|stdClass|mixed[]
*/ */
private function executeFieldsSerially(ObjectType $parentType, $sourceValue, $path, $fields) private function executeFieldsSerially(ObjectType $parentType, $rootValue, $path, $fields)
{ {
$result = $this->promiseReduce( $result = $this->promiseReduce(
array_keys($fields->getArrayCopy()), array_keys($fields->getArrayCopy()),
function ($results, $responseName) use ($path, $parentType, $sourceValue, $fields) { function ($results, $responseName) use ($path, $parentType, $rootValue, $fields) {
$fieldNodes = $fields[$responseName]; $fieldNodes = $fields[$responseName];
$fieldPath = $path; $fieldPath = $path;
$fieldPath[] = $responseName; $fieldPath[] = $responseName;
$result = $this->resolveField($parentType, $sourceValue, $fieldNodes, $fieldPath); $result = $this->resolveField($parentType, $rootValue, $fieldNodes, $fieldPath);
if ($result === self::$UNDEFINED) { if ($result === self::$UNDEFINED) {
return $results; return $results;
} }
$promise = $this->getPromise($result); $promise = $this->getPromise($result);
if ($promise) { if ($promise !== null) {
return $promise->then(static function ($resolvedResult) use ($responseName, $results) { return $promise->then(static function ($resolvedResult) use ($responseName, $results) {
$results[$responseName] = $resolvedResult; $results[$responseName] = $resolvedResult;
return $results; return $results;
}); });
} }
$results[$responseName] = $result; $results[$responseName] = $result;
return $results; return $results;
}, },
[] []
@ -487,32 +496,38 @@ class ReferenceExecutor implements ExecutorImplementation
return self::fixResultsIfEmptyArray($resolvedResults); return self::fixResultsIfEmptyArray($resolvedResults);
}); });
} }
return self::fixResultsIfEmptyArray($result); return self::fixResultsIfEmptyArray($result);
} }
/** /**
* Resolves the field on the given source object. In particular, this * Resolves the field on the given root value.
* figures out the value that the field returns by calling its resolve function,
* then calls completeValue to complete promises, serialize scalars, or execute
* the sub-selection-set for objects.
* *
* @param object|null $source * In particular, this figures out the value that the field returns
* by calling its resolve function, then calls completeValue to complete promises,
* serialize scalars, or execute the sub-selection-set for objects.
*
* @param mixed $rootValue
* @param FieldNode[] $fieldNodes * @param FieldNode[] $fieldNodes
* @param mixed[] $path * @param mixed[] $path
* *
* @return mixed[]|Exception|mixed|null * @return mixed[]|Exception|mixed|null
*/ */
private function resolveField(ObjectType $parentType, $source, $fieldNodes, $path) private function resolveField(ObjectType $parentType, $rootValue, $fieldNodes, $path)
{ {
$exeContext = $this->exeContext; $exeContext = $this->exeContext;
$fieldNode = $fieldNodes[0]; $fieldNode = $fieldNodes[0];
$fieldName = $fieldNode->name->value; $fieldName = $fieldNode->name->value;
$fieldDef = $this->getFieldDef($exeContext->schema, $parentType, $fieldName); $fieldDef = $this->getFieldDef($exeContext->schema, $parentType, $fieldName);
if (! $fieldDef) { if ($fieldDef === null) {
return self::$UNDEFINED; return self::$UNDEFINED;
} }
$returnType = $fieldDef->getType(); $returnType = $fieldDef->getType();
// The resolve function's optional third argument is a collection of // The resolve function's optional 3rd argument is a context value that
// is provided to every resolve function within an execution. It is commonly
// used to represent an authenticated user, or request-specific caches.
$context = $exeContext->contextValue;
// The resolve function's optional 4th argument is a collection of
// information about the current execution state. // information about the current execution state.
$info = new ResolveInfo( $info = new ResolveInfo(
$fieldName, $fieldName,
@ -533,17 +548,13 @@ class ReferenceExecutor implements ExecutorImplementation
} else { } else {
$resolveFn = $this->exeContext->fieldResolver; $resolveFn = $this->exeContext->fieldResolver;
} }
// The resolve function's optional third argument is a context value that
// is provided to every resolve function within an execution. It is commonly
// used to represent an authenticated user, or request-specific caches.
$context = $exeContext->contextValue;
// Get the resolve function, regardless of if its result is normal // Get the resolve function, regardless of if its result is normal
// or abrupt (error). // or abrupt (error).
$result = $this->resolveOrError( $result = $this->resolveOrError(
$fieldDef, $fieldDef,
$fieldNode, $fieldNode,
$resolveFn, $resolveFn,
$source, $rootValue,
$context, $context,
$info $info
); );
@ -554,23 +565,21 @@ class ReferenceExecutor implements ExecutorImplementation
$path, $path,
$result $result
); );
return $result; return $result;
} }
/** /**
* This method looks up the field on the given type definition. * This method looks up the field on the given type definition.
*
* It has special casing for the two introspection fields, __schema * It has special casing for the two introspection fields, __schema
* and __typename. __typename is special because it can always be * and __typename. __typename is special because it can always be
* queried as a field, even in situations where no other fields * queried as a field, even in situations where no other fields
* are allowed, like on a Union. __schema could get automatically * are allowed, like on a Union. __schema could get automatically
* added to the query type, but that would require mutating type * added to the query type, but that would require mutating type
* definitions, which would cause issues. * definitions, which would cause issues.
*
* @param string $fieldName
*
* @return FieldDefinition
*/ */
private function getFieldDef(Schema $schema, ObjectType $parentType, $fieldName) private function getFieldDef(Schema $schema, ObjectType $parentType, string $fieldName) : ?FieldDefinition
{ {
static $schemaMetaFieldDef, $typeMetaFieldDef, $typeNameMetaFieldDef; static $schemaMetaFieldDef, $typeMetaFieldDef, $typeNameMetaFieldDef;
$schemaMetaFieldDef = $schemaMetaFieldDef ?: Introspection::schemaMetaFieldDef(); $schemaMetaFieldDef = $schemaMetaFieldDef ?: Introspection::schemaMetaFieldDef();
@ -578,39 +587,45 @@ class ReferenceExecutor implements ExecutorImplementation
$typeNameMetaFieldDef = $typeNameMetaFieldDef ?: Introspection::typeNameMetaFieldDef(); $typeNameMetaFieldDef = $typeNameMetaFieldDef ?: Introspection::typeNameMetaFieldDef();
if ($fieldName === $schemaMetaFieldDef->name && $schema->getQueryType() === $parentType) { if ($fieldName === $schemaMetaFieldDef->name && $schema->getQueryType() === $parentType) {
return $schemaMetaFieldDef; return $schemaMetaFieldDef;
} elseif ($fieldName === $typeMetaFieldDef->name && $schema->getQueryType() === $parentType) { }
if ($fieldName === $typeMetaFieldDef->name && $schema->getQueryType() === $parentType) {
return $typeMetaFieldDef; return $typeMetaFieldDef;
} elseif ($fieldName === $typeNameMetaFieldDef->name) { }
if ($fieldName === $typeNameMetaFieldDef->name) {
return $typeNameMetaFieldDef; return $typeNameMetaFieldDef;
} }
$tmp = $parentType->getFields(); $tmp = $parentType->getFields();
return $tmp[$fieldName] ?? null; return $tmp[$fieldName] ?? null;
} }
/** /**
* Isolates the "ReturnOrAbrupt" behavior to not de-opt the `resolveField` * Isolates the "ReturnOrAbrupt" behavior to not de-opt the `resolveField` function.
* function. Returns the result of resolveFn or the abrupt-return Error object. * Returns the result of resolveFn or the abrupt-return Error object.
* *
* @param FieldDefinition $fieldDef * @param FieldDefinition $fieldDef
* @param FieldNode $fieldNode * @param FieldNode $fieldNode
* @param callable $resolveFn * @param callable $resolveFn
* @param mixed $source * @param mixed $rootValue
* @param mixed $context * @param mixed $context
* @param ResolveInfo $info * @param ResolveInfo $info
* *
* @return Throwable|Promise|mixed * @return Throwable|Promise|mixed
*/ */
private function resolveOrError($fieldDef, $fieldNode, $resolveFn, $source, $context, $info) private function resolveOrError($fieldDef, $fieldNode, $resolveFn, $rootValue, $context, $info)
{ {
try { try {
// Build hash of arguments from the field.arguments AST, using the // Build a map of arguments from the field.arguments AST, using the
// variables scope to fulfill any variable references. // variables scope to fulfill any variable references.
$args = Values::getArgumentValues( $args = Values::getArgumentValues(
$fieldDef, $fieldDef,
$fieldNode, $fieldNode,
$this->exeContext->variableValues $this->exeContext->variableValues
); );
return $resolveFn($source, $args, $context, $info);
return $resolveFn($rootValue, $args, $context, $info);
} catch (Exception $error) { } catch (Exception $error) {
return $error; return $error;
} catch (Throwable $error) { } catch (Throwable $error) {
@ -658,20 +673,23 @@ class ReferenceExecutor implements ExecutorImplementation
$result $result
); );
$promise = $this->getPromise($completed); $promise = $this->getPromise($completed);
if ($promise) { if ($promise !== null) {
return $promise->then( return $promise->then(
null, null,
function ($error) use ($exeContext) { function ($error) use ($exeContext) {
$exeContext->addError($error); $exeContext->addError($error);
return $this->exeContext->promises->createFulfilled(null);
return $this->exeContext->promiseAdapter->createFulfilled(null);
} }
); );
} }
return $completed; return $completed;
} catch (Error $err) { } catch (Error $err) {
// If `completeValueWithLocatedError` returned abruptly (threw an error), log the error // If `completeValueWithLocatedError` returned abruptly (threw an error), log the error
// and return null. // and return null.
$exeContext->addError($err); $exeContext->addError($err);
return null; return null;
} }
} }
@ -704,11 +722,11 @@ class ReferenceExecutor implements ExecutorImplementation
$result $result
); );
$promise = $this->getPromise($completed); $promise = $this->getPromise($completed);
if ($promise) { if ($promise !== null) {
return $promise->then( return $promise->then(
null, null,
function ($error) use ($fieldNodes, $path) { function ($error) use ($fieldNodes, $path) {
return $this->exeContext->promises->createRejected(Error::createLocatedError( return $this->exeContext->promiseAdapter->createRejected(Error::createLocatedError(
$error, $error,
$fieldNodes, $fieldNodes,
$path $path
@ -716,6 +734,7 @@ class ReferenceExecutor implements ExecutorImplementation
} }
); );
} }
return $completed; return $completed;
} catch (Exception $error) { } catch (Exception $error) {
throw Error::createLocatedError($error, $fieldNodes, $path); throw Error::createLocatedError($error, $fieldNodes, $path);
@ -763,7 +782,7 @@ class ReferenceExecutor implements ExecutorImplementation
) { ) {
$promise = $this->getPromise($result); $promise = $this->getPromise($result);
// If result is a Promise, apply-lift over completeValue. // If result is a Promise, apply-lift over completeValue.
if ($promise) { if ($promise !== null) {
return $promise->then(function (&$resolved) use ($returnType, $fieldNodes, $info, $path) { return $promise->then(function (&$resolved) use ($returnType, $fieldNodes, $info, $path) {
return $this->completeValue($returnType, $fieldNodes, $info, $path, $resolved); return $this->completeValue($returnType, $fieldNodes, $info, $path, $resolved);
}); });
@ -786,6 +805,7 @@ class ReferenceExecutor implements ExecutorImplementation
'Cannot return null for non-nullable field ' . $info->parentType . '.' . $info->fieldName . '.' 'Cannot return null for non-nullable field ' . $info->parentType . '.' . $info->fieldName . '.'
); );
} }
return $completed; return $completed;
} }
// If result is null-like, return null. // If result is null-like, return null.
@ -800,7 +820,7 @@ class ReferenceExecutor implements ExecutorImplementation
// instance than `resolveType` or $field->getType() or $arg->getType() // instance than `resolveType` or $field->getType() or $arg->getType()
if ($returnType !== $this->exeContext->schema->getType($returnType->name)) { if ($returnType !== $this->exeContext->schema->getType($returnType->name)) {
$hint = ''; $hint = '';
if ($this->exeContext->schema->getConfig()->typeLoader) { if ($this->exeContext->schema->getConfig()->typeLoader !== null) {
$hint = sprintf( $hint = sprintf(
'Make sure that type loader returns the same instance as defined in %s.%s', 'Make sure that type loader returns the same instance as defined in %s.%s',
$info->parentType, $info->parentType,
@ -838,7 +858,7 @@ class ReferenceExecutor implements ExecutorImplementation
*/ */
private function isPromise($value) private function isPromise($value)
{ {
return $value instanceof Promise || $this->exeContext->promises->isThenable($value); return $value instanceof Promise || $this->exeContext->promiseAdapter->isThenable($value);
} }
/** /**
@ -854,17 +874,19 @@ class ReferenceExecutor implements ExecutorImplementation
if ($value === null || $value instanceof Promise) { if ($value === null || $value instanceof Promise) {
return $value; return $value;
} }
if ($this->exeContext->promises->isThenable($value)) { if ($this->exeContext->promiseAdapter->isThenable($value)) {
$promise = $this->exeContext->promises->convertThenable($value); $promise = $this->exeContext->promiseAdapter->convertThenable($value);
if (! $promise instanceof Promise) { if (! $promise instanceof Promise) {
throw new InvariantViolation(sprintf( throw new InvariantViolation(sprintf(
'%s::convertThenable is expected to return instance of GraphQL\Executor\Promise\Promise, got: %s', '%s::convertThenable is expected to return instance of GraphQL\Executor\Promise\Promise, got: %s',
get_class($this->exeContext->promises), get_class($this->exeContext->promiseAdapter),
Utils::printSafe($promise) Utils::printSafe($promise)
)); ));
} }
return $promise; return $promise;
} }
return null; return null;
} }
@ -878,7 +900,7 @@ class ReferenceExecutor implements ExecutorImplementation
* @param mixed[] $values * @param mixed[] $values
* @param Promise|mixed|null $initialValue * @param Promise|mixed|null $initialValue
* *
* @return mixed[] * @return mixed
*/ */
private function promiseReduce(array $values, callable $callback, $initialValue) private function promiseReduce(array $values, callable $callback, $initialValue)
{ {
@ -886,11 +908,12 @@ class ReferenceExecutor implements ExecutorImplementation
$values, $values,
function ($previous, $value) use ($callback) { function ($previous, $value) use ($callback) {
$promise = $this->getPromise($previous); $promise = $this->getPromise($previous);
if ($promise) { if ($promise !== null) {
return $promise->then(static function ($resolved) use ($callback, $value) { return $promise->then(static function ($resolved) use ($callback, $value) {
return $callback($resolved, $value); return $callback($resolved, $value);
}); });
} }
return $callback($previous, $value); return $callback($previous, $value);
}, },
$initialValue $initialValue
@ -898,37 +921,40 @@ class ReferenceExecutor implements ExecutorImplementation
} }
/** /**
* Complete a list value by completing each item in the list with the * Complete a list value by completing each item in the list with the inner type.
* inner type
* *
* @param FieldNode[] $fieldNodes * @param FieldNode[] $fieldNodes
* @param mixed[] $path * @param mixed[] $path
* @param mixed $result * @param mixed[]|Traversable $results
* *
* @return mixed[]|Promise * @return mixed[]|Promise
* *
* @throws Exception * @throws Exception
*/ */
private function completeListValue(ListOfType $returnType, $fieldNodes, ResolveInfo $info, $path, &$result) private function completeListValue(ListOfType $returnType, $fieldNodes, ResolveInfo $info, $path, &$results)
{ {
$itemType = $returnType->getWrappedType(); $itemType = $returnType->getWrappedType();
Utils::invariant( Utils::invariant(
is_array($result) || $result instanceof Traversable, is_array($results) || $results instanceof Traversable,
'User Error: expected iterable, but did not find one for field ' . $info->parentType . '.' . $info->fieldName . '.' 'User Error: expected iterable, but did not find one for field ' . $info->parentType . '.' . $info->fieldName . '.'
); );
$containsPromise = false; $containsPromise = false;
$i = 0; $i = 0;
$completedItems = []; $completedItems = [];
foreach ($result as $item) { foreach ($results as $item) {
$fieldPath = $path; $fieldPath = $path;
$fieldPath[] = $i++; $fieldPath[] = $i++;
$info->path = $fieldPath;
$completedItem = $this->completeValueCatchingError($itemType, $fieldNodes, $info, $fieldPath, $item); $completedItem = $this->completeValueCatchingError($itemType, $fieldNodes, $info, $fieldPath, $item);
if (! $containsPromise && $this->getPromise($completedItem)) { if (! $containsPromise && $this->getPromise($completedItem) !== null) {
$containsPromise = true; $containsPromise = true;
} }
$completedItems[] = $completedItem; $completedItems[] = $completedItem;
} }
return $containsPromise ? $this->exeContext->promises->all($completedItems) : $completedItems;
return $containsPromise
? $this->exeContext->promiseAdapter->all($completedItems)
: $completedItems;
} }
/** /**
@ -979,7 +1005,7 @@ class ReferenceExecutor implements ExecutorImplementation
$runtimeType = self::defaultTypeResolver($result, $exeContext->contextValue, $info, $returnType); $runtimeType = self::defaultTypeResolver($result, $exeContext->contextValue, $info, $returnType);
} }
$promise = $this->getPromise($runtimeType); $promise = $this->getPromise($runtimeType);
if ($promise) { if ($promise !== null) {
return $promise->then(function ($resolvedRuntimeType) use ( return $promise->then(function ($resolvedRuntimeType) use (
$returnType, $returnType,
$fieldNodes, $fieldNodes,
@ -1001,6 +1027,7 @@ class ReferenceExecutor implements ExecutorImplementation
); );
}); });
} }
return $this->completeObjectValue( return $this->completeObjectValue(
$this->ensureValidRuntimeType( $this->ensureValidRuntimeType(
$runtimeType, $runtimeType,
@ -1040,7 +1067,8 @@ class ReferenceExecutor implements ExecutorImplementation
) { ) {
return $value['__typename']; return $value['__typename'];
} }
if ($abstractType instanceof InterfaceType && $info->schema->getConfig()->typeLoader) {
if ($abstractType instanceof InterfaceType && $info->schema->getConfig()->typeLoader !== null) {
Warning::warnOnce( Warning::warnOnce(
sprintf( sprintf(
'GraphQL Interface Type `%s` returned `null` from its `resolveType` function ' . 'GraphQL Interface Type `%s` returned `null` from its `resolveType` function ' .
@ -1062,23 +1090,25 @@ class ReferenceExecutor implements ExecutorImplementation
continue; continue;
} }
$promise = $this->getPromise($isTypeOfResult); $promise = $this->getPromise($isTypeOfResult);
if ($promise) { if ($promise !== null) {
$promisedIsTypeOfResults[$index] = $promise; $promisedIsTypeOfResults[$index] = $promise;
} elseif ($isTypeOfResult) { } elseif ($isTypeOfResult) {
return $type; return $type;
} }
} }
if (! empty($promisedIsTypeOfResults)) { if (! empty($promisedIsTypeOfResults)) {
return $this->exeContext->promises->all($promisedIsTypeOfResults) return $this->exeContext->promiseAdapter->all($promisedIsTypeOfResults)
->then(static function ($isTypeOfResults) use ($possibleTypes) { ->then(static function ($isTypeOfResults) use ($possibleTypes) {
foreach ($isTypeOfResults as $index => $result) { foreach ($isTypeOfResults as $index => $result) {
if ($result) { if ($result) {
return $possibleTypes[$index]; return $possibleTypes[$index];
} }
} }
return null; return null;
}); });
} }
return null; return null;
} }
@ -1101,7 +1131,7 @@ class ReferenceExecutor implements ExecutorImplementation
$isTypeOf = $returnType->isTypeOf($result, $this->exeContext->contextValue, $info); $isTypeOf = $returnType->isTypeOf($result, $this->exeContext->contextValue, $info);
if ($isTypeOf !== null) { if ($isTypeOf !== null) {
$promise = $this->getPromise($isTypeOf); $promise = $this->getPromise($isTypeOf);
if ($promise) { if ($promise !== null) {
return $promise->then(function ($isTypeOfResult) use ( return $promise->then(function ($isTypeOfResult) use (
$returnType, $returnType,
$fieldNodes, $fieldNodes,
@ -1111,6 +1141,7 @@ class ReferenceExecutor implements ExecutorImplementation
if (! $isTypeOfResult) { if (! $isTypeOfResult) {
throw $this->invalidReturnTypeError($returnType, $result, $fieldNodes); throw $this->invalidReturnTypeError($returnType, $result, $fieldNodes);
} }
return $this->collectAndExecuteSubfields( return $this->collectAndExecuteSubfields(
$returnType, $returnType,
$fieldNodes, $fieldNodes,
@ -1123,6 +1154,7 @@ class ReferenceExecutor implements ExecutorImplementation
throw $this->invalidReturnTypeError($returnType, $result, $fieldNodes); throw $this->invalidReturnTypeError($returnType, $result, $fieldNodes);
} }
} }
return $this->collectAndExecuteSubfields( return $this->collectAndExecuteSubfields(
$returnType, $returnType,
$fieldNodes, $fieldNodes,
@ -1151,7 +1183,7 @@ class ReferenceExecutor implements ExecutorImplementation
/** /**
* @param FieldNode[] $fieldNodes * @param FieldNode[] $fieldNodes
* @param mixed[] $path * @param mixed[] $path
* @param mixed[] $result * @param mixed $result
* *
* @return mixed[]|Promise|stdClass * @return mixed[]|Promise|stdClass
* *
@ -1164,6 +1196,7 @@ class ReferenceExecutor implements ExecutorImplementation
&$result &$result
) { ) {
$subFieldNodes = $this->collectSubFields($returnType, $fieldNodes); $subFieldNodes = $this->collectSubFields($returnType, $fieldNodes);
return $this->executeFields($returnType, $result, $path, $subFieldNodes); return $this->executeFields($returnType, $result, $path, $subFieldNodes);
} }
@ -1189,6 +1222,7 @@ class ReferenceExecutor implements ExecutorImplementation
} }
$this->subFieldCache[$returnType][$fieldNodes] = $subFieldNodes; $this->subFieldCache[$returnType][$fieldNodes] = $subFieldNodes;
} }
return $this->subFieldCache[$returnType][$fieldNodes]; return $this->subFieldCache[$returnType][$fieldNodes];
} }
@ -1196,24 +1230,24 @@ class ReferenceExecutor implements ExecutorImplementation
* Implements the "Evaluating selection sets" section of the spec * Implements the "Evaluating selection sets" section of the spec
* for "read" mode. * for "read" mode.
* *
* @param mixed|null $source * @param mixed $rootValue
* @param mixed[] $path * @param mixed[] $path
* @param ArrayObject $fields * @param ArrayObject $fields
* *
* @return Promise|stdClass|mixed[] * @return Promise|stdClass|mixed[]
*/ */
private function executeFields(ObjectType $parentType, $source, $path, $fields) private function executeFields(ObjectType $parentType, $rootValue, $path, $fields)
{ {
$containsPromise = false; $containsPromise = false;
$finalResults = []; $finalResults = [];
foreach ($fields as $responseName => $fieldNodes) { foreach ($fields as $responseName => $fieldNodes) {
$fieldPath = $path; $fieldPath = $path;
$fieldPath[] = $responseName; $fieldPath[] = $responseName;
$result = $this->resolveField($parentType, $source, $fieldNodes, $fieldPath); $result = $this->resolveField($parentType, $rootValue, $fieldNodes, $fieldPath);
if ($result === self::$UNDEFINED) { if ($result === self::$UNDEFINED) {
continue; continue;
} }
if (! $containsPromise && $this->getPromise($result)) { if (! $containsPromise && $this->getPromise($result) !== null) {
$containsPromise = true; $containsPromise = true;
} }
$finalResults[$responseName] = $result; $finalResults[$responseName] = $result;
@ -1222,6 +1256,7 @@ class ReferenceExecutor implements ExecutorImplementation
if (! $containsPromise) { if (! $containsPromise) {
return self::fixResultsIfEmptyArray($finalResults); return self::fixResultsIfEmptyArray($finalResults);
} }
// Otherwise, results is a map from field name to the result // Otherwise, results is a map from field name to the result
// of resolving that field, which is possibly a promise. Return // of resolving that field, which is possibly a promise. Return
// a promise that will return this same map, but with any // a promise that will return this same map, but with any
@ -1241,6 +1276,7 @@ class ReferenceExecutor implements ExecutorImplementation
if ($results === []) { if ($results === []) {
return new stdClass(); return new stdClass();
} }
return $results; return $results;
} }
@ -1259,19 +1295,20 @@ class ReferenceExecutor implements ExecutorImplementation
{ {
$keys = array_keys($assoc); $keys = array_keys($assoc);
$valuesAndPromises = array_values($assoc); $valuesAndPromises = array_values($assoc);
$promise = $this->exeContext->promises->all($valuesAndPromises); $promise = $this->exeContext->promiseAdapter->all($valuesAndPromises);
return $promise->then(static function ($values) use ($keys) { return $promise->then(static function ($values) use ($keys) {
$resolvedResults = []; $resolvedResults = [];
foreach ($values as $i => $value) { foreach ($values as $i => $value) {
$resolvedResults[$keys[$i]] = $value; $resolvedResults[$keys[$i]] = $value;
} }
return self::fixResultsIfEmptyArray($resolvedResults); return self::fixResultsIfEmptyArray($resolvedResults);
}); });
} }
/** /**
* @param string|ObjectType|null $runtimeTypeOrName * @param string|ObjectType|null $runtimeTypeOrName
* @param FieldNode[] $fieldNodes
* @param mixed $result * @param mixed $result
* *
* @return ObjectType * @return ObjectType
@ -1282,9 +1319,9 @@ class ReferenceExecutor implements ExecutorImplementation
ResolveInfo $info, ResolveInfo $info,
&$result &$result
) { ) {
$runtimeType = is_string($runtimeTypeOrName) ? $runtimeType = is_string($runtimeTypeOrName)
$this->exeContext->schema->getType($runtimeTypeOrName) : ? $this->exeContext->schema->getType($runtimeTypeOrName)
$runtimeTypeOrName; : $runtimeTypeOrName;
if (! $runtimeType instanceof ObjectType) { if (! $runtimeType instanceof ObjectType) {
throw new InvariantViolation( throw new InvariantViolation(
sprintf( sprintf(
@ -1318,6 +1355,7 @@ class ReferenceExecutor implements ExecutorImplementation
) )
); );
} }
return $runtimeType; return $runtimeType;
} }
} }

View file

@ -92,7 +92,7 @@ class Values
), ),
[$varDefNode] [$varDefNode]
); );
} elseif ($varDefNode->defaultValue) { } elseif ($varDefNode->defaultValue !== null) {
$coercedValues[$varName] = AST::valueFromAST($varDefNode->defaultValue, $varType); $coercedValues[$varName] = AST::valueFromAST($varDefNode->defaultValue, $varType);
} }
} }
@ -196,7 +196,7 @@ class Values
$argType = $argumentDefinition->getType(); $argType = $argumentDefinition->getType();
$argumentValueNode = $argumentValueMap[$name] ?? null; $argumentValueNode = $argumentValueMap[$name] ?? null;
if (! $argumentValueNode) { if ($argumentValueNode === null) {
if ($argumentDefinition->defaultValueExists()) { if ($argumentDefinition->defaultValueExists()) {
$coercedValues[$name] = $argumentDefinition->defaultValue; $coercedValues[$name] = $argumentDefinition->defaultValue;
} elseif ($argType instanceof NonNull) { } elseif ($argType instanceof NonNull) {
@ -209,7 +209,7 @@ class Values
} elseif ($argumentValueNode instanceof VariableNode) { } elseif ($argumentValueNode instanceof VariableNode) {
$variableName = $argumentValueNode->name->value; $variableName = $argumentValueNode->name->value;
if ($variableValues && array_key_exists($variableName, $variableValues)) { if ($variableValues !== null && array_key_exists($variableName, $variableValues)) {
// Note: this does not check that this variable value is correct. // Note: this does not check that this variable value is correct.
// This assumes that this query has been validated and the variable // This assumes that this query has been validated and the variable
// usage here is of the correct type. // usage here is of the correct type.
@ -273,6 +273,7 @@ class Values
return $error->getMessage(); return $error->getMessage();
}, },
$errors $errors
) : []; )
: [];
} }
} }

View file

@ -64,7 +64,7 @@ class Collector
foreach ($documentNode->definitions as $definitionNode) { foreach ($documentNode->definitions as $definitionNode) {
/** @var DefinitionNode|Node $definitionNode */ /** @var DefinitionNode|Node $definitionNode */
if ($definitionNode->kind === NodeKind::OPERATION_DEFINITION) { if ($definitionNode instanceof OperationDefinitionNode) {
/** @var OperationDefinitionNode $definitionNode */ /** @var OperationDefinitionNode $definitionNode */
if ($operationName === null && $this->operation !== null) { if ($operationName === null && $this->operation !== null) {
$hasMultipleAssumedOperations = true; $hasMultipleAssumedOperations = true;
@ -74,7 +74,7 @@ class Collector
) { ) {
$this->operation = $definitionNode; $this->operation = $definitionNode;
} }
} elseif ($definitionNode->kind === NodeKind::FRAGMENT_DEFINITION) { } elseif ($definitionNode instanceof FragmentDefinitionNode) {
/** @var FragmentDefinitionNode $definitionNode */ /** @var FragmentDefinitionNode $definitionNode */
$this->fragments[$definitionNode->name->value] = $definitionNode; $this->fragments[$definitionNode->name->value] = $definitionNode;
} }
@ -86,11 +86,13 @@ class Collector
} else { } else {
$this->runtime->addError(new Error('Must provide an operation.')); $this->runtime->addError(new Error('Must provide an operation.'));
} }
return; return;
} }
if ($hasMultipleAssumedOperations) { if ($hasMultipleAssumedOperations) {
$this->runtime->addError(new Error('Must provide operation name if query contains multiple operations.')); $this->runtime->addError(new Error('Must provide operation name if query contains multiple operations.'));
return; return;
} }
@ -194,24 +196,26 @@ class Collector
} }
} }
if ($selection->kind === NodeKind::FIELD) { if ($selection instanceof FieldNode) {
/** @var FieldNode $selection */ /** @var FieldNode $selection */
$resultName = $selection->alias ? $selection->alias->value : $selection->name->value; $resultName = $selection->alias === null ? $selection->name->value : $selection->alias->value;
if (! isset($this->fields[$resultName])) { if (! isset($this->fields[$resultName])) {
$this->fields[$resultName] = []; $this->fields[$resultName] = [];
} }
$this->fields[$resultName][] = $selection; $this->fields[$resultName][] = $selection;
} elseif ($selection->kind === NodeKind::FRAGMENT_SPREAD) { } elseif ($selection instanceof FragmentSpreadNode) {
/** @var FragmentSpreadNode $selection */ /** @var FragmentSpreadNode $selection */
$fragmentName = $selection->name->value; $fragmentName = $selection->name->value;
if (isset($this->visitedFragments[$fragmentName])) { if (isset($this->visitedFragments[$fragmentName])) {
continue; continue;
} elseif (! isset($this->fragments[$fragmentName])) { }
if (! isset($this->fragments[$fragmentName])) {
$this->runtime->addError(new Error( $this->runtime->addError(new Error(
sprintf('Fragment "%s" does not exist.', $fragmentName), sprintf('Fragment "%s" does not exist.', $fragmentName),
$selection $selection
@ -245,7 +249,7 @@ class Collector
} }
$this->doCollectFields($runtimeType, $fragmentDefinition->selectionSet); $this->doCollectFields($runtimeType, $fragmentDefinition->selectionSet);
} elseif ($selection->kind === NodeKind::INLINE_FRAGMENT) { } elseif ($selection instanceof InlineFragmentNode) {
/** @var InlineFragmentNode $selection */ /** @var InlineFragmentNode $selection */
if ($selection->typeCondition !== null) { if ($selection->typeCondition !== null) {

View file

@ -147,6 +147,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
if ($emptyObjectAsStdClass && empty($array)) { if ($emptyObjectAsStdClass && empty($array)) {
return new stdClass(); return new stdClass();
} }
return $array; return $array;
} }
@ -155,6 +156,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
foreach ($value as $key => $item) { foreach ($value as $key => $item) {
$array[$key] = self::resultToArray($item); $array[$key] = self::resultToArray($item);
} }
return $array; return $array;
} }
@ -291,13 +293,17 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
$strand->stack[$strand->depth++] = $strand->current; $strand->stack[$strand->depth++] = $strand->current;
$strand->current = $value; $strand->current = $value;
goto START; goto START;
} elseif ($this->promiseAdapter->isThenable($value)) { } elseif ($this->isPromise($value)) {
// !!! increment pending before calling ->then() as it may invoke the callback right away // !!! increment pending before calling ->then() as it may invoke the callback right away
++$this->pending; ++$this->pending;
if (! $value instanceof Promise) {
$value = $this->promiseAdapter->convertThenable($value);
}
$this->promiseAdapter $this->promiseAdapter
->convertThenable($value)
->then( ->then(
$value,
function ($value) use ($strand) { function ($value) use ($strand) {
$strand->success = true; $strand->success = true;
$strand->value = $value; $strand->value = $value;
@ -365,6 +371,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
// short-circuit evaluation for __typename // short-circuit evaluation for __typename
if ($ctx->shared->fieldName === Introspection::TYPE_NAME_FIELD_NAME) { if ($ctx->shared->fieldName === Introspection::TYPE_NAME_FIELD_NAME) {
$ctx->result->{$ctx->shared->resultName} = $ctx->type->name; $ctx->result->{$ctx->shared->resultName} = $ctx->type->name;
return; return;
} }
@ -475,7 +482,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
private function completeValueFast(CoroutineContext $ctx, Type $type, $value, array $path, &$returnValue) : bool private function completeValueFast(CoroutineContext $ctx, Type $type, $value, array $path, &$returnValue) : bool
{ {
// special handling of Throwable inherited from JS reference implementation, but makes no sense in this PHP // special handling of Throwable inherited from JS reference implementation, but makes no sense in this PHP
if ($this->promiseAdapter->isThenable($value) || $value instanceof Throwable) { if ($this->isPromise($value) || $value instanceof Throwable) {
return false; return false;
} }
@ -491,7 +498,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
if ($type !== $this->schema->getType($type->name)) { if ($type !== $this->schema->getType($type->name)) {
$hint = ''; $hint = '';
if ($this->schema->getConfig()->typeLoader) { if ($this->schema->getConfig()->typeLoader !== null) {
$hint = sprintf( $hint = sprintf(
'Make sure that type loader returns the same instance as defined in %s.%s', 'Make sure that type loader returns the same instance as defined in %s.%s',
$ctx->type, $ctx->type,
@ -571,7 +578,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
// !!! $value might be promise, yield to resolve // !!! $value might be promise, yield to resolve
try { try {
if ($this->promiseAdapter->isThenable($value)) { if ($this->isPromise($value)) {
$value = yield $value; $value = yield $value;
} }
} catch (Throwable $reason) { } catch (Throwable $reason) {
@ -613,8 +620,9 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
foreach ($value as $itemValue) { foreach ($value as $itemValue) {
++$index; ++$index;
$itemPath = $path; $itemPath = $path;
$itemPath[] = $index; // !!! use arrays COW semantics $itemPath[] = $index; // !!! use arrays COW semantics
$ctx->resolveInfo->path = $itemPath;
try { try {
if (! $this->completeValueFast($ctx, $itemType, $itemValue, $itemPath, $itemReturnValue)) { if (! $this->completeValueFast($ctx, $itemType, $itemValue, $itemPath, $itemReturnValue)) {
@ -639,7 +647,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
} else { } else {
if ($type !== $this->schema->getType($type->name)) { if ($type !== $this->schema->getType($type->name)) {
$hint = ''; $hint = '';
if ($this->schema->getConfig()->typeLoader) { if ($this->schema->getConfig()->typeLoader !== null) {
$hint = sprintf( $hint = sprintf(
'Make sure that type loader returns the same instance as defined in %s.%s', 'Make sure that type loader returns the same instance as defined in %s.%s',
$ctx->type, $ctx->type,
@ -813,7 +821,10 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
} else { } else {
$childContexts = []; $childContexts = [];
foreach ($this->collector->collectFields($objectType, $ctx->shared->mergedSelectionSet ?? $this->mergeSelectionSets($ctx)) as $childShared) { foreach ($this->collector->collectFields(
$objectType,
$ctx->shared->mergedSelectionSet ?? $this->mergeSelectionSets($ctx)
) as $childShared) {
/** @var CoroutineContextShared $childShared */ /** @var CoroutineContextShared $childShared */
$childPath = $path; $childPath = $path;
@ -897,7 +908,7 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
return $this->schema->getType($value['__typename']); return $this->schema->getType($value['__typename']);
} }
if ($abstractType instanceof InterfaceType && $this->schema->getConfig()->typeLoader) { if ($abstractType instanceof InterfaceType && $this->schema->getConfig()->typeLoader !== null) {
Warning::warnOnce( Warning::warnOnce(
sprintf( sprintf(
'GraphQL Interface Type `%s` returned `null` from its `resolveType` function ' . 'GraphQL Interface Type `%s` returned `null` from its `resolveType` function ' .
@ -928,4 +939,14 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
return $selectedType; return $selectedType;
} }
/**
* @param mixed $value
*
* @return bool
*/
private function isPromise($value)
{
return $value instanceof Promise || $this->promiseAdapter->isThenable($value);
}
} }

View file

@ -9,6 +9,6 @@ class ListTypeNode extends Node implements TypeNode
/** @var string */ /** @var string */
public $kind = NodeKind::LIST_TYPE; public $kind = NodeKind::LIST_TYPE;
/** @var Node */ /** @var TypeNode */
public $type; public $type;
} }

View file

@ -59,6 +59,7 @@ class Location
$tmp = new static(); $tmp = new static();
$tmp->start = $start; $tmp->start = $start;
$tmp->end = $end; $tmp->end = $end;
return $tmp; return $tmp;
} }
@ -68,7 +69,7 @@ class Location
$this->endToken = $endToken; $this->endToken = $endToken;
$this->source = $source; $this->source = $source;
if (! $startToken || ! $endToken) { if ($startToken === null || $endToken === null) {
return; return;
} }

View file

@ -106,7 +106,7 @@ abstract class Node
$tmp = (array) $this; $tmp = (array) $this;
if ($this->loc) { if ($this->loc !== null) {
$tmp['loc'] = [ $tmp['loc'] = [
'start' => $this->loc->start, 'start' => $this->loc->start,
'end' => $this->loc->end, 'end' => $this->loc->end,
@ -125,7 +125,7 @@ abstract class Node
'kind' => $node->kind, 'kind' => $node->kind,
]; ];
if ($node->loc) { if ($node->loc !== null) {
$result['loc'] = [ $result['loc'] = [
'start' => $node->loc->start, 'start' => $node->loc->start,
'end' => $node->loc->end, 'end' => $node->loc->end,

View file

@ -105,6 +105,7 @@ class NodeList implements ArrayAccess, IteratorAggregate, Countable
if ($list instanceof self) { if ($list instanceof self) {
$list = $list->nodes; $list = $list->nodes;
} }
return new NodeList(array_merge($this->nodes, $list)); return new NodeList(array_merge($this->nodes, $list));
} }
@ -113,9 +114,8 @@ class NodeList implements ArrayAccess, IteratorAggregate, Countable
*/ */
public function getIterator() public function getIterator()
{ {
$count = count($this->nodes); foreach ($this->nodes as $key => $_) {
for ($i = 0; $i < $count; $i++) { yield $this->offsetGet($key);
yield $this->offsetGet($i);
} }
} }

View file

@ -9,6 +9,6 @@ class NonNullTypeNode extends Node implements TypeNode
/** @var string */ /** @var string */
public $kind = NodeKind::NON_NULL_TYPE; public $kind = NodeKind::NON_NULL_TYPE;
/** @var NameNode | ListTypeNode */ /** @var NamedTypeNode | ListTypeNode */
public $type; public $type;
} }

View file

@ -23,6 +23,22 @@ use function preg_match;
*/ */
class Lexer class Lexer
{ {
private const TOKEN_BANG = 33;
private const TOKEN_HASH = 35;
private const TOKEN_DOLLAR = 36;
private const TOKEN_AMP = 38;
private const TOKEN_PAREN_L = 40;
private const TOKEN_PAREN_R = 41;
private const TOKEN_DOT = 46;
private const TOKEN_COLON = 58;
private const TOKEN_EQUALS = 61;
private const TOKEN_AT = 64;
private const TOKEN_BRACKET_L = 91;
private const TOKEN_BRACKET_R = 93;
private const TOKEN_BRACE_L = 123;
private const TOKEN_PIPE = 124;
private const TOKEN_BRACE_R = 125;
/** @var Source */ /** @var Source */
public $source; public $source;
@ -92,7 +108,8 @@ class Lexer
*/ */
public function advance() public function advance()
{ {
$this->lastToken = $this->token; $this->lastToken = $this->token;
return $this->token = $this->lookahead(); return $this->token = $this->lookahead();
} }
@ -131,44 +148,45 @@ class Lexer
[, $code, $bytes] = $this->readChar(true); [, $code, $bytes] = $this->readChar(true);
switch ($code) { switch ($code) {
case 33: // ! case self::TOKEN_BANG:
return new Token(Token::BANG, $position, $position + 1, $line, $col, $prev); return new Token(Token::BANG, $position, $position + 1, $line, $col, $prev);
case 35: // # case self::TOKEN_HASH: // #
$this->moveStringCursor(-1, -1 * $bytes); $this->moveStringCursor(-1, -1 * $bytes);
return $this->readComment($line, $col, $prev); return $this->readComment($line, $col, $prev);
case 36: // $ case self::TOKEN_DOLLAR:
return new Token(Token::DOLLAR, $position, $position + 1, $line, $col, $prev); return new Token(Token::DOLLAR, $position, $position + 1, $line, $col, $prev);
case 38: // & case self::TOKEN_AMP:
return new Token(Token::AMP, $position, $position + 1, $line, $col, $prev); return new Token(Token::AMP, $position, $position + 1, $line, $col, $prev);
case 40: // ( case self::TOKEN_PAREN_L:
return new Token(Token::PAREN_L, $position, $position + 1, $line, $col, $prev); return new Token(Token::PAREN_L, $position, $position + 1, $line, $col, $prev);
case 41: // ) case self::TOKEN_PAREN_R:
return new Token(Token::PAREN_R, $position, $position + 1, $line, $col, $prev); return new Token(Token::PAREN_R, $position, $position + 1, $line, $col, $prev);
case 46: // . case self::TOKEN_DOT: // .
[, $charCode1] = $this->readChar(true); [, $charCode1] = $this->readChar(true);
[, $charCode2] = $this->readChar(true); [, $charCode2] = $this->readChar(true);
if ($charCode1 === 46 && $charCode2 === 46) { if ($charCode1 === self::TOKEN_DOT && $charCode2 === self::TOKEN_DOT) {
return new Token(Token::SPREAD, $position, $position + 3, $line, $col, $prev); return new Token(Token::SPREAD, $position, $position + 3, $line, $col, $prev);
} }
break; break;
case 58: // : case self::TOKEN_COLON:
return new Token(Token::COLON, $position, $position + 1, $line, $col, $prev); return new Token(Token::COLON, $position, $position + 1, $line, $col, $prev);
case 61: // = case self::TOKEN_EQUALS:
return new Token(Token::EQUALS, $position, $position + 1, $line, $col, $prev); return new Token(Token::EQUALS, $position, $position + 1, $line, $col, $prev);
case 64: // @ case self::TOKEN_AT:
return new Token(Token::AT, $position, $position + 1, $line, $col, $prev); return new Token(Token::AT, $position, $position + 1, $line, $col, $prev);
case 91: // [ case self::TOKEN_BRACKET_L:
return new Token(Token::BRACKET_L, $position, $position + 1, $line, $col, $prev); return new Token(Token::BRACKET_L, $position, $position + 1, $line, $col, $prev);
case 93: // ] case self::TOKEN_BRACKET_R:
return new Token(Token::BRACKET_R, $position, $position + 1, $line, $col, $prev); return new Token(Token::BRACKET_R, $position, $position + 1, $line, $col, $prev);
case 123: // { case self::TOKEN_BRACE_L:
return new Token(Token::BRACE_L, $position, $position + 1, $line, $col, $prev); return new Token(Token::BRACE_L, $position, $position + 1, $line, $col, $prev);
case 124: // | case self::TOKEN_PIPE:
return new Token(Token::PIPE, $position, $position + 1, $line, $col, $prev); return new Token(Token::PIPE, $position, $position + 1, $line, $col, $prev);
case 125: // } case self::TOKEN_BRACE_R:
return new Token(Token::BRACE_R, $position, $position + 1, $line, $col, $prev); return new Token(Token::BRACE_R, $position, $position + 1, $line, $col, $prev);
// A-Z // A-Z
case 65: case 65:
case 66: case 66:
@ -227,6 +245,7 @@ class Lexer
case 122: case 122:
return $this->moveStringCursor(-1, -1 * $bytes) return $this->moveStringCursor(-1, -1 * $bytes)
->readName($line, $col, $prev); ->readName($line, $col, $prev);
// - // -
case 45: case 45:
// 0-9 // 0-9
@ -242,6 +261,7 @@ class Lexer
case 57: case 57:
return $this->moveStringCursor(-1, -1 * $bytes) return $this->moveStringCursor(-1, -1 * $bytes)
->readNumber($line, $col, $prev); ->readNumber($line, $col, $prev);
// " // "
case 34: case 34:
[, $nextCode] = $this->readChar(); [, $nextCode] = $this->readChar();
@ -294,11 +314,11 @@ class Lexer
$start = $this->position; $start = $this->position;
[$char, $code] = $this->readChar(); [$char, $code] = $this->readChar();
while ($code && ( while ($code !== null && (
$code === 95 || // _ $code === 95 || // _
$code >= 48 && $code <= 57 || // 0-9 ($code >= 48 && $code <= 57) || // 0-9
$code >= 65 && $code <= 90 || // A-Z ($code >= 65 && $code <= 90) || // A-Z
$code >= 97 && $code <= 122 // a-z ($code >= 97 && $code <= 122) // a-z
)) { )) {
$value .= $char; $value .= $char;
[$char, $code] = $this->moveStringCursor(1, 1)->readChar(); [$char, $code] = $this->moveStringCursor(1, 1)->readChar();
@ -564,10 +584,10 @@ class Lexer
$prev, $prev,
BlockString::value($value) BlockString::value($value)
); );
} else {
// move cursor back to before the first quote
$this->moveStringCursor(-2, -2);
} }
// move cursor back to before the first quote
$this->moveStringCursor(-2, -2);
} }
$this->assertValidBlockStringCharacterCode($code, $this->position); $this->assertValidBlockStringCharacterCode($code, $this->position);
@ -675,7 +695,7 @@ class Lexer
do { do {
[$char, $code, $bytes] = $this->moveStringCursor(1, $bytes)->readChar(); [$char, $code, $bytes] = $this->moveStringCursor(1, $bytes)->readChar();
$value .= $char; $value .= $char;
} while ($code && } while ($code !== null &&
// SourceCharacter but not LineTerminator // SourceCharacter but not LineTerminator
($code > 0x001F || $code === 0x0009) ($code > 0x001F || $code === 0x0009)
); );

View file

@ -439,7 +439,6 @@ class Parser
case 'mutation': case 'mutation':
case 'subscription': case 'subscription':
return $this->parseOperationDefinition(); return $this->parseOperationDefinition();
case 'fragment': case 'fragment':
return $this->parseFragmentDefinition(); return $this->parseFragmentDefinition();
} }
@ -513,15 +512,15 @@ class Parser
*/ */
private function parseVariableDefinitions() private function parseVariableDefinitions()
{ {
return $this->peek(Token::PAREN_L) ? return $this->peek(Token::PAREN_L)
$this->many( ? $this->many(
Token::PAREN_L, Token::PAREN_L,
function () { function () {
return $this->parseVariableDefinition(); return $this->parseVariableDefinition();
}, },
Token::PAREN_R Token::PAREN_R
) : )
new NodeList([]); : new NodeList([]);
} }
/** /**
@ -593,9 +592,9 @@ class Parser
*/ */
private function parseSelection() private function parseSelection()
{ {
return $this->peek(Token::SPREAD) ? return $this->peek(Token::SPREAD)
$this->parseFragment() : ? $this->parseFragment()
$this->parseField(); : $this->parseField();
} }
/** /**
@ -635,17 +634,17 @@ class Parser
*/ */
private function parseArguments($isConst) private function parseArguments($isConst)
{ {
$parseFn = $isConst ? $parseFn = $isConst
function () { ? function () {
return $this->parseConstArgument(); return $this->parseConstArgument();
} : }
function () { : function () {
return $this->parseArgument(); return $this->parseArgument();
}; };
return $this->peek(Token::PAREN_L) ? return $this->peek(Token::PAREN_L)
$this->many(Token::PAREN_L, $parseFn, Token::PAREN_R) : ? $this->many(Token::PAREN_L, $parseFn, Token::PAREN_R)
new NodeList([]); : new NodeList([]);
} }
/** /**
@ -827,7 +826,9 @@ class Parser
'value' => $token->value === 'true', 'value' => $token->value === 'true',
'loc' => $this->loc($token), 'loc' => $this->loc($token),
]); ]);
} elseif ($token->value === 'null') { }
if ($token->value === 'null') {
$this->lexer->advance(); $this->lexer->advance();
return new NullValueNode([ return new NullValueNode([
@ -1207,8 +1208,8 @@ class Parser
do { do {
$types[] = $this->parseNamedType(); $types[] = $this->parseNamedType();
} while ($this->skip(Token::AMP) || } while ($this->skip(Token::AMP) ||
// Legacy support for the SDL? // Legacy support for the SDL?
(! empty($this->lexer->options['allowLegacySDLImplementsInterfaces']) && $this->peek(Token::NAME)) (! empty($this->lexer->options['allowLegacySDLImplementsInterfaces']) && $this->peek(Token::NAME))
); );
} }
@ -1544,7 +1545,8 @@ class Parser
Token::BRACE_L, Token::BRACE_L,
[$this, 'parseOperationTypeDefinition'], [$this, 'parseOperationTypeDefinition'],
Token::BRACE_R Token::BRACE_R
) : []; )
: [];
if (count($directives) === 0 && count($operationTypes) === 0) { if (count($directives) === 0 && count($operationTypes) === 0) {
$this->unexpected(); $this->unexpected();
} }
@ -1654,9 +1656,7 @@ class Parser
$name = $this->parseName(); $name = $this->parseName();
$directives = $this->parseDirectives(true); $directives = $this->parseDirectives(true);
$types = $this->parseUnionMemberTypes(); $types = $this->parseUnionMemberTypes();
if (count($directives) === 0 && if (count($directives) === 0 && count($types) === 0) {
! $types
) {
throw $this->unexpected(); throw $this->unexpected();
} }

View file

@ -28,6 +28,7 @@ use GraphQL\Language\AST\IntValueNode;
use GraphQL\Language\AST\ListTypeNode; use GraphQL\Language\AST\ListTypeNode;
use GraphQL\Language\AST\ListValueNode; use GraphQL\Language\AST\ListValueNode;
use GraphQL\Language\AST\NamedTypeNode; use GraphQL\Language\AST\NamedTypeNode;
use GraphQL\Language\AST\NameNode;
use GraphQL\Language\AST\Node; use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\NonNullTypeNode; use GraphQL\Language\AST\NonNullTypeNode;
@ -47,6 +48,7 @@ use GraphQL\Language\AST\StringValueNode;
use GraphQL\Language\AST\UnionTypeDefinitionNode; use GraphQL\Language\AST\UnionTypeDefinitionNode;
use GraphQL\Language\AST\UnionTypeExtensionNode; use GraphQL\Language\AST\UnionTypeExtensionNode;
use GraphQL\Language\AST\VariableDefinitionNode; use GraphQL\Language\AST\VariableDefinitionNode;
use GraphQL\Language\AST\VariableNode;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use function count; use function count;
use function implode; use function implode;
@ -97,11 +99,11 @@ class Printer
$ast, $ast,
[ [
'leave' => [ 'leave' => [
NodeKind::NAME => static function (Node $node) { NodeKind::NAME => static function (NameNode $node) {
return '' . $node->value; return '' . $node->value;
}, },
NodeKind::VARIABLE => static function ($node) { NodeKind::VARIABLE => static function (VariableNode $node) {
return '$' . $node->name; return '$' . $node->name;
}, },
@ -115,6 +117,7 @@ class Printer
$varDefs = $this->wrap('(', $this->join($node->variableDefinitions, ', '), ')'); $varDefs = $this->wrap('(', $this->join($node->variableDefinitions, ', '), ')');
$directives = $this->join($node->directives, ' '); $directives = $this->join($node->directives, ' ');
$selectionSet = $node->selectionSet; $selectionSet = $node->selectionSet;
// Anonymous queries with no directives or variable definitions can use // Anonymous queries with no directives or variable definitions can use
// the query short form. // the query short form.
return ! $name && ! $directives && ! $varDefs && $op === 'query' return ! $name && ! $directives && ! $varDefs && $op === 'query'

View file

@ -11,28 +11,28 @@ namespace GraphQL\Language;
class Token class Token
{ {
// Each kind of token. // Each kind of token.
const SOF = '<SOF>'; public const SOF = '<SOF>';
const EOF = '<EOF>'; public const EOF = '<EOF>';
const BANG = '!'; public const BANG = '!';
const DOLLAR = '$'; public const DOLLAR = '$';
const AMP = '&'; public const AMP = '&';
const PAREN_L = '('; public const PAREN_L = '(';
const PAREN_R = ')'; public const PAREN_R = ')';
const SPREAD = '...'; public const SPREAD = '...';
const COLON = ':'; public const COLON = ':';
const EQUALS = '='; public const EQUALS = '=';
const AT = '@'; public const AT = '@';
const BRACKET_L = '['; public const BRACKET_L = '[';
const BRACKET_R = ']'; public const BRACKET_R = ']';
const BRACE_L = '{'; public const BRACE_L = '{';
const PIPE = '|'; public const PIPE = '|';
const BRACE_R = '}'; public const BRACE_R = '}';
const NAME = 'Name'; public const NAME = 'Name';
const INT = 'Int'; public const INT = 'Int';
const FLOAT = 'Float'; public const FLOAT = 'Float';
const STRING = 'String'; public const STRING = 'String';
const BLOCK_STRING = 'BlockString'; public const BLOCK_STRING = 'BlockString';
const COMMENT = 'Comment'; public const COMMENT = 'Comment';
/** /**
* The kind of Token (see one of constants above). * The kind of Token (see one of constants above).
@ -104,18 +104,15 @@ class Token
$this->value = $value; $this->value = $value;
} }
/** public function getDescription() : string
* @return string
*/
public function getDescription()
{ {
return $this->kind . ($this->value ? ' "' . $this->value . '"' : ''); return $this->kind . ($this->value === null ? '' : ' "' . $this->value . '"');
} }
/** /**
* @return (string|int|null)[] * @return (string|int|null)[]
*/ */
public function toArray() public function toArray() : array
{ {
return [ return [
'kind' => $this->kind, 'kind' => $this->kind,

View file

@ -251,8 +251,18 @@ class Visitor
$inArray = $stack['inArray']; $inArray = $stack['inArray'];
$stack = $stack['prev']; $stack = $stack['prev'];
} else { } else {
$key = $parent !== null ? ($inArray ? $index : $keys[$index]) : $UNDEFINED; $key = $parent !== null
$node = $parent !== null ? ($parent instanceof NodeList || is_array($parent) ? $parent[$key] : $parent->{$key}) : $newRoot; ? ($inArray
? $index
: $keys[$index]
)
: $UNDEFINED;
$node = $parent !== null
? ($parent instanceof NodeList || is_array($parent)
? $parent[$key]
: $parent->{$key}
)
: $newRoot;
if ($node === null || $node === $UNDEFINED) { if ($node === null || $node === $UNDEFINED) {
continue; continue;
} }

View file

@ -1,22 +0,0 @@
<?php
declare(strict_types=1);
namespace GraphQL;
use function trigger_error;
use const E_USER_DEPRECATED;
trigger_error(
'GraphQL\Schema is moved to GraphQL\Type\Schema',
E_USER_DEPRECATED
);
/**
* Schema Definition
*
* @deprecated moved to GraphQL\Type\Schema
*/
class Schema extends \GraphQL\Type\Schema
{
}

View file

@ -73,10 +73,14 @@ class Helper
} }
if (stripos($contentType, 'application/graphql') !== false) { if (stripos($contentType, 'application/graphql') !== false) {
$rawBody = $readRawBodyFn ? $readRawBodyFn() : $this->readRawBody(); $rawBody = $readRawBodyFn
? $readRawBodyFn()
: $this->readRawBody();
$bodyParams = ['query' => $rawBody ?: '']; $bodyParams = ['query' => $rawBody ?: ''];
} elseif (stripos($contentType, 'application/json') !== false) { } elseif (stripos($contentType, 'application/json') !== false) {
$rawBody = $readRawBodyFn ? $readRawBodyFn() : $this->readRawBody(); $rawBody = $readRawBodyFn ?
$readRawBodyFn()
: $this->readRawBody();
$bodyParams = json_decode($rawBody ?: '', true); $bodyParams = json_decode($rawBody ?: '', true);
if (json_last_error()) { if (json_last_error()) {
@ -272,7 +276,9 @@ class Helper
); );
} }
$doc = $op->queryId ? $this->loadPersistedQuery($config, $op) : $op->query; $doc = $op->queryId
? $this->loadPersistedQuery($config, $op)
: $op->query;
if (! $doc instanceof DocumentNode) { if (! $doc instanceof DocumentNode) {
$doc = Parser::parse($doc); $doc = Parser::parse($doc);
@ -385,13 +391,13 @@ class Helper
*/ */
private function resolveRootValue(ServerConfig $config, OperationParams $params, DocumentNode $doc, $operationType) private function resolveRootValue(ServerConfig $config, OperationParams $params, DocumentNode $doc, $operationType)
{ {
$root = $config->getRootValue(); $rootValue = $config->getRootValue();
if (is_callable($root)) { if (is_callable($rootValue)) {
$root = $root($params, $doc, $operationType); $rootValue = $rootValue($params, $doc, $operationType);
} }
return $root; return $rootValue;
} }
/** /**

View file

@ -9,6 +9,7 @@ use function is_string;
use function json_decode; use function json_decode;
use function json_last_error; use function json_last_error;
use const CASE_LOWER; use const CASE_LOWER;
use const JSON_ERROR_NONE;
/** /**
* Structure representing parsed HTTP parameters for GraphQL operation * Structure representing parsed HTTP parameters for GraphQL operation
@ -62,13 +63,10 @@ class OperationParams
* Creates an instance from given array * Creates an instance from given array
* *
* @param mixed[] $params * @param mixed[] $params
* @param bool $readonly
*
* @return OperationParams
* *
* @api * @api
*/ */
public static function create(array $params, $readonly = false) public static function create(array $params, bool $readonly = false) : OperationParams
{ {
$instance = new static(); $instance = new static();
@ -96,7 +94,7 @@ class OperationParams
} }
$tmp = json_decode($params[$param], true); $tmp = json_decode($params[$param], true);
if (json_last_error()) { if (json_last_error() !== JSON_ERROR_NONE) {
continue; continue;
} }
@ -108,7 +106,7 @@ class OperationParams
$instance->operation = $params['operationname']; $instance->operation = $params['operationname'];
$instance->variables = $params['variables']; $instance->variables = $params['variables'];
$instance->extensions = $params['extensions']; $instance->extensions = $params['extensions'];
$instance->readOnly = (bool) $readonly; $instance->readOnly = $readonly;
// Apollo server/client compatibility: look for the queryid in extensions // Apollo server/client compatibility: look for the queryid in extensions
if (isset($instance->extensions['persistedQuery']['sha256Hash']) && empty($instance->query) && empty($instance->queryId)) { if (isset($instance->extensions['persistedQuery']['sha256Hash']) && empty($instance->query) && empty($instance->queryId)) {

View file

@ -225,15 +225,11 @@ class ServerConfig
/** /**
* Allow batching queries (disabled by default) * Allow batching queries (disabled by default)
* *
* @param bool $enableBatching
*
* @return self
*
* @api * @api
*/ */
public function setQueryBatching($enableBatching) public function setQueryBatching(bool $enableBatching) : self
{ {
$this->queryBatching = (bool) $enableBatching; $this->queryBatching = $enableBatching;
return $this; return $this;
} }

View file

@ -151,6 +151,7 @@ class StandardServer
StreamInterface $writableBodyStream StreamInterface $writableBodyStream
) { ) {
$result = $this->executePsrRequest($request); $result = $this->executePsrRequest($request);
return $this->helper->toPsrResponse($result, $response, $writableBodyStream); return $this->helper->toPsrResponse($result, $response, $writableBodyStream);
} }
@ -165,6 +166,7 @@ class StandardServer
public function executePsrRequest(ServerRequestInterface $request) public function executePsrRequest(ServerRequestInterface $request)
{ {
$parsedBody = $this->helper->parsePsrRequest($request); $parsedBody = $this->helper->parsePsrRequest($request);
return $this->executeRequest($parsedBody); return $this->executeRequest($parsedBody);
} }

View file

@ -11,9 +11,6 @@ use GraphQL\Language\AST\Node;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use function is_bool; use function is_bool;
/**
* Class BooleanType
*/
class BooleanType extends ScalarType class BooleanType extends ScalarType
{ {
/** @var string */ /** @var string */
@ -23,11 +20,14 @@ class BooleanType extends ScalarType
public $description = 'The `Boolean` scalar type represents `true` or `false`.'; public $description = 'The `Boolean` scalar type represents `true` or `false`.';
/** /**
* @param mixed $value * Coerce the given value to a boolean.
* *
* @return bool * The GraphQL spec leaves this up to the implementations, so we just do what
* PHP does natively to make this intuitive for developers.
*
* @param mixed $value
*/ */
public function serialize($value) public function serialize($value) : bool
{ {
return (bool) $value; return (bool) $value;
} }
@ -58,11 +58,11 @@ class BooleanType extends ScalarType
*/ */
public function parseLiteral($valueNode, ?array $variables = null) public function parseLiteral($valueNode, ?array $variables = null)
{ {
if ($valueNode instanceof BooleanValueNode) { if (! $valueNode instanceof BooleanValueNode) {
return (bool) $valueNode->value; // Intentionally without message, as all information already in wrapped Exception
throw new Exception();
} }
// Intentionally without message, as all information already in wrapped Exception return $valueNode->value;
throw new Exception();
} }
} }

View file

@ -12,9 +12,6 @@ use function call_user_func;
use function is_callable; use function is_callable;
use function sprintf; use function sprintf;
/**
* Class CustomScalarType
*/
class CustomScalarType extends ScalarType class CustomScalarType extends ScalarType
{ {
/** /**

View file

@ -8,24 +8,19 @@ use GraphQL\Language\AST\DirectiveDefinitionNode;
use GraphQL\Language\DirectiveLocation; use GraphQL\Language\DirectiveLocation;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use function array_key_exists; use function array_key_exists;
use function array_keys;
use function in_array;
use function is_array; use function is_array;
/**
* Class Directive
*/
class Directive class Directive
{ {
public const DEFAULT_DEPRECATION_REASON = 'No longer supported'; public const DEFAULT_DEPRECATION_REASON = 'No longer supported';
const INCLUDE_NAME = 'include'; public const INCLUDE_NAME = 'include';
const IF_ARGUMENT_NAME = 'if'; public const IF_ARGUMENT_NAME = 'if';
const SKIP_NAME = 'skip'; public const SKIP_NAME = 'skip';
const DEPRECATED_NAME = 'deprecated'; public const DEPRECATED_NAME = 'deprecated';
const REASON_ARGUMENT_NAME = 'reason'; public const REASON_ARGUMENT_NAME = 'reason';
/** @var Directive[] */ /** @var Directive[]|null */
public static $internalDirectives; public static $internalDirectives;
// Schema Definitions // Schema Definitions
@ -40,7 +35,7 @@ class Directive
public $locations; public $locations;
/** @var FieldArgument[] */ /** @var FieldArgument[] */
public $args; public $args = [];
/** @var DirectiveDefinitionNode|null */ /** @var DirectiveDefinitionNode|null */
public $astNode; public $astNode;
@ -80,15 +75,16 @@ class Directive
public static function includeDirective() public static function includeDirective()
{ {
$internal = self::getInternalDirectives(); $internal = self::getInternalDirectives();
return $internal['include']; return $internal['include'];
} }
/** /**
* @return Directive[] * @return Directive[]
*/ */
public static function getInternalDirectives() public static function getInternalDirectives() : array
{ {
if (! self::$internalDirectives) { if (self::$internalDirectives === null) {
self::$internalDirectives = [ self::$internalDirectives = [
'include' => new self([ 'include' => new self([
'name' => self::INCLUDE_NAME, 'name' => self::INCLUDE_NAME,
@ -140,24 +136,30 @@ class Directive
]), ]),
]; ];
} }
return self::$internalDirectives; return self::$internalDirectives;
} }
/** /**
* @return Directive * @return Directive
*/ */
public static function skipDirective() public static function skipDirective()
{ {
$internal = self::getInternalDirectives(); $internal = self::getInternalDirectives();
return $internal['skip']; return $internal['skip'];
} }
/** /**
* @return Directive * @return Directive
*/ */
public static function deprecatedDirective() public static function deprecatedDirective()
{ {
$internal = self::getInternalDirectives(); $internal = self::getInternalDirectives();
return $internal['deprecated']; return $internal['deprecated'];
} }
/** /**
* @return bool * @return bool
*/ */

View file

@ -19,9 +19,6 @@ use function is_int;
use function is_string; use function is_string;
use function sprintf; use function sprintf;
/**
* Class EnumType
*/
class EnumType extends Type implements InputType, OutputType, LeafType, NullableType, NamedType class EnumType extends Type implements InputType, OutputType, LeafType, NullableType, NamedType
{ {
/** @var EnumTypeDefinitionNode|null */ /** @var EnumTypeDefinitionNode|null */
@ -33,7 +30,7 @@ class EnumType extends Type implements InputType, OutputType, LeafType, Nullable
/** @var MixedStore<mixed, EnumValueDefinition> */ /** @var MixedStore<mixed, EnumValueDefinition> */
private $valueLookup; private $valueLookup;
/** @var \ArrayObject<string, EnumValueDefinition> */ /** @var ArrayObject<string, EnumValueDefinition> */
private $nameLookup; private $nameLookup;
/** @var EnumTypeExtensionNode[] */ /** @var EnumTypeExtensionNode[] */
@ -71,7 +68,7 @@ class EnumType extends Type implements InputType, OutputType, LeafType, Nullable
} }
/** /**
* @return \ArrayObject<string, EnumValueDefinition> * @return ArrayObject<string, EnumValueDefinition>
*/ */
private function getNameLookup() private function getNameLookup()
{ {

View file

@ -6,9 +6,6 @@ namespace GraphQL\Type\Definition;
use GraphQL\Language\AST\EnumValueDefinitionNode; use GraphQL\Language\AST\EnumValueDefinitionNode;
/**
* Class EnumValueDefinition
*/
class EnumValueDefinition class EnumValueDefinition
{ {
/** @var string */ /** @var string */

View file

@ -11,9 +11,6 @@ use function is_array;
use function is_string; use function is_string;
use function sprintf; use function sprintf;
/**
* Class FieldArgument
*/
class FieldArgument class FieldArgument
{ {
/** @var string */ /** @var string */

View file

@ -81,7 +81,7 @@ class FieldDefinition
$this->config = $config; $this->config = $config;
$this->complexityFn = $config['complexity'] ?? static::DEFAULT_COMPLEXITY_FN; $this->complexityFn = $config['complexity'] ?? self::DEFAULT_COMPLEXITY_FN;
} }
public static function defineFieldMap(Type $type, $fields) public static function defineFieldMap(Type $type, $fields)

View file

@ -12,9 +12,6 @@ use GraphQL\Language\AST\Node;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use function is_numeric; use function is_numeric;
/**
* Class FloatType
*/
class FloatType extends ScalarType class FloatType extends ScalarType
{ {
/** @var string */ /** @var string */

View file

@ -15,9 +15,6 @@ use function is_callable;
use function is_string; use function is_string;
use function sprintf; use function sprintf;
/**
* Class InputObjectType
*/
class InputObjectType extends Type implements InputType, NullableType, NamedType class InputObjectType extends Type implements InputType, NullableType, NamedType
{ {
/** @var InputObjectTypeDefinitionNode|null */ /** @var InputObjectTypeDefinitionNode|null */
@ -72,7 +69,9 @@ class InputObjectType extends Type implements InputType, NullableType, NamedType
if ($this->fields === null) { if ($this->fields === null) {
$this->fields = []; $this->fields = [];
$fields = $this->config['fields'] ?? []; $fields = $this->config['fields'] ?? [];
$fields = is_callable($fields) ? call_user_func($fields) : $fields; $fields = is_callable($fields)
? call_user_func($fields)
: $fields;
if (! is_array($fields)) { if (! is_array($fields)) {
throw new InvariantViolation( throw new InvariantViolation(

View file

@ -14,9 +14,6 @@ use function intval;
use function is_bool; use function is_bool;
use function is_numeric; use function is_numeric;
/**
* Class IntType
*/
class IntType extends ScalarType class IntType extends ScalarType
{ {
// As per the GraphQL Spec, Integers are only treated as valid when a valid // As per the GraphQL Spec, Integers are only treated as valid when a valid

View file

@ -12,9 +12,6 @@ use function is_callable;
use function is_string; use function is_string;
use function sprintf; use function sprintf;
/**
* Class InterfaceType
*/
class InterfaceType extends Type implements AbstractType, OutputType, CompositeType, NullableType, NamedType class InterfaceType extends Type implements AbstractType, OutputType, CompositeType, NullableType, NamedType
{ {
/** @var InterfaceTypeDefinitionNode|null */ /** @var InterfaceTypeDefinitionNode|null */

View file

@ -4,9 +4,6 @@ declare(strict_types=1);
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
/**
* Class ListOfType
*/
class ListOfType extends Type implements WrappingType, OutputType, NullableType, InputType class ListOfType extends Type implements WrappingType, OutputType, NullableType, InputType
{ {
/** @var ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType */ /** @var ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType */
@ -20,15 +17,9 @@ class ListOfType extends Type implements WrappingType, OutputType, NullableType,
$this->ofType = Type::assertType($type); $this->ofType = Type::assertType($type);
} }
/** public function toString() : string
* @return string
*/
public function toString()
{ {
$type = $this->ofType; return '[' . $this->ofType->toString() . ']';
$str = $type instanceof Type ? $type->toString() : (string) $type;
return '[' . $str . ']';
} }
/** /**
@ -40,6 +31,8 @@ class ListOfType extends Type implements WrappingType, OutputType, NullableType,
{ {
$type = $this->ofType; $type = $this->ofType;
return $recurse && $type instanceof WrappingType ? $type->getWrappedType($recurse) : $type; return $recurse && $type instanceof WrappingType
? $type->getWrappedType($recurse)
: $type;
} }
} }

View file

@ -6,9 +6,6 @@ namespace GraphQL\Type\Definition;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
/**
* Class NonNull
*/
class NonNull extends Type implements WrappingType, OutputType, InputType class NonNull extends Type implements WrappingType, OutputType, InputType
{ {
/** @var NullableType */ /** @var NullableType */
@ -69,6 +66,8 @@ class NonNull extends Type implements WrappingType, OutputType, InputType
{ {
$type = $this->ofType; $type = $this->ofType;
return $recurse && $type instanceof WrappingType ? $type->getWrappedType($recurse) : $type; return $recurse && $type instanceof WrappingType
? $type->getWrappedType($recurse)
: $type;
} }
} }

View file

@ -168,7 +168,7 @@ class ObjectType extends Type implements OutputType, CompositeType, NullableType
private function getInterfaceMap() private function getInterfaceMap()
{ {
if (! $this->interfaceMap) { if ($this->interfaceMap === null) {
$this->interfaceMap = []; $this->interfaceMap = [];
foreach ($this->getInterfaces() as $interface) { foreach ($this->getInterfaces() as $interface) {
$this->interfaceMap[$interface->name] = $interface; $this->interfaceMap[$interface->name] = $interface;
@ -185,7 +185,9 @@ class ObjectType extends Type implements OutputType, CompositeType, NullableType
{ {
if ($this->interfaces === null) { if ($this->interfaces === null) {
$interfaces = $this->config['interfaces'] ?? []; $interfaces = $this->config['interfaces'] ?? [];
$interfaces = is_callable($interfaces) ? call_user_func($interfaces) : $interfaces; $interfaces = is_callable($interfaces)
? call_user_func($interfaces)
: $interfaces;
if ($interfaces !== null && ! is_array($interfaces)) { if ($interfaces !== null && ! is_array($interfaces)) {
throw new InvariantViolation( throw new InvariantViolation(
@ -200,19 +202,21 @@ class ObjectType extends Type implements OutputType, CompositeType, NullableType
} }
/** /**
* @param mixed[] $value * @param mixed $value
* @param mixed[]|null $context * @param mixed[]|null $context
* *
* @return bool|null * @return bool|null
*/ */
public function isTypeOf($value, $context, ResolveInfo $info) public function isTypeOf($value, $context, ResolveInfo $info)
{ {
return isset($this->config['isTypeOf']) ? call_user_func( return isset($this->config['isTypeOf'])
$this->config['isTypeOf'], ? call_user_func(
$value, $this->config['isTypeOf'],
$context, $value,
$info $context,
) : null; $info
)
: null;
} }
/** /**

View file

@ -0,0 +1,242 @@
<?php
declare(strict_types=1);
namespace GraphQL\Type\Definition;
use GraphQL\Error\Error;
use GraphQL\Executor\Values;
use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\FragmentDefinitionNode;
use GraphQL\Language\AST\FragmentSpreadNode;
use GraphQL\Language\AST\InlineFragmentNode;
use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Type\Schema;
use function array_filter;
use function array_key_exists;
use function array_keys;
use function array_merge;
use function array_merge_recursive;
use function array_unique;
use function array_values;
use function count;
use function in_array;
use function is_array;
use function is_numeric;
class QueryPlan
{
/** @var string[][] */
private $types = [];
/** @var Schema */
private $schema;
/** @var mixed[] */
private $queryPlan = [];
/** @var mixed[] */
private $variableValues;
/** @var FragmentDefinitionNode[] */
private $fragments;
/**
* @param FieldNode[] $fieldNodes
* @param mixed[] $variableValues
* @param FragmentDefinitionNode[] $fragments
*/
public function __construct(ObjectType $parentType, Schema $schema, iterable $fieldNodes, array $variableValues, array $fragments)
{
$this->schema = $schema;
$this->variableValues = $variableValues;
$this->fragments = $fragments;
$this->analyzeQueryPlan($parentType, $fieldNodes);
}
/**
* @return mixed[]
*/
public function queryPlan() : array
{
return $this->queryPlan;
}
/**
* @return string[]
*/
public function getReferencedTypes() : array
{
return array_keys($this->types);
}
public function hasType(string $type) : bool
{
return count(array_filter($this->getReferencedTypes(), static function (string $referencedType) use ($type) {
return $type === $referencedType;
})) > 0;
}
/**
* @return string[]
*/
public function getReferencedFields() : array
{
return array_values(array_unique(array_merge(...array_values($this->types))));
}
public function hasField(string $field) : bool
{
return count(array_filter($this->getReferencedFields(), static function (string $referencedField) use ($field) {
return $field === $referencedField;
})) > 0;
}
/**
* @return string[]
*/
public function subFields(string $typename) : array
{
if (! array_key_exists($typename, $this->types)) {
return [];
}
return $this->types[$typename];
}
/**
* @param FieldNode[] $fieldNodes
*/
private function analyzeQueryPlan(ObjectType $parentType, iterable $fieldNodes) : void
{
$queryPlan = [];
/** @var FieldNode $fieldNode */
foreach ($fieldNodes as $fieldNode) {
if (! $fieldNode->selectionSet) {
continue;
}
$type = $parentType->getField($fieldNode->name->value)->getType();
if ($type instanceof WrappingType) {
$type = $type->getWrappedType();
}
$subfields = $this->analyzeSelectionSet($fieldNode->selectionSet, $type);
$this->types[$type->name] = array_unique(array_merge(
array_key_exists($type->name, $this->types) ? $this->types[$type->name] : [],
array_keys($subfields)
));
$queryPlan = array_merge_recursive(
$queryPlan,
$subfields
);
}
$this->queryPlan = $queryPlan;
}
/**
* @return mixed[]
*
* $parentType InterfaceType|ObjectType.
*
* @throws Error
*/
private function analyzeSelectionSet(SelectionSetNode $selectionSet, Type $parentType) : array
{
$fields = [];
foreach ($selectionSet->selections as $selectionNode) {
if ($selectionNode instanceof FieldNode) {
$fieldName = $selectionNode->name->value;
$type = $parentType->getField($fieldName);
$selectionType = $type->getType();
$subfields = [];
if ($selectionNode->selectionSet) {
$subfields = $this->analyzeSubFields($selectionType, $selectionNode->selectionSet);
}
$fields[$fieldName] = [
'type' => $selectionType,
'fields' => $subfields ?? [],
'args' => Values::getArgumentValues($type, $selectionNode, $this->variableValues),
];
} elseif ($selectionNode instanceof FragmentSpreadNode) {
$spreadName = $selectionNode->name->value;
if (isset($this->fragments[$spreadName])) {
$fragment = $this->fragments[$spreadName];
$type = $this->schema->getType($fragment->typeCondition->name->value);
$subfields = $this->analyzeSubFields($type, $fragment->selectionSet);
$fields = $this->arrayMergeDeep(
$subfields,
$fields
);
}
} elseif ($selectionNode instanceof InlineFragmentNode) {
$type = $this->schema->getType($selectionNode->typeCondition->name->value);
$subfields = $this->analyzeSubFields($type, $selectionNode->selectionSet);
$fields = $this->arrayMergeDeep(
$subfields,
$fields
);
}
}
return $fields;
}
/**
* @return mixed[]
*/
private function analyzeSubFields(Type $type, SelectionSetNode $selectionSet) : array
{
if ($type instanceof WrappingType) {
$type = $type->getWrappedType();
}
$subfields = [];
if ($type instanceof ObjectType) {
$subfields = $this->analyzeSelectionSet($selectionSet, $type);
$this->types[$type->name] = array_unique(array_merge(
array_key_exists($type->name, $this->types) ? $this->types[$type->name] : [],
array_keys($subfields)
));
}
return $subfields;
}
/**
* similar to array_merge_recursive this merges nested arrays, but handles non array values differently
* while array_merge_recursive tries to merge non array values, in this implementation they will be overwritten
*
* @see https://stackoverflow.com/a/25712428
*
* @param mixed[] $array1
* @param mixed[] $array2
*
* @return mixed[]
*/
private function arrayMergeDeep(array $array1, array $array2) : array
{
$merged = $array1;
foreach ($array2 as $key => & $value) {
if (is_numeric($key)) {
if (! in_array($value, $merged, true)) {
$merged[] = $value;
}
} elseif (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
$merged[$key] = $this->arrayMergeDeep($merged[$key], $value);
} else {
$merged[$key] = $value;
}
}
return $merged;
}
}

View file

@ -15,12 +15,13 @@ use function array_merge_recursive;
/** /**
* Structure containing information useful for field resolution process. * Structure containing information useful for field resolution process.
* Passed as 3rd argument to every field resolver. See [docs on field resolving (data fetching)](data-fetching.md). *
* Passed as 4th argument to every field resolver. See [docs on field resolving (data fetching)](data-fetching.md).
*/ */
class ResolveInfo class ResolveInfo
{ {
/** /**
* The name of the field being resolved * The name of the field being resolved.
* *
* @api * @api
* @var string * @var string
@ -36,7 +37,7 @@ class ResolveInfo
public $fieldNodes = []; public $fieldNodes = [];
/** /**
* Expected return type of the field being resolved * Expected return type of the field being resolved.
* *
* @api * @api
* @var ScalarType|ObjectType|InterfaceType|UnionType|EnumType|ListOfType|NonNull * @var ScalarType|ObjectType|InterfaceType|UnionType|EnumType|ListOfType|NonNull
@ -44,7 +45,7 @@ class ResolveInfo
public $returnType; public $returnType;
/** /**
* Parent type of the field being resolved * Parent type of the field being resolved.
* *
* @api * @api
* @var ObjectType * @var ObjectType
@ -52,7 +53,7 @@ class ResolveInfo
public $parentType; public $parentType;
/** /**
* Path to this field from the very root value * Path to this field from the very root value.
* *
* @api * @api
* @var string[][] * @var string[][]
@ -60,15 +61,15 @@ class ResolveInfo
public $path; public $path;
/** /**
* Instance of a schema used for execution * Instance of a schema used for execution.
* *
* @api * @api
* @var Schema|null * @var Schema
*/ */
public $schema; public $schema;
/** /**
* AST of all fragments defined in query * AST of all fragments defined in query.
* *
* @api * @api
* @var FragmentDefinitionNode[] * @var FragmentDefinitionNode[]
@ -76,15 +77,15 @@ class ResolveInfo
public $fragments = []; public $fragments = [];
/** /**
* Root value passed to query execution * Root value passed to query execution.
* *
* @api * @api
* @var mixed|null * @var mixed
*/ */
public $rootValue; public $rootValue;
/** /**
* AST of operation definition node (query, mutation) * AST of operation definition node (query, mutation).
* *
* @api * @api
* @var OperationDefinitionNode|null * @var OperationDefinitionNode|null
@ -92,13 +93,16 @@ class ResolveInfo
public $operation; public $operation;
/** /**
* Array of variables passed to query execution * Array of variables passed to query execution.
* *
* @api * @api
* @var mixed[] * @var mixed[]
*/ */
public $variableValues = []; public $variableValues = [];
/** @var QueryPlan */
private $queryPlan;
/** /**
* @param FieldNode[] $fieldNodes * @param FieldNode[] $fieldNodes
* @param ScalarType|ObjectType|InterfaceType|UnionType|EnumType|ListOfType|NonNull $returnType * @param ScalarType|ObjectType|InterfaceType|UnionType|EnumType|ListOfType|NonNull $returnType
@ -109,7 +113,7 @@ class ResolveInfo
*/ */
public function __construct( public function __construct(
string $fieldName, string $fieldName,
$fieldNodes, iterable $fieldNodes,
$returnType, $returnType,
ObjectType $parentType, ObjectType $parentType,
array $path, array $path,
@ -133,7 +137,7 @@ class ResolveInfo
/** /**
* Helper method that returns names of all fields selected in query for * Helper method that returns names of all fields selected in query for
* $this->fieldName up to $depth levels * $this->fieldName up to $depth levels.
* *
* Example: * Example:
* query MyQuery{ * query MyQuery{
@ -174,11 +178,30 @@ class ResolveInfo
/** @var FieldNode $fieldNode */ /** @var FieldNode $fieldNode */
foreach ($this->fieldNodes as $fieldNode) { foreach ($this->fieldNodes as $fieldNode) {
$fields = array_merge_recursive($fields, $this->foldSelectionSet($fieldNode->selectionSet, $depth)); $fields = array_merge_recursive(
$fields,
$this->foldSelectionSet($fieldNode->selectionSet, $depth)
);
} }
return $fields; return $fields;
} }
public function lookAhead() : QueryPlan
{
if ($this->queryPlan === null) {
$this->queryPlan = new QueryPlan(
$this->parentType,
$this->schema,
$this->fieldNodes,
$this->variableValues,
$this->fragments
);
}
return $this->queryPlan;
}
/** /**
* @return bool[] * @return bool[]
*/ */
@ -207,6 +230,7 @@ class ResolveInfo
); );
} }
} }
return $fields; return $fields;
} }
} }

View file

@ -14,9 +14,6 @@ use function is_object;
use function is_scalar; use function is_scalar;
use function method_exists; use function method_exists;
/**
* Class StringType
*/
class StringType extends ScalarType class StringType extends ScalarType
{ {
/** @var string */ /** @var string */

View file

@ -194,6 +194,7 @@ abstract class Type implements JsonSerializable
public static function getInternalTypes() public static function getInternalTypes()
{ {
trigger_error(__METHOD__ . ' is deprecated. Use Type::getStandardTypes() instead', E_USER_DEPRECATED); trigger_error(__METHOD__ . ' is deprecated. Use Type::getStandardTypes() instead', E_USER_DEPRECATED);
return self::getStandardTypes(); return self::getStandardTypes();
} }
@ -344,7 +345,9 @@ abstract class Type implements JsonSerializable
*/ */
public static function getNullableType($type) public static function getNullableType($type)
{ {
return $type instanceof NonNull ? $type->getWrappedType() : $type; return $type instanceof NonNull
? $type->getWrappedType()
: $type;
} }
/** /**

View file

@ -14,9 +14,6 @@ use function is_callable;
use function is_string; use function is_string;
use function sprintf; use function sprintf;
/**
* Class UnionType
*/
class UnionType extends Type implements AbstractType, OutputType, CompositeType, NullableType, NamedType class UnionType extends Type implements AbstractType, OutputType, CompositeType, NullableType, NamedType
{ {
/** @var UnionTypeDefinitionNode */ /** @var UnionTypeDefinitionNode */

View file

@ -488,7 +488,9 @@ EOD;
'type' => [ 'type' => [
'type' => Type::nonNull(self::_type()), 'type' => Type::nonNull(self::_type()),
'resolve' => static function ($value) { 'resolve' => static function ($value) {
return method_exists($value, 'getType') ? $value->getType() : $value->type; return method_exists($value, 'getType')
? $value->getType()
: $value->type;
}, },
], ],
'defaultValue' => [ 'defaultValue' => [
@ -693,7 +695,7 @@ EOD;
return self::$map['__DirectiveLocation']; return self::$map['__DirectiveLocation'];
} }
public static function schemaMetaFieldDef() public static function schemaMetaFieldDef() : FieldDefinition
{ {
if (! isset(self::$map[self::SCHEMA_FIELD_NAME])) { if (! isset(self::$map[self::SCHEMA_FIELD_NAME])) {
self::$map[self::SCHEMA_FIELD_NAME] = FieldDefinition::create([ self::$map[self::SCHEMA_FIELD_NAME] = FieldDefinition::create([
@ -715,7 +717,7 @@ EOD;
return self::$map[self::SCHEMA_FIELD_NAME]; return self::$map[self::SCHEMA_FIELD_NAME];
} }
public static function typeMetaFieldDef() public static function typeMetaFieldDef() : FieldDefinition
{ {
if (! isset(self::$map[self::TYPE_FIELD_NAME])) { if (! isset(self::$map[self::TYPE_FIELD_NAME])) {
self::$map[self::TYPE_FIELD_NAME] = FieldDefinition::create([ self::$map[self::TYPE_FIELD_NAME] = FieldDefinition::create([
@ -734,7 +736,7 @@ EOD;
return self::$map[self::TYPE_FIELD_NAME]; return self::$map[self::TYPE_FIELD_NAME];
} }
public static function typeNameMetaFieldDef() public static function typeNameMetaFieldDef() : FieldDefinition
{ {
if (! isset(self::$map[self::TYPE_NAME_FIELD_NAME])) { if (! isset(self::$map[self::TYPE_NAME_FIELD_NAME])) {
self::$map[self::TYPE_NAME_FIELD_NAME] = FieldDefinition::create([ self::$map[self::TYPE_NAME_FIELD_NAME] = FieldDefinition::create([

View file

@ -42,7 +42,7 @@ class SchemaConfig
/** @var Directive[] */ /** @var Directive[] */
public $directives; public $directives;
/** @var callable */ /** @var callable|null */
public $typeLoader; public $typeLoader;
/** @var SchemaDefinitionNode */ /** @var SchemaDefinitionNode */

View file

@ -29,6 +29,7 @@ use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType; use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Validation\InputObjectCircularRefs;
use GraphQL\Utils\TypeComparators; use GraphQL\Utils\TypeComparators;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use function array_filter; use function array_filter;
@ -48,9 +49,13 @@ class SchemaValidationContext
/** @var Schema */ /** @var Schema */
private $schema; private $schema;
/** @var InputObjectCircularRefs */
private $inputObjectCircularRefs;
public function __construct(Schema $schema) public function __construct(Schema $schema)
{ {
$this->schema = $schema; $this->schema = $schema;
$this->inputObjectCircularRefs = new InputObjectCircularRefs($this);
} }
/** /**
@ -99,7 +104,7 @@ class SchemaValidationContext
* @param string $message * @param string $message
* @param Node[]|Node|TypeNode|TypeDefinitionNode|null $nodes * @param Node[]|Node|TypeNode|TypeDefinitionNode|null $nodes
*/ */
private function reportError($message, $nodes = null) public function reportError($message, $nodes = null)
{ {
$nodes = array_filter($nodes && is_array($nodes) ? $nodes : [$nodes]); $nodes = array_filter($nodes && is_array($nodes) ? $nodes : [$nodes]);
$this->addError(new Error($message, $nodes)); $this->addError(new Error($message, $nodes));
@ -247,7 +252,7 @@ class SchemaValidationContext
if (! $type instanceof NamedType) { if (! $type instanceof NamedType) {
$this->reportError( $this->reportError(
'Expected GraphQL named type but got: ' . Utils::printSafe($type) . '.', 'Expected GraphQL named type but got: ' . Utils::printSafe($type) . '.',
is_object($type) ? $type->astNode : null $type instanceof Type ? $type->astNode : null
); );
continue; continue;
} }
@ -275,6 +280,9 @@ class SchemaValidationContext
} elseif ($type instanceof InputObjectType) { } elseif ($type instanceof InputObjectType) {
// Ensure Input Object fields are valid. // Ensure Input Object fields are valid.
$this->validateInputFields($type); $this->validateInputFields($type);
// Ensure Input Objects do not contain non-nullable circular references
$this->inputObjectCircularRefs->validate($type);
} }
} }
} }
@ -760,8 +768,9 @@ class SchemaValidationContext
); );
} }
return $union->astNode ? return $union->astNode
$union->astNode->types : null; ? $union->astNode->types
: null;
} }
private function validateEnumValues(EnumType $enumType) private function validateEnumValues(EnumType $enumType)
@ -816,8 +825,9 @@ class SchemaValidationContext
); );
} }
return $enum->astNode ? return $enum->astNode
$enum->astNode->values : null; ? $enum->astNode->values
: null;
} }
private function validateInputFields(InputObjectType $inputObj) private function validateInputFields(InputObjectType $inputObj)

View file

@ -0,0 +1,105 @@
<?php
declare(strict_types=1);
namespace GraphQL\Type\Validation;
use GraphQL\Language\AST\InputValueDefinitionNode;
use GraphQL\Type\Definition\InputObjectField;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\SchemaValidationContext;
use function array_map;
use function array_pop;
use function array_slice;
use function count;
use function implode;
class InputObjectCircularRefs
{
/** @var SchemaValidationContext */
private $schemaValidationContext;
/**
* Tracks already visited types to maintain O(N) and to ensure that cycles
* are not redundantly reported.
*
* @var InputObjectType[]
*/
private $visitedTypes = [];
/** @var InputObjectField[] */
private $fieldPath = [];
/**
* Position in the type path.
*
* [string $typeName => int $index]
*
* @var int[]
*/
private $fieldPathIndexByTypeName = [];
public function __construct(SchemaValidationContext $schemaValidationContext)
{
$this->schemaValidationContext = $schemaValidationContext;
}
/**
* This does a straight-forward DFS to find cycles.
* It does not terminate when a cycle was found but continues to explore
* the graph to find all possible cycles.
*/
public function validate(InputObjectType $inputObj) : void
{
if (isset($this->visitedTypes[$inputObj->name])) {
return;
}
$this->visitedTypes[$inputObj->name] = true;
$this->fieldPathIndexByTypeName[$inputObj->name] = count($this->fieldPath);
$fieldMap = $inputObj->getFields();
foreach ($fieldMap as $fieldName => $field) {
$type = $field->getType();
if ($type instanceof NonNull) {
$fieldType = $type->getWrappedType();
// If the type of the field is anything else then a non-nullable input object,
// there is no chance of an unbreakable cycle
if ($fieldType instanceof InputObjectType) {
$this->fieldPath[] = $field;
if (! isset($this->fieldPathIndexByTypeName[$fieldType->name])) {
$this->validate($fieldType);
} else {
$cycleIndex = $this->fieldPathIndexByTypeName[$fieldType->name];
$cyclePath = array_slice($this->fieldPath, $cycleIndex);
$fieldNames = array_map(
static function (InputObjectField $field) : string {
return $field->name;
},
$cyclePath
);
$this->schemaValidationContext->reportError(
'Cannot reference Input Object "' . $fieldType->name . '" within itself '
. 'through a series of non-null fields: "' . implode('.', $fieldNames) . '".',
array_map(
static function (InputObjectField $field) : ?InputValueDefinitionNode {
return $field->astNode;
},
$cyclePath
)
);
}
}
}
array_pop($this->fieldPath);
}
unset($this->fieldPathIndexByTypeName[$inputObj->name]);
}
}

View file

@ -183,7 +183,7 @@ class AST
$valuesNodes[] = $itemNode; $valuesNodes[] = $itemNode;
} }
return new ListValueNode(['values' => $valuesNodes]); return new ListValueNode(['values' => new NodeList($valuesNodes)]);
} }
return self::astFromValue($value, $itemType); return self::astFromValue($value, $itemType);
@ -235,7 +235,7 @@ class AST
]); ]);
} }
return new ObjectValueNode(['fields' => $fieldNodes]); return new ObjectValueNode(['fields' => new NodeList($fieldNodes)]);
} }
if ($type instanceof ScalarType || $type instanceof EnumType) { if ($type instanceof ScalarType || $type instanceof EnumType) {
@ -322,7 +322,7 @@ class AST
* *
* @api * @api
*/ */
public static function valueFromAST($valueNode, InputType $type, ?array $variables = null) public static function valueFromAST($valueNode, Type $type, ?array $variables = null)
{ {
$undefined = Utils::undefined(); $undefined = Utils::undefined();
@ -353,6 +353,7 @@ class AST
// No valid return value. // No valid return value.
return $undefined; return $undefined;
} }
// Note: we're not doing any checking that this variable is correct. We're // Note: we're not doing any checking that this variable is correct. We're
// assuming that this query has been validated and the variable usage here // assuming that this query has been validated and the variable usage here
// is of the correct type. // is of the correct type.

View file

@ -17,7 +17,6 @@ use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
use GraphQL\Language\AST\ListTypeNode; use GraphQL\Language\AST\ListTypeNode;
use GraphQL\Language\AST\NamedTypeNode; use GraphQL\Language\AST\NamedTypeNode;
use GraphQL\Language\AST\Node; use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\NonNullTypeNode; use GraphQL\Language\AST\NonNullTypeNode;
use GraphQL\Language\AST\ObjectTypeDefinitionNode; use GraphQL\Language\AST\ObjectTypeDefinitionNode;
use GraphQL\Language\AST\ScalarTypeDefinitionNode; use GraphQL\Language\AST\ScalarTypeDefinitionNode;
@ -144,7 +143,8 @@ class ASTDefinitionBuilder
// Note: While this could make assertions to get the correctly typed // Note: While this could make assertions to get the correctly typed
// value, that would throw immediately while type system validation // value, that would throw immediately while type system validation
// with validateSchema() will produce more actionable results. // with validateSchema() will produce more actionable results.
$type = $this->internalBuildWrappedType($value->type); $type = $this->internalBuildWrappedType($value->type);
$config = [ $config = [
'name' => $value->name->value, 'name' => $value->name->value,
'type' => $type, 'type' => $type,
@ -244,23 +244,20 @@ class ASTDefinitionBuilder
* *
* @throws Error * @throws Error
*/ */
private function makeSchemaDef($def) private function makeSchemaDef(Node $def)
{ {
if (! $def) { switch (true) {
throw new Error('def must be defined.'); case $def instanceof ObjectTypeDefinitionNode:
}
switch ($def->kind) {
case NodeKind::OBJECT_TYPE_DEFINITION:
return $this->makeTypeDef($def); return $this->makeTypeDef($def);
case NodeKind::INTERFACE_TYPE_DEFINITION: case $def instanceof InterfaceTypeDefinitionNode:
return $this->makeInterfaceDef($def); return $this->makeInterfaceDef($def);
case NodeKind::ENUM_TYPE_DEFINITION: case $def instanceof EnumTypeDefinitionNode:
return $this->makeEnumDef($def); return $this->makeEnumDef($def);
case NodeKind::UNION_TYPE_DEFINITION: case $def instanceof UnionTypeDefinitionNode:
return $this->makeUnionDef($def); return $this->makeUnionDef($def);
case NodeKind::SCALAR_TYPE_DEFINITION: case $def instanceof ScalarTypeDefinitionNode:
return $this->makeScalarDef($def); return $this->makeScalarDef($def);
case NodeKind::INPUT_OBJECT_TYPE_DEFINITION: case $def instanceof InputObjectTypeDefinitionNode:
return $this->makeInputObjectDef($def); return $this->makeInputObjectDef($def);
default: default:
throw new Error(sprintf('Type kind of %s not supported.', $def->kind)); throw new Error(sprintf('Type kind of %s not supported.', $def->kind));
@ -397,8 +394,8 @@ class ASTDefinitionBuilder
function ($typeNode) { function ($typeNode) {
return $this->buildType($typeNode); return $this->buildType($typeNode);
} }
) : )
[], : [],
'astNode' => $def, 'astNode' => $def,
]); ]);
} }
@ -430,62 +427,48 @@ class ASTDefinitionBuilder
} }
/** /**
* @param ObjectTypeDefinitionNode|InterfaceTypeDefinitionNode|EnumTypeExtensionNode|ScalarTypeDefinitionNode|InputObjectTypeDefinitionNode $def * @param mixed[] $config
* @param mixed[] $config
* *
* @return CustomScalarType|EnumType|InputObjectType|InterfaceType|ObjectType|UnionType * @return CustomScalarType|EnumType|InputObjectType|InterfaceType|ObjectType|UnionType
* *
* @throws Error * @throws Error
*/ */
private function makeSchemaDefFromConfig($def, array $config) private function makeSchemaDefFromConfig(Node $def, array $config)
{ {
if (! $def) { switch (true) {
throw new Error('def must be defined.'); case $def instanceof ObjectTypeDefinitionNode:
}
switch ($def->kind) {
case NodeKind::OBJECT_TYPE_DEFINITION:
return new ObjectType($config); return new ObjectType($config);
case NodeKind::INTERFACE_TYPE_DEFINITION: case $def instanceof InterfaceTypeDefinitionNode:
return new InterfaceType($config); return new InterfaceType($config);
case NodeKind::ENUM_TYPE_DEFINITION: case $def instanceof EnumTypeDefinitionNode:
return new EnumType($config); return new EnumType($config);
case NodeKind::UNION_TYPE_DEFINITION: case $def instanceof UnionTypeDefinitionNode:
return new UnionType($config); return new UnionType($config);
case NodeKind::SCALAR_TYPE_DEFINITION: case $def instanceof ScalarTypeDefinitionNode:
return new CustomScalarType($config); return new CustomScalarType($config);
case NodeKind::INPUT_OBJECT_TYPE_DEFINITION: case $def instanceof InputObjectTypeDefinitionNode:
return new InputObjectType($config); return new InputObjectType($config);
default: default:
throw new Error(sprintf('Type kind of %s not supported.', $def->kind)); throw new Error(sprintf('Type kind of %s not supported.', $def->kind));
} }
} }
/** private function getNamedTypeNode(TypeNode $typeNode) : TypeNode
* @param TypeNode|ListTypeNode|NonNullTypeNode $typeNode
*
* @return TypeNode
*/
private function getNamedTypeNode(TypeNode $typeNode)
{ {
$namedType = $typeNode; $namedType = $typeNode;
while ($namedType->kind === NodeKind::LIST_TYPE || $namedType->kind === NodeKind::NON_NULL_TYPE) { while ($namedType instanceof ListTypeNode || $namedType instanceof NonNullTypeNode) {
$namedType = $namedType->type; $namedType = $namedType->type;
} }
return $namedType; return $namedType;
} }
/** private function buildWrappedType(Type $innerType, TypeNode $inputTypeNode) : Type
* @param TypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode
*
* @return Type
*/
private function buildWrappedType(Type $innerType, TypeNode $inputTypeNode)
{ {
if ($inputTypeNode->kind === NodeKind::LIST_TYPE) { if ($inputTypeNode instanceof ListTypeNode) {
return Type::listOf($this->buildWrappedType($innerType, $inputTypeNode->type)); return Type::listOf($this->buildWrappedType($innerType, $inputTypeNode->type));
} }
if ($inputTypeNode->kind === NodeKind::NON_NULL_TYPE) { if ($inputTypeNode instanceof NonNullTypeNode) {
$wrappedType = $this->buildWrappedType($innerType, $inputTypeNode->type); $wrappedType = $this->buildWrappedType($innerType, $inputTypeNode->type);
return Type::nonNull(NonNull::assertNullableType($wrappedType)); return Type::nonNull(NonNull::assertNullableType($wrappedType));

View file

@ -112,27 +112,31 @@ class BreakingChangesFinder
* @return string[][] * @return string[][]
*/ */
public static function findTypesThatChangedKind( public static function findTypesThatChangedKind(
Schema $oldSchema, Schema $schemaA,
Schema $newSchema Schema $schemaB
) { ) : iterable {
$oldTypeMap = $oldSchema->getTypeMap(); $schemaATypeMap = $schemaA->getTypeMap();
$newTypeMap = $newSchema->getTypeMap(); $schemaBTypeMap = $schemaB->getTypeMap();
$breakingChanges = []; $breakingChanges = [];
foreach ($oldTypeMap as $typeName => $oldType) { foreach ($schemaATypeMap as $typeName => $schemaAType) {
if (! isset($newTypeMap[$typeName])) { if (! isset($schemaBTypeMap[$typeName])) {
continue; continue;
} }
$newType = $newTypeMap[$typeName]; $schemaBType = $schemaBTypeMap[$typeName];
if ($oldType instanceof $newType) { if ($schemaAType instanceof $schemaBType) {
continue; continue;
} }
$oldTypeKindName = self::typeKindName($oldType); if ($schemaBType instanceof $schemaAType) {
$newTypeKindName = self::typeKindName($newType); continue;
$breakingChanges[] = [ }
$schemaATypeKindName = self::typeKindName($schemaAType);
$schemaBTypeKindName = self::typeKindName($schemaBType);
$breakingChanges[] = [
'type' => self::BREAKING_CHANGE_TYPE_CHANGED_KIND, 'type' => self::BREAKING_CHANGE_TYPE_CHANGED_KIND,
'description' => "${typeName} changed from ${oldTypeKindName} to ${newTypeKindName}.", 'description' => "${typeName} changed from ${schemaATypeKindName} to ${schemaBTypeKindName}.",
]; ];
} }
@ -527,12 +531,12 @@ class BreakingChangesFinder
]; ];
} }
// Check if a non-null arg was added to the field // Check if a non-null arg was added to the field
foreach ($newTypeFields[$fieldName]->args as $newArgDef) { foreach ($newTypeFields[$fieldName]->args as $newTypeFieldArgDef) {
$oldArgs = $oldTypeFields[$fieldName]->args; $oldArgs = $oldTypeFields[$fieldName]->args;
$oldArgDef = Utils::find( $oldArgDef = Utils::find(
$oldArgs, $oldArgs,
static function ($arg) use ($newArgDef) { static function ($arg) use ($newTypeFieldArgDef) {
return $arg->name === $newArgDef->name; return $arg->name === $newTypeFieldArgDef->name;
} }
); );
@ -541,8 +545,8 @@ class BreakingChangesFinder
} }
$newTypeName = $newType->name; $newTypeName = $newType->name;
$newArgName = $newArgDef->name; $newArgName = $newTypeFieldArgDef->name;
if ($newArgDef->getType() instanceof NonNull) { if ($newTypeFieldArgDef->getType() instanceof NonNull) {
$breakingChanges[] = [ $breakingChanges[] = [
'type' => self::BREAKING_CHANGE_NON_NULL_ARG_ADDED, 'type' => self::BREAKING_CHANGE_NON_NULL_ARG_ADDED,
'description' => "A non-null arg ${newArgName} on ${newTypeName}.${fieldName} was added", 'description' => "A non-null arg ${newArgName} on ${newTypeName}.${fieldName} was added",
@ -664,7 +668,7 @@ class BreakingChangesFinder
{ {
$removedArgs = []; $removedArgs = [];
$newArgMap = self::getArgumentMapForDirective($newDirective); $newArgMap = self::getArgumentMapForDirective($newDirective);
foreach ((array) $oldDirective->args as $arg) { foreach ($oldDirective->args as $arg) {
if (isset($newArgMap[$arg->name])) { if (isset($newArgMap[$arg->name])) {
continue; continue;
} }
@ -723,7 +727,7 @@ class BreakingChangesFinder
{ {
$addedArgs = []; $addedArgs = [];
$oldArgMap = self::getArgumentMapForDirective($oldDirective); $oldArgMap = self::getArgumentMapForDirective($oldDirective);
foreach ((array) $newDirective->args as $arg) { foreach ($newDirective->args as $arg) {
if (isset($oldArgMap[$arg->name])) { if (isset($oldArgMap[$arg->name])) {
continue; continue;
} }

View file

@ -5,10 +5,16 @@ declare(strict_types=1);
namespace GraphQL\Utils; namespace GraphQL\Utils;
use GraphQL\Error\Error; use GraphQL\Error\Error;
use GraphQL\Language\AST\DirectiveDefinitionNode;
use GraphQL\Language\AST\DocumentNode; use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\AST\EnumTypeDefinitionNode;
use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
use GraphQL\Language\AST\Node; use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\ObjectTypeDefinitionNode;
use GraphQL\Language\AST\ScalarTypeDefinitionNode;
use GraphQL\Language\AST\SchemaDefinitionNode; use GraphQL\Language\AST\SchemaDefinitionNode;
use GraphQL\Language\AST\UnionTypeDefinitionNode;
use GraphQL\Language\Parser; use GraphQL\Language\Parser;
use GraphQL\Language\Source; use GraphQL\Language\Source;
use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\Directive;
@ -95,39 +101,38 @@ class BuildSchema
public function buildSchema() public function buildSchema()
{ {
/** @var SchemaDefinitionNode $schemaDef */
$schemaDef = null; $schemaDef = null;
$typeDefs = []; $typeDefs = [];
$this->nodeMap = []; $this->nodeMap = [];
$directiveDefs = []; $directiveDefs = [];
foreach ($this->ast->definitions as $d) { foreach ($this->ast->definitions as $definition) {
switch ($d->kind) { switch (true) {
case NodeKind::SCHEMA_DEFINITION: case $definition instanceof SchemaDefinitionNode:
if ($schemaDef) { if ($schemaDef !== null) {
throw new Error('Must provide only one schema definition.'); throw new Error('Must provide only one schema definition.');
} }
$schemaDef = $d; $schemaDef = $definition;
break; break;
case NodeKind::SCALAR_TYPE_DEFINITION: case $definition instanceof ScalarTypeDefinitionNode:
case NodeKind::OBJECT_TYPE_DEFINITION: case $definition instanceof ObjectTypeDefinitionNode:
case NodeKind::INTERFACE_TYPE_DEFINITION: case $definition instanceof InterfaceTypeDefinitionNode:
case NodeKind::ENUM_TYPE_DEFINITION: case $definition instanceof EnumTypeDefinitionNode:
case NodeKind::UNION_TYPE_DEFINITION: case $definition instanceof UnionTypeDefinitionNode:
case NodeKind::INPUT_OBJECT_TYPE_DEFINITION: case $definition instanceof InputObjectTypeDefinitionNode:
$typeName = $d->name->value; $typeName = $definition->name->value;
if (! empty($this->nodeMap[$typeName])) { if (! empty($this->nodeMap[$typeName])) {
throw new Error(sprintf('Type "%s" was defined more than once.', $typeName)); throw new Error(sprintf('Type "%s" was defined more than once.', $typeName));
} }
$typeDefs[] = $d; $typeDefs[] = $definition;
$this->nodeMap[$typeName] = $d; $this->nodeMap[$typeName] = $definition;
break; break;
case NodeKind::DIRECTIVE_DEFINITION: case $definition instanceof DirectiveDefinitionNode:
$directiveDefs[] = $d; $directiveDefs[] = $definition;
break; break;
} }
} }
$operationTypes = $schemaDef $operationTypes = $schemaDef !== null
? $this->getOperationTypes($schemaDef) ? $this->getOperationTypes($schemaDef)
: [ : [
'query' => isset($this->nodeMap['Query']) ? 'Query' : null, 'query' => isset($this->nodeMap['Query']) ? 'Query' : null,
@ -154,9 +159,10 @@ class BuildSchema
// If specified directives were not explicitly declared, add them. // If specified directives were not explicitly declared, add them.
$skip = array_reduce( $skip = array_reduce(
$directives, $directives,
static function ($hasSkip, $directive) { static function (bool $hasSkip, Directive $directive) : bool {
return $hasSkip || $directive->name === 'skip'; return $hasSkip || $directive->name === 'skip';
} },
false
); );
if (! $skip) { if (! $skip) {
$directives[] = Directive::skipDirective(); $directives[] = Directive::skipDirective();
@ -164,9 +170,10 @@ class BuildSchema
$include = array_reduce( $include = array_reduce(
$directives, $directives,
static function ($hasInclude, $directive) { static function (bool $hasInclude, Directive $directive) : bool {
return $hasInclude || $directive->name === 'include'; return $hasInclude || $directive->name === 'include';
} },
false
); );
if (! $include) { if (! $include) {
$directives[] = Directive::includeDirective(); $directives[] = Directive::includeDirective();
@ -174,9 +181,10 @@ class BuildSchema
$deprecated = array_reduce( $deprecated = array_reduce(
$directives, $directives,
static function ($hasDeprecated, $directive) { static function (bool $hasDeprecated, Directive $directive) : bool {
return $hasDeprecated || $directive->name === 'deprecated'; return $hasDeprecated || $directive->name === 'deprecated';
} },
false
); );
if (! $deprecated) { if (! $deprecated) {
$directives[] = Directive::deprecatedDirective(); $directives[] = Directive::deprecatedDirective();

View file

@ -21,9 +21,7 @@ use function is_string;
* Similar to PHP array, but allows any type of data to act as key (including arrays, objects, scalars) * Similar to PHP array, but allows any type of data to act as key (including arrays, objects, scalars)
* *
* Note: unfortunately when storing array as key - access and modification is O(N) * Note: unfortunately when storing array as key - access and modification is O(N)
* (yet this should be really rare case and should be avoided when possible) * (yet this should rarely be the case and should be avoided when possible)
*
* Class MixedStore
*/ */
class MixedStore implements ArrayAccess class MixedStore implements ArrayAccess
{ {

View file

@ -7,13 +7,16 @@ namespace GraphQL\Utils;
use GraphQL\Error\Error; use GraphQL\Error\Error;
use GraphQL\Language\AST\DirectiveDefinitionNode; use GraphQL\Language\AST\DirectiveDefinitionNode;
use GraphQL\Language\AST\DocumentNode; use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\AST\EnumTypeExtensionNode;
use GraphQL\Language\AST\InputObjectTypeExtensionNode;
use GraphQL\Language\AST\InterfaceTypeExtensionNode;
use GraphQL\Language\AST\Node; use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\ObjectTypeExtensionNode; use GraphQL\Language\AST\ObjectTypeExtensionNode;
use GraphQL\Language\AST\SchemaDefinitionNode; use GraphQL\Language\AST\SchemaDefinitionNode;
use GraphQL\Language\AST\SchemaTypeExtensionNode; use GraphQL\Language\AST\SchemaTypeExtensionNode;
use GraphQL\Language\AST\TypeDefinitionNode; use GraphQL\Language\AST\TypeDefinitionNode;
use GraphQL\Language\AST\TypeExtensionNode; use GraphQL\Language\AST\TypeExtensionNode;
use GraphQL\Language\AST\UnionTypeExtensionNode;
use GraphQL\Type\Definition\CustomScalarType; use GraphQL\Type\Definition\CustomScalarType;
use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\EnumType;
@ -66,6 +69,7 @@ class SchemaExtender
return $type->extensionASTNodes; return $type->extensionASTNodes;
} }
return static::$typeExtensionsMap[$name] ?? null; return static::$typeExtensionsMap[$name] ?? null;
} }
@ -74,8 +78,8 @@ class SchemaExtender
*/ */
protected static function checkExtensionNode(Type $type, Node $node) : void protected static function checkExtensionNode(Type $type, Node $node) : void
{ {
switch ($node->kind) { switch (true) {
case NodeKind::OBJECT_TYPE_EXTENSION: case $node instanceof ObjectTypeExtensionNode:
if (! ($type instanceof ObjectType)) { if (! ($type instanceof ObjectType)) {
throw new Error( throw new Error(
'Cannot extend non-object type "' . $type->name . '".', 'Cannot extend non-object type "' . $type->name . '".',
@ -83,7 +87,7 @@ class SchemaExtender
); );
} }
break; break;
case NodeKind::INTERFACE_TYPE_EXTENSION: case $node instanceof InterfaceTypeExtensionNode:
if (! ($type instanceof InterfaceType)) { if (! ($type instanceof InterfaceType)) {
throw new Error( throw new Error(
'Cannot extend non-interface type "' . $type->name . '".', 'Cannot extend non-interface type "' . $type->name . '".',
@ -91,7 +95,7 @@ class SchemaExtender
); );
} }
break; break;
case NodeKind::ENUM_TYPE_EXTENSION: case $node instanceof EnumTypeExtensionNode:
if (! ($type instanceof EnumType)) { if (! ($type instanceof EnumType)) {
throw new Error( throw new Error(
'Cannot extend non-enum type "' . $type->name . '".', 'Cannot extend non-enum type "' . $type->name . '".',
@ -99,7 +103,7 @@ class SchemaExtender
); );
} }
break; break;
case NodeKind::UNION_TYPE_EXTENSION: case $node instanceof UnionTypeExtensionNode:
if (! ($type instanceof UnionType)) { if (! ($type instanceof UnionType)) {
throw new Error( throw new Error(
'Cannot extend non-union type "' . $type->name . '".', 'Cannot extend non-union type "' . $type->name . '".',
@ -107,7 +111,7 @@ class SchemaExtender
); );
} }
break; break;
case NodeKind::INPUT_OBJECT_TYPE_EXTENSION: case $node instanceof InputObjectTypeExtensionNode:
if (! ($type instanceof InputObjectType)) { if (! ($type instanceof InputObjectType)) {
throw new Error( throw new Error(
'Cannot extend non-input object type "' . $type->name . '".', 'Cannot extend non-input object type "' . $type->name . '".',
@ -284,6 +288,7 @@ class SchemaExtender
} }
} }
} }
return $interfaces; return $interfaces;
} }
@ -604,7 +609,9 @@ class SchemaExtender
} }
$schemaExtensionASTNodes = count($schemaExtensions) > 0 $schemaExtensionASTNodes = count($schemaExtensions) > 0
? ($schema->extensionASTNodes ? array_merge($schema->extensionASTNodes, $schemaExtensions) : $schemaExtensions) ? ($schema->extensionASTNodes
? array_merge($schema->extensionASTNodes, $schemaExtensions)
: $schemaExtensions)
: $schema->extensionASTNodes; : $schema->extensionASTNodes;
$types = array_merge( $types = array_merge(

View file

@ -153,11 +153,8 @@ class SchemaPrinter
} }
$subscriptionType = $schema->getSubscriptionType(); $subscriptionType = $schema->getSubscriptionType();
if ($subscriptionType && $subscriptionType->name !== 'Subscription') {
return false;
}
return true; return ! $subscriptionType || $subscriptionType->name === 'Subscription';
} }
private static function printDirective($directive, $options) : string private static function printDirective($directive, $options) : string
@ -356,8 +353,8 @@ class SchemaPrinter
private static function printObject(ObjectType $type, array $options) : string private static function printObject(ObjectType $type, array $options) : string
{ {
$interfaces = $type->getInterfaces(); $interfaces = $type->getInterfaces();
$implementedInterfaces = ! empty($interfaces) ? $implementedInterfaces = ! empty($interfaces)
' implements ' . implode( ? ' implements ' . implode(
' & ', ' & ',
array_map( array_map(
static function ($i) { static function ($i) {
@ -365,7 +362,8 @@ class SchemaPrinter
}, },
$interfaces $interfaces
) )
) : ''; )
: '';
return self::printDescription($options, $type) . return self::printDescription($options, $type) .
sprintf("type %s%s {\n%s\n}", $type->name, $implementedInterfaces, self::printFields($options, $type)); sprintf("type %s%s {\n%s\n}", $type->name, $implementedInterfaces, self::printFields($options, $type));

View file

@ -86,18 +86,12 @@ class TypeComparators
// If superType type is an abstract type, maybeSubType type may be a currently // If superType type is an abstract type, maybeSubType type may be a currently
// possible object type. // possible object type.
if (Type::isAbstractType($superType) && return Type::isAbstractType($superType) &&
$maybeSubType instanceof ObjectType && $maybeSubType instanceof ObjectType &&
$schema->isPossibleType( $schema->isPossibleType(
$superType, $superType,
$maybeSubType $maybeSubType
) );
) {
return true;
}
// Otherwise, the child type is not a valid subtype of the parent type.
return false;
} }
/** /**
@ -131,13 +125,11 @@ class TypeComparators
return false; return false;
} }
/** @var $typeB ObjectType */
// Determine if the latter type is a possible concrete type of the former. // Determine if the latter type is a possible concrete type of the former.
return $schema->isPossibleType($typeA, $typeB); return $schema->isPossibleType($typeA, $typeB);
} }
if ($typeB instanceof AbstractType) { if ($typeB instanceof AbstractType) {
/** @var $typeA ObjectType */
// Determine if the former type is a possible concrete type of the latter. // Determine if the former type is a possible concrete type of the latter.
return $schema->isPossibleType($typeB, $typeA); return $schema->isPossibleType($typeB, $typeA);
} }

View file

@ -6,12 +6,21 @@ namespace GraphQL\Utils;
use GraphQL\Error\InvariantViolation; use GraphQL\Error\InvariantViolation;
use GraphQL\Error\Warning; use GraphQL\Error\Warning;
use GraphQL\Language\AST\ArgumentNode;
use GraphQL\Language\AST\DirectiveNode;
use GraphQL\Language\AST\EnumValueNode;
use GraphQL\Language\AST\FieldNode; use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\FragmentDefinitionNode;
use GraphQL\Language\AST\InlineFragmentNode;
use GraphQL\Language\AST\ListTypeNode; use GraphQL\Language\AST\ListTypeNode;
use GraphQL\Language\AST\ListValueNode;
use GraphQL\Language\AST\NamedTypeNode; use GraphQL\Language\AST\NamedTypeNode;
use GraphQL\Language\AST\Node; use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\NonNullTypeNode; use GraphQL\Language\AST\NonNullTypeNode;
use GraphQL\Language\AST\ObjectFieldNode;
use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Language\AST\VariableDefinitionNode;
use GraphQL\Type\Definition\CompositeType; use GraphQL\Type\Definition\CompositeType;
use GraphQL\Type\Definition\Directive; use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\EnumType; use GraphQL\Type\Definition\EnumType;
@ -28,6 +37,7 @@ use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Definition\WrappingType; use GraphQL\Type\Definition\WrappingType;
use GraphQL\Type\Introspection; use GraphQL\Type\Introspection;
use GraphQL\Type\Schema; use GraphQL\Type\Schema;
use SplStack;
use function array_map; use function array_map;
use function array_merge; use function array_merge;
use function array_pop; use function array_pop;
@ -35,24 +45,21 @@ use function count;
use function is_array; use function is_array;
use function sprintf; use function sprintf;
/**
* Class TypeInfo
*/
class TypeInfo class TypeInfo
{ {
/** @var Schema */ /** @var Schema */
private $schema; private $schema;
/** @var \SplStack<OutputType> */ /** @var SplStack<OutputType> */
private $typeStack; private $typeStack;
/** @var \SplStack<CompositeType> */ /** @var SplStack<CompositeType> */
private $parentTypeStack; private $parentTypeStack;
/** @var \SplStack<InputType> */ /** @var SplStack<InputType> */
private $inputTypeStack; private $inputTypeStack;
/** @var \SplStack<FieldDefinition> */ /** @var SplStack<FieldDefinition> */
private $fieldDefStack; private $fieldDefStack;
/** @var Directive */ /** @var Directive */
@ -155,6 +162,7 @@ class TypeInfo
if (! $alreadyInMap) { if (! $alreadyInMap) {
$typeMap[$i] = $type; $typeMap[$i] = $type;
} }
return $typeMap; return $typeMap;
} }
@ -178,7 +186,7 @@ class TypeInfo
$nestedTypes = array_merge($nestedTypes, $type->getInterfaces()); $nestedTypes = array_merge($nestedTypes, $type->getInterfaces());
} }
if ($type instanceof ObjectType || $type instanceof InterfaceType) { if ($type instanceof ObjectType || $type instanceof InterfaceType) {
foreach ((array) $type->getFields() as $fieldName => $field) { foreach ($type->getFields() as $fieldName => $field) {
if (! empty($field->args)) { if (! empty($field->args)) {
$fieldArgTypes = array_map( $fieldArgTypes = array_map(
static function (FieldArgument $arg) { static function (FieldArgument $arg) {
@ -193,12 +201,12 @@ class TypeInfo
} }
} }
if ($type instanceof InputObjectType) { if ($type instanceof InputObjectType) {
foreach ((array) $type->getFields() as $fieldName => $field) { foreach ($type->getFields() as $fieldName => $field) {
$nestedTypes[] = $field->getType(); $nestedTypes[] = $field->getType();
} }
} }
foreach ($nestedTypes as $type) { foreach ($nestedTypes as $nestedType) {
$typeMap = self::extractTypes($type, $typeMap); $typeMap = self::extractTypes($nestedType, $typeMap);
} }
return $typeMap; return $typeMap;
@ -255,13 +263,13 @@ class TypeInfo
// any assumptions of a valid schema to ensure runtime types are properly // any assumptions of a valid schema to ensure runtime types are properly
// checked before continuing since TypeInfo is used as part of validation // checked before continuing since TypeInfo is used as part of validation
// which occurs before guarantees of schema and document validity. // which occurs before guarantees of schema and document validity.
switch ($node->kind) { switch (true) {
case NodeKind::SELECTION_SET: case $node instanceof SelectionSetNode:
$namedType = Type::getNamedType($this->getType()); $namedType = Type::getNamedType($this->getType());
$this->parentTypeStack[] = Type::isCompositeType($namedType) ? $namedType : null; $this->parentTypeStack[] = Type::isCompositeType($namedType) ? $namedType : null;
break; break;
case NodeKind::FIELD: case $node instanceof FieldNode:
$parentType = $this->getParentType(); $parentType = $this->getParentType();
$fieldDef = null; $fieldDef = null;
if ($parentType) { if ($parentType) {
@ -275,11 +283,11 @@ class TypeInfo
$this->typeStack[] = Type::isOutputType($fieldType) ? $fieldType : null; $this->typeStack[] = Type::isOutputType($fieldType) ? $fieldType : null;
break; break;
case NodeKind::DIRECTIVE: case $node instanceof DirectiveNode:
$this->directive = $schema->getDirective($node->name->value); $this->directive = $schema->getDirective($node->name->value);
break; break;
case NodeKind::OPERATION_DEFINITION: case $node instanceof OperationDefinitionNode:
$type = null; $type = null;
if ($node->operation === 'query') { if ($node->operation === 'query') {
$type = $schema->getQueryType(); $type = $schema->getQueryType();
@ -291,22 +299,24 @@ class TypeInfo
$this->typeStack[] = Type::isOutputType($type) ? $type : null; $this->typeStack[] = Type::isOutputType($type) ? $type : null;
break; break;
case NodeKind::INLINE_FRAGMENT: case $node instanceof InlineFragmentNode:
case NodeKind::FRAGMENT_DEFINITION: case $node instanceof FragmentDefinitionNode:
$typeConditionNode = $node->typeCondition; $typeConditionNode = $node->typeCondition;
$outputType = $typeConditionNode ? self::typeFromAST( $outputType = $typeConditionNode
$schema, ? self::typeFromAST(
$typeConditionNode $schema,
) : Type::getNamedType($this->getType()); $typeConditionNode
)
: Type::getNamedType($this->getType());
$this->typeStack[] = Type::isOutputType($outputType) ? $outputType : null; $this->typeStack[] = Type::isOutputType($outputType) ? $outputType : null;
break; break;
case NodeKind::VARIABLE_DEFINITION: case $node instanceof VariableDefinitionNode:
$inputType = self::typeFromAST($schema, $node->type); $inputType = self::typeFromAST($schema, $node->type);
$this->inputTypeStack[] = Type::isInputType($inputType) ? $inputType : null; // push $this->inputTypeStack[] = Type::isInputType($inputType) ? $inputType : null; // push
break; break;
case NodeKind::ARGUMENT: case $node instanceof ArgumentNode:
$fieldOrDirective = $this->getDirective() ?: $this->getFieldDef(); $fieldOrDirective = $this->getDirective() ?: $this->getFieldDef();
$argDef = $argType = null; $argDef = $argType = null;
if ($fieldOrDirective) { if ($fieldOrDirective) {
@ -324,7 +334,7 @@ class TypeInfo
$this->inputTypeStack[] = Type::isInputType($argType) ? $argType : null; $this->inputTypeStack[] = Type::isInputType($argType) ? $argType : null;
break; break;
case NodeKind::LST: case $node instanceof ListValueNode:
$listType = Type::getNullableType($this->getInputType()); $listType = Type::getNullableType($this->getInputType());
$itemType = $listType instanceof ListOfType $itemType = $listType instanceof ListOfType
? $listType->getWrappedType() ? $listType->getWrappedType()
@ -332,7 +342,7 @@ class TypeInfo
$this->inputTypeStack[] = Type::isInputType($itemType) ? $itemType : null; $this->inputTypeStack[] = Type::isInputType($itemType) ? $itemType : null;
break; break;
case NodeKind::OBJECT_FIELD: case $node instanceof ObjectFieldNode:
$objectType = Type::getNamedType($this->getInputType()); $objectType = Type::getNamedType($this->getInputType());
$fieldType = null; $fieldType = null;
$inputFieldType = null; $inputFieldType = null;
@ -344,7 +354,7 @@ class TypeInfo
$this->inputTypeStack[] = Type::isInputType($inputFieldType) ? $inputFieldType : null; $this->inputTypeStack[] = Type::isInputType($inputFieldType) ? $inputFieldType : null;
break; break;
case NodeKind::ENUM: case $node instanceof EnumValueNode:
$enumType = Type::getNamedType($this->getInputType()); $enumType = Type::getNamedType($this->getInputType());
$enumValue = null; $enumValue = null;
if ($enumType instanceof EnumType) { if ($enumType instanceof EnumType) {
@ -458,37 +468,37 @@ class TypeInfo
public function leave(Node $node) public function leave(Node $node)
{ {
switch ($node->kind) { switch (true) {
case NodeKind::SELECTION_SET: case $node instanceof SelectionSetNode:
array_pop($this->parentTypeStack); array_pop($this->parentTypeStack);
break; break;
case NodeKind::FIELD: case $node instanceof FieldNode:
array_pop($this->fieldDefStack); array_pop($this->fieldDefStack);
array_pop($this->typeStack); array_pop($this->typeStack);
break; break;
case NodeKind::DIRECTIVE: case $node instanceof DirectiveNode:
$this->directive = null; $this->directive = null;
break; break;
case NodeKind::OPERATION_DEFINITION: case $node instanceof OperationDefinitionNode:
case NodeKind::INLINE_FRAGMENT: case $node instanceof InlineFragmentNode:
case NodeKind::FRAGMENT_DEFINITION: case $node instanceof FragmentDefinitionNode:
array_pop($this->typeStack); array_pop($this->typeStack);
break; break;
case NodeKind::VARIABLE_DEFINITION: case $node instanceof VariableDefinitionNode:
array_pop($this->inputTypeStack); array_pop($this->inputTypeStack);
break; break;
case NodeKind::ARGUMENT: case $node instanceof ArgumentNode:
$this->argument = null; $this->argument = null;
array_pop($this->inputTypeStack); array_pop($this->inputTypeStack);
break; break;
case NodeKind::LST: case $node instanceof ListValueNode:
case NodeKind::OBJECT_FIELD: case $node instanceof ObjectFieldNode:
array_pop($this->inputTypeStack); array_pop($this->inputTypeStack);
break; break;
case NodeKind::ENUM: case $node instanceof EnumValueNode:
$this->enumValue = null; $this->enumValue = null;
break; break;
} }

View file

@ -264,8 +264,8 @@ class Utils
$grouped = []; $grouped = [];
foreach ($traversable as $key => $value) { foreach ($traversable as $key => $value) {
$newKeys = (array) $keyFn($value, $key); $newKeys = (array) $keyFn($value, $key);
foreach ($newKeys as $key) { foreach ($newKeys as $newKey) {
$grouped[$key][] = $value; $grouped[$newKey][] = $value;
} }
} }

View file

@ -199,7 +199,7 @@ class Value
} }
$suggestions = Utils::suggestionList( $suggestions = Utils::suggestionList(
$fieldName, (string) $fieldName,
array_keys($fields) array_keys($fields)
); );
$didYouMean = $suggestions $didYouMean = $suggestions

View file

@ -52,6 +52,6 @@ class DisableIntrospection extends QuerySecurityRule
protected function isEnabled() protected function isEnabled()
{ {
return $this->isEnabled !== static::DISABLED; return $this->isEnabled !== self::DISABLED;
} }
} }

View file

@ -6,6 +6,8 @@ namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error; use GraphQL\Error\Error;
use GraphQL\Language\AST\ArgumentNode; use GraphQL\Language\AST\ArgumentNode;
use GraphQL\Language\AST\DirectiveNode;
use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\Node; use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\NodeList; use GraphQL\Language\AST\NodeList;
@ -34,7 +36,7 @@ class KnownArgumentNames extends ValidationRule
} }
$argumentOf = $ancestors[count($ancestors) - 1]; $argumentOf = $ancestors[count($ancestors) - 1];
if ($argumentOf->kind === NodeKind::FIELD) { if ($argumentOf instanceof FieldNode) {
$fieldDef = $context->getFieldDef(); $fieldDef = $context->getFieldDef();
$parentType = $context->getParentType(); $parentType = $context->getParentType();
if ($fieldDef && $parentType) { if ($fieldDef && $parentType) {
@ -56,7 +58,7 @@ class KnownArgumentNames extends ValidationRule
[$node] [$node]
)); ));
} }
} elseif ($argumentOf->kind === NodeKind::DIRECTIVE) { } elseif ($argumentOf instanceof DirectiveNode) {
$directive = $context->getDirective(); $directive = $context->getDirective();
if ($directive) { if ($directive) {
$context->reportError(new Error( $context->reportError(new Error(

View file

@ -7,10 +7,31 @@ namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error; use GraphQL\Error\Error;
use GraphQL\Language\AST\DirectiveDefinitionNode; use GraphQL\Language\AST\DirectiveDefinitionNode;
use GraphQL\Language\AST\DirectiveNode; use GraphQL\Language\AST\DirectiveNode;
use GraphQL\Language\AST\EnumTypeDefinitionNode;
use GraphQL\Language\AST\EnumTypeExtensionNode;
use GraphQL\Language\AST\EnumValueDefinitionNode;
use GraphQL\Language\AST\FieldDefinitionNode;
use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\FragmentDefinitionNode;
use GraphQL\Language\AST\FragmentSpreadNode;
use GraphQL\Language\AST\InlineFragmentNode;
use GraphQL\Language\AST\InputObjectTypeDefinitionNode; use GraphQL\Language\AST\InputObjectTypeDefinitionNode;
use GraphQL\Language\AST\InputObjectTypeExtensionNode;
use GraphQL\Language\AST\InputValueDefinitionNode;
use GraphQL\Language\AST\InterfaceTypeDefinitionNode;
use GraphQL\Language\AST\InterfaceTypeExtensionNode;
use GraphQL\Language\AST\Node; use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\NodeList; use GraphQL\Language\AST\NodeList;
use GraphQL\Language\AST\ObjectTypeDefinitionNode;
use GraphQL\Language\AST\ObjectTypeExtensionNode;
use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Language\AST\ScalarTypeDefinitionNode;
use GraphQL\Language\AST\ScalarTypeExtensionNode;
use GraphQL\Language\AST\SchemaDefinitionNode;
use GraphQL\Language\AST\SchemaTypeExtensionNode;
use GraphQL\Language\AST\UnionTypeDefinitionNode;
use GraphQL\Language\AST\UnionTypeExtensionNode;
use GraphQL\Language\DirectiveLocation; use GraphQL\Language\DirectiveLocation;
use GraphQL\Validator\ValidationContext; use GraphQL\Validator\ValidationContext;
use function array_map; use function array_map;
@ -96,8 +117,8 @@ class KnownDirectives extends ValidationRule
private function getDirectiveLocationForASTPath(array $ancestors) private function getDirectiveLocationForASTPath(array $ancestors)
{ {
$appliedTo = $ancestors[count($ancestors) - 1]; $appliedTo = $ancestors[count($ancestors) - 1];
switch ($appliedTo->kind) { switch (true) {
case NodeKind::OPERATION_DEFINITION: case $appliedTo instanceof OperationDefinitionNode:
switch ($appliedTo->operation) { switch ($appliedTo->operation) {
case 'query': case 'query':
return DirectiveLocation::QUERY; return DirectiveLocation::QUERY;
@ -107,40 +128,40 @@ class KnownDirectives extends ValidationRule
return DirectiveLocation::SUBSCRIPTION; return DirectiveLocation::SUBSCRIPTION;
} }
break; break;
case NodeKind::FIELD: case $appliedTo instanceof FieldNode:
return DirectiveLocation::FIELD; return DirectiveLocation::FIELD;
case NodeKind::FRAGMENT_SPREAD: case $appliedTo instanceof FragmentSpreadNode:
return DirectiveLocation::FRAGMENT_SPREAD; return DirectiveLocation::FRAGMENT_SPREAD;
case NodeKind::INLINE_FRAGMENT: case $appliedTo instanceof InlineFragmentNode:
return DirectiveLocation::INLINE_FRAGMENT; return DirectiveLocation::INLINE_FRAGMENT;
case NodeKind::FRAGMENT_DEFINITION: case $appliedTo instanceof FragmentDefinitionNode:
return DirectiveLocation::FRAGMENT_DEFINITION; return DirectiveLocation::FRAGMENT_DEFINITION;
case NodeKind::SCHEMA_DEFINITION: case $appliedTo instanceof SchemaDefinitionNode:
case NodeKind::SCHEMA_EXTENSION: case $appliedTo instanceof SchemaTypeExtensionNode:
return DirectiveLocation::SCHEMA; return DirectiveLocation::SCHEMA;
case NodeKind::SCALAR_TYPE_DEFINITION: case $appliedTo instanceof ScalarTypeDefinitionNode:
case NodeKind::SCALAR_TYPE_EXTENSION: case $appliedTo instanceof ScalarTypeExtensionNode:
return DirectiveLocation::SCALAR; return DirectiveLocation::SCALAR;
case NodeKind::OBJECT_TYPE_DEFINITION: case $appliedTo instanceof ObjectTypeDefinitionNode:
case NodeKind::OBJECT_TYPE_EXTENSION: case $appliedTo instanceof ObjectTypeExtensionNode:
return DirectiveLocation::OBJECT; return DirectiveLocation::OBJECT;
case NodeKind::FIELD_DEFINITION: case $appliedTo instanceof FieldDefinitionNode:
return DirectiveLocation::FIELD_DEFINITION; return DirectiveLocation::FIELD_DEFINITION;
case NodeKind::INTERFACE_TYPE_DEFINITION: case $appliedTo instanceof InterfaceTypeDefinitionNode:
case NodeKind::INTERFACE_TYPE_EXTENSION: case $appliedTo instanceof InterfaceTypeExtensionNode:
return DirectiveLocation::IFACE; return DirectiveLocation::IFACE;
case NodeKind::UNION_TYPE_DEFINITION: case $appliedTo instanceof UnionTypeDefinitionNode:
case NodeKind::UNION_TYPE_EXTENSION: case $appliedTo instanceof UnionTypeExtensionNode:
return DirectiveLocation::UNION; return DirectiveLocation::UNION;
case NodeKind::ENUM_TYPE_DEFINITION: case $appliedTo instanceof EnumTypeDefinitionNode:
case NodeKind::ENUM_TYPE_EXTENSION: case $appliedTo instanceof EnumTypeExtensionNode:
return DirectiveLocation::ENUM; return DirectiveLocation::ENUM;
case NodeKind::ENUM_VALUE_DEFINITION: case $appliedTo instanceof EnumValueDefinitionNode:
return DirectiveLocation::ENUM_VALUE; return DirectiveLocation::ENUM_VALUE;
case NodeKind::INPUT_OBJECT_TYPE_DEFINITION: case $appliedTo instanceof InputObjectTypeDefinitionNode:
case NodeKind::INPUT_OBJECT_TYPE_EXTENSION: case $appliedTo instanceof InputObjectTypeExtensionNode:
return DirectiveLocation::INPUT_OBJECT; return DirectiveLocation::INPUT_OBJECT;
case NodeKind::INPUT_VALUE_DEFINITION: case $appliedTo instanceof InputValueDefinitionNode:
$parentNode = $ancestors[count($ancestors) - 3]; $parentNode = $ancestors[count($ancestors) - 3];
return $parentNode instanceof InputObjectTypeDefinitionNode return $parentNode instanceof InputObjectTypeDefinitionNode

View file

@ -30,7 +30,7 @@ class LoneAnonymousOperation extends ValidationRule
$tmp = Utils::filter( $tmp = Utils::filter(
$node->definitions, $node->definitions,
static function (Node $definition) { static function (Node $definition) {
return $definition->kind === NodeKind::OPERATION_DEFINITION; return $definition instanceof OperationDefinitionNode;
} }
); );

View file

@ -19,12 +19,14 @@ class LoneSchemaDefinition extends ValidationRule
public function getVisitor(ValidationContext $context) public function getVisitor(ValidationContext $context)
{ {
$oldSchema = $context->getSchema(); $oldSchema = $context->getSchema();
$alreadyDefined = $oldSchema !== null ? ( $alreadyDefined = $oldSchema !== null
$oldSchema->getAstNode() || ? (
$oldSchema->getQueryType() || $oldSchema->getAstNode() ||
$oldSchema->getMutationType() || $oldSchema->getQueryType() ||
$oldSchema->getSubscriptionType() $oldSchema->getMutationType() ||
) : false; $oldSchema->getSubscriptionType()
)
: false;
$schemaDefinitionsCount = 0; $schemaDefinitionsCount = 0;
@ -32,6 +34,7 @@ class LoneSchemaDefinition extends ValidationRule
NodeKind::SCHEMA_DEFINITION => static function (SchemaDefinitionNode $node) use ($alreadyDefined, $context, &$schemaDefinitionsCount) { NodeKind::SCHEMA_DEFINITION => static function (SchemaDefinitionNode $node) use ($alreadyDefined, $context, &$schemaDefinitionsCount) {
if ($alreadyDefined !== false) { if ($alreadyDefined !== false) {
$context->reportError(new Error('Cannot define a new schema within a schema extension.', $node)); $context->reportError(new Error('Cannot define a new schema within a schema extension.', $node));
return; return;
} }

View file

@ -12,8 +12,6 @@ use GraphQL\Validator\ValidationContext;
use function sprintf; use function sprintf;
/** /**
* Class NoUndefinedVariables
*
* A GraphQL operation is only valid if all variables encountered, both directly * A GraphQL operation is only valid if all variables encountered, both directly
* and via fragment spreads, are defined by that operation. * and via fragment spreads, are defined by that operation.
*/ */

View file

@ -473,24 +473,24 @@ class OverlappingFieldsCanBeMerged extends ValidationRule
private function doTypesConflict(OutputType $type1, OutputType $type2) private function doTypesConflict(OutputType $type1, OutputType $type2)
{ {
if ($type1 instanceof ListOfType) { if ($type1 instanceof ListOfType) {
return $type2 instanceof ListOfType ? return $type2 instanceof ListOfType
$this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType()) : ? $this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType())
true; : true;
} }
if ($type2 instanceof ListOfType) { if ($type2 instanceof ListOfType) {
return $type1 instanceof ListOfType ? return $type1 instanceof ListOfType
$this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType()) : ? $this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType())
true; : true;
} }
if ($type1 instanceof NonNull) { if ($type1 instanceof NonNull) {
return $type2 instanceof NonNull ? return $type2 instanceof NonNull
$this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType()) : ? $this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType())
true; : true;
} }
if ($type2 instanceof NonNull) { if ($type2 instanceof NonNull) {
return $type1 instanceof NonNull ? return $type1 instanceof NonNull
$this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType()) : ? $this->doTypesConflict($type1->getWrappedType(), $type2->getWrappedType())
true; : true;
} }
if (Type::isLeafType($type1) || Type::isLeafType($type2)) { if (Type::isLeafType($type1) || Type::isLeafType($type2)) {
return $type1 !== $type2; return $type1 !== $type2;

View file

@ -66,13 +66,15 @@ class ProvidedRequiredArgumentsOnDirectives extends ValidationRule
} }
$requiredArgsMap[$def->name->value] = Utils::keyMap( $requiredArgsMap[$def->name->value] = Utils::keyMap(
$arguments ? array_filter($arguments, static function (Node $argument) : bool { $arguments
return $argument instanceof NonNullTypeNode && ? array_filter($arguments, static function (Node $argument) : bool {
( return $argument instanceof NonNullTypeNode &&
! isset($argument->defaultValue) || (
$argument->defaultValue === null ! isset($argument->defaultValue) ||
); $argument->defaultValue === null
}) : [], );
})
: [],
static function (NamedTypeNode $argument) : string { static function (NamedTypeNode $argument) : string {
return $argument->name->value; return $argument->name->value;
} }

View file

@ -110,9 +110,8 @@ class QueryComplexity extends QuerySecurityRule
private function nodeComplexity(Node $node, $complexity = 0) private function nodeComplexity(Node $node, $complexity = 0)
{ {
switch ($node->kind) { switch (true) {
case NodeKind::FIELD: case $node instanceof FieldNode:
/** @var FieldNode $node */
// default values // default values
$args = []; $args = [];
$complexityFn = FieldDefinition::DEFAULT_COMPLEXITY_FN; $complexityFn = FieldDefinition::DEFAULT_COMPLEXITY_FN;
@ -143,16 +142,14 @@ class QueryComplexity extends QuerySecurityRule
$complexity += call_user_func_array($complexityFn, [$childrenComplexity, $args]); $complexity += call_user_func_array($complexityFn, [$childrenComplexity, $args]);
break; break;
case NodeKind::INLINE_FRAGMENT: case $node instanceof InlineFragmentNode:
/** @var InlineFragmentNode $node */
// node has children? // node has children?
if (isset($node->selectionSet)) { if (isset($node->selectionSet)) {
$complexity = $this->fieldComplexity($node, $complexity); $complexity = $this->fieldComplexity($node, $complexity);
} }
break; break;
case NodeKind::FRAGMENT_SPREAD: case $node instanceof FragmentSpreadNode:
/** @var FragmentSpreadNode $node */
$fragment = $this->getFragment($node); $fragment = $this->getFragment($node);
if ($fragment !== null) { if ($fragment !== null) {
@ -206,10 +203,12 @@ class QueryComplexity extends QuerySecurityRule
$directive = Directive::includeDirective(); $directive = Directive::includeDirective();
/** @var bool $directiveArgsIf */ /** @var bool $directiveArgsIf */
$directiveArgsIf = Values::getArgumentValues($directive, $directiveNode, $variableValues)['if']; $directiveArgsIf = Values::getArgumentValues($directive, $directiveNode, $variableValues)['if'];
return ! $directiveArgsIf; return ! $directiveArgsIf;
} }
$directive = Directive::skipDirective(); $directive = Directive::skipDirective();
$directiveArgsIf = Values::getArgumentValues($directive, $directiveNode, $variableValues); $directiveArgsIf = Values::getArgumentValues($directive, $directiveNode, $variableValues);
return $directiveArgsIf['if']; return $directiveArgsIf['if'];
} }
} }
@ -282,6 +281,6 @@ class QueryComplexity extends QuerySecurityRule
protected function isEnabled() protected function isEnabled()
{ {
return $this->getMaxQueryComplexity() !== static::DISABLED; return $this->getMaxQueryComplexity() !== self::DISABLED;
} }
} }

View file

@ -5,6 +5,9 @@ declare(strict_types=1);
namespace GraphQL\Validator\Rules; namespace GraphQL\Validator\Rules;
use GraphQL\Error\Error; use GraphQL\Error\Error;
use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\FragmentSpreadNode;
use GraphQL\Language\AST\InlineFragmentNode;
use GraphQL\Language\AST\Node; use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeKind; use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\OperationDefinitionNode; use GraphQL\Language\AST\OperationDefinitionNode;
@ -57,9 +60,8 @@ class QueryDepth extends QuerySecurityRule
private function nodeDepth(Node $node, $depth = 0, $maxDepth = 0) private function nodeDepth(Node $node, $depth = 0, $maxDepth = 0)
{ {
switch ($node->kind) { switch (true) {
case NodeKind::FIELD: case $node instanceof FieldNode:
/** @var FieldNode $node */
// node has children? // node has children?
if ($node->selectionSet !== null) { if ($node->selectionSet !== null) {
// update maxDepth if needed // update maxDepth if needed
@ -70,16 +72,14 @@ class QueryDepth extends QuerySecurityRule
} }
break; break;
case NodeKind::INLINE_FRAGMENT: case $node instanceof InlineFragmentNode:
/** @var InlineFragmentNode $node */
// node has children? // node has children?
if ($node->selectionSet !== null) { if ($node->selectionSet !== null) {
$maxDepth = $this->fieldDepth($node, $depth, $maxDepth); $maxDepth = $this->fieldDepth($node, $depth, $maxDepth);
} }
break; break;
case NodeKind::FRAGMENT_SPREAD: case $node instanceof FragmentSpreadNode:
/** @var FragmentSpreadNode $node */
$fragment = $this->getFragment($node); $fragment = $this->getFragment($node);
if ($fragment !== null) { if ($fragment !== null) {
@ -98,8 +98,6 @@ class QueryDepth extends QuerySecurityRule
/** /**
* Set max query depth. If equal to 0 no check is done. Must be greater or equal to 0. * Set max query depth. If equal to 0 no check is done. Must be greater or equal to 0.
*
* @param int $maxQueryDepth
*/ */
public function setMaxQueryDepth($maxQueryDepth) public function setMaxQueryDepth($maxQueryDepth)
{ {
@ -115,6 +113,6 @@ class QueryDepth extends QuerySecurityRule
protected function isEnabled() protected function isEnabled()
{ {
return $this->getMaxQueryDepth() !== static::DISABLED; return $this->getMaxQueryDepth() !== self::DISABLED;
} }
} }

Some files were not shown because too many files have changed in this diff Show more