Compare commits

...

184 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
0226b08429 v0.13.1 2019-03-09 22:34:29 +07:00
Simon Podlipsky
255ecbd709 Test against PHP 7.4
(cherry picked from commit dfefdf24cb)
2019-03-09 22:27:54 +07:00
Petr Skoda
8c66fa8d1e Standardise whitespace
(cherry picked from commit d20a6a9d56)
2019-03-09 22:27:40 +07:00
Petr Skoda
7405ddc852 Fix incorrect array type of rootValue in PHPDocs
(cherry picked from commit 77448ba623)
2019-03-09 22:27:30 +07:00
Simon Podlipsky
0cbc1c9c07 Support PHP 8
(cherry picked from commit bc637414e5)
2019-03-09 22:27:17 +07:00
Petr Skoda
c628fa39a1 Fix incorrect array type of contextValue in PHPDocs
(cherry picked from commit 7d59811c4f)
2019-03-09 22:27:03 +07:00
Stefano Torresi
a0f214a9f9 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.

(cherry picked from commit b1ab1820b6)
2019-03-09 22:26:44 +07:00
Jan Bukva
153f6f862e Fix return annotation of resolveType() in InterfaceType
(cherry picked from commit edb5268583)
2019-03-09 22:26:29 +07:00
Simon Podlipsky
610979555d Merge clovers
(cherry picked from commit fd7465521a)
2019-03-09 22:26:10 +07:00
Simon Podlipsky
ababa18157 Fix Deferred
(cherry picked from commit 828a9fb002)
2019-03-09 22:25:47 +07:00
Jan Bukva
f52dfcfaef Use key-value foreach
(cherry picked from commit 00490d289c)
2019-03-09 22:24:46 +07:00
Jan Bukva
8b8ea0d4a3 Fix CoroutineExecutor::resultToArray for associative arrays
(cherry picked from commit 2295b96a49)
2019-03-09 22:24:39 +07:00
Jan Bukva
42b20e7651 Failing test for CoroutineExecutor removes associative array when using custom scalar producing JSON
(cherry picked from commit 31d89acfae)
2019-03-09 22:24:17 +07:00
chriszarate
08992de960 Allow extensions to be provided in GET request.
(cherry picked from commit 244ec66ecc)
2019-03-09 22:20:14 +07:00
Torsten Blindert
1d8f526d91 TASK: Code style
(cherry picked from commit 9609d2ac84)
2019-03-09 22:18:54 +07:00
Torsten Blindert
fda73f3212 TASK: Added test
(cherry picked from commit d5fddfd504)
2019-03-09 22:18:45 +07:00
Torsten Blindert
22cee49747 BUGFIX: expect ->getType() to throw
(cherry picked from commit 62b0036437)
2019-03-09 22:18:35 +07:00
Erik Gaal
59c128c54a Add NullableType interface
(cherry picked from commit 9a0dbff26b)
2019-03-09 22:17:22 +07:00
chriszarate
a116127436 Add extensions to OperationParams instance.
(cherry picked from commit f644c1a837)
2019-03-09 22:16:45 +07:00
chriszarate
9ada606919 Fix linting issue and typos.
(cherry picked from commit c33e41f2bf)
2019-03-09 22:16:30 +07:00
chriszarate
1e778d259e Apollo server/client compatibility. Look for queryid in extensions.
(cherry picked from commit 63b4e3f0a4)
2019-03-09 22:15:55 +07:00
Simon Podlipsky
b005803bf6 Add PHP 7.3 to Travis
(cherry picked from commit f95d1e81ea)
2019-03-09 22:15:26 +07:00
Yury
21dc3fe664 Error handling improvements
(cherry picked from commit 33e3c9c338)
2019-03-09 22:15:11 +07:00
Simon Podlipsky
16d42dead3 Document BC and fix types in ResolveInfo
(cherry picked from commit 376e927505)
2019-03-09 22:14:35 +07:00
Simon Podlipsky
bf471838ae Added all possible scalar types to Node constructor
(cherry picked from commit ead1b864bc)
2019-03-09 22:14:24 +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
Vladimir Razuvaev
012082d1d9 Minor documentation fix 2018-11-27 17:53:03 +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
140 changed files with 2880 additions and 1039 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

@ -20,7 +20,7 @@ build:
tools: tools:
external_code_coverage: external_code_coverage:
timeout: 600 timeout: 900
build_failure_conditions: build_failure_conditions:
- 'elements.rating(<= C).new.exists' # No new classes/methods with a rating of C or worse allowed - 'elements.rating(<= C).new.exists' # No new classes/methods with a rating of C or worse allowed

View file

@ -4,12 +4,16 @@ language: php
php: php:
- 7.1 - 7.1
- 7.2 - 7.2
- 7.3
- 7.4snapshot
- nightly - nightly
env: env:
matrix: matrix:
- EXECUTOR=coroutine - EXECUTOR= DEPENDENCIES=--prefer-lowest
- EXECUTOR=coroutine DEPENDENCIES=--prefer-lowest
- EXECUTOR= - EXECUTOR=
- EXECUTOR=coroutine
cache: cache:
@ -26,13 +30,13 @@ script: ./vendor/bin/phpunit --group default,ReactPromise
jobs: jobs:
allow_failures: allow_failures:
- php: 7.4snapshot
- php: nightly - php: nightly
include: include:
- stage: Test - stage: Test
env: DEPENDENCIES=low
install: install:
- travis_retry composer update --prefer-dist --prefer-lowest - travis_retry composer update --prefer-dist {$DEPENDENCIES}
- stage: Test - stage: Test
env: COVERAGE env: COVERAGE
@ -40,10 +44,12 @@ jobs:
- mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{.disabled,} - mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{.disabled,}
- if [[ ! $(php -m | grep -si xdebug) ]]; then echo "xdebug required for coverage"; exit 1; fi - if [[ ! $(php -m | grep -si xdebug) ]]; then echo "xdebug required for coverage"; exit 1; fi
script: script:
- ./vendor/bin/phpunit --coverage-clover clover.xml - ./vendor/bin/phpunit --coverage-php /tmp/coverage/clover_executor.cov
- EXECUTOR=coroutine ./vendor/bin/phpunit --coverage-php /tmp/coverage/clover_executor-coroutine.cov
after_script: after_script:
- wget https://scrutinizer-ci.com/ocular.phar - ./vendor/bin/phpcov merge /tmp/coverage --clover /tmp/clover.xml
- php ocular.phar code-coverage:upload --format=php-clover clover.xml - wget https://github.com/scrutinizer-ci/ocular/releases/download/1.5.2/ocular.phar
- php ocular.phar code-coverage:upload --format=php-clover /tmp/clover.xml
- stage: Code Quality - stage: Code Quality
php: 7.1 php: 7.1
@ -57,3 +63,4 @@ jobs:
env: STATIC_ANALYSIS env: STATIC_ANALYSIS
install: travis_retry composer install --prefer-dist install: travis_retry composer install --prefer-dist
script: composer static-analysis script: composer static-analysis

View file

@ -1,4 +1,26 @@
# 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
- Better validation of field/directive arguments
- Support for apollo client/server persisted queries
- Minor tweaks and fixes
## v0.13.0 ## v0.13.0
This release brings several breaking changes. Please refer to [UPGRADE](UPGRADE.md) document for details. This release brings several breaking changes. Please refer to [UPGRADE](UPGRADE.md) document for details.

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
@ -78,6 +83,9 @@ Parser::parse($source, [ 'allowLegacySDLImplementsInterfaces' => true])
- `AbstractQuerySecurity` renamed to `QuerySecurityRule` (NS `GraphQL\Validator\Rules`) - `AbstractQuerySecurity` renamed to `QuerySecurityRule` (NS `GraphQL\Validator\Rules`)
- `FindBreakingChanges` renamed to `BreakingChangesFinder` (NS `GraphQL\Utils`) - `FindBreakingChanges` renamed to `BreakingChangesFinder` (NS `GraphQL\Utils`)
### Breaking: new constructors
`GraphQL\Type\Definition\ResolveInfo` now takes 10 arguments instead of one array.
## Upgrade v0.11.x > v0.12.x ## Upgrade v0.11.x > v0.12.x

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

@ -9,16 +9,17 @@
"API" "API"
], ],
"require": { "require": {
"php": "^7.1", "php": "^7.1||^8.0",
"ext-json": "*", "ext-json": "*",
"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/phpunit": "^7.2", "phpunit/phpunit": "^7.2",
"psr/http-message": "^1.0", "psr/http-message": "^1.0",
"react/promise": "2.*" "react/promise": "2.*"

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

@ -7,7 +7,7 @@
# About GraphQL # About GraphQL
GraphQL is a modern way to build HTTP APIs consumed by the web and mobile clients. GraphQL is a modern way to build HTTP APIs consumed by the web and mobile clients.
It is intended to be a replacement for REST and SOAP APIs (even for **existing applications**). It is intended to be an alternative to REST and SOAP APIs (even for **existing applications**).
GraphQL itself is a [specification](https://github.com/facebook/graphql) designed by Facebook GraphQL itself is a [specification](https://github.com/facebook/graphql) designed by Facebook
engineers. Various implementations of this specification were written engineers. Various implementations of this specification were written
@ -44,8 +44,8 @@ existing PHP frameworks, add support for Relay, etc.
## Current Status ## Current Status
The first version of this library (v0.1) was released on August 10th 2015. The first version of this library (v0.1) was released on August 10th 2015.
The current version (v0.10) supports all features described by GraphQL specification The current version supports all features described by GraphQL specification
(including April 2016 add-ons) as well as some experimental features like as well as some experimental features like
[Schema Language parser](type-system/type-language.md) and [Schema Language parser](type-system/type-language.md) and
[Schema printer](reference.md#graphqlutilsschemaprinter). [Schema printer](reference.md#graphqlutilsschemaprinter).

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

@ -11,7 +11,7 @@ use Throwable;
class Deferred class Deferred
{ {
/** @var SplQueue */ /** @var SplQueue|null */
private static $queue; private static $queue;
/** @var callable */ /** @var callable */
@ -20,21 +20,6 @@ class Deferred
/** @var SyncPromise */ /** @var SyncPromise */
public $promise; public $promise;
public static function getQueue()
{
return self::$queue ?: self::$queue = new SplQueue();
}
public static function runQueue()
{
$q = self::$queue;
while ($q && ! $q->isEmpty()) {
/** @var self $dfd */
$dfd = $q->dequeue();
$dfd->run();
}
}
public function __construct(callable $callback) public function __construct(callable $callback)
{ {
$this->callback = $callback; $this->callback = $callback;
@ -42,6 +27,25 @@ class Deferred
self::getQueue()->enqueue($this); self::getQueue()->enqueue($this);
} }
public static function getQueue() : SplQueue
{
if (self::$queue === null) {
self::$queue = new SplQueue();
}
return self::$queue;
}
public static function runQueue() : void
{
$queue = self::getQueue();
while (! $queue->isEmpty()) {
/** @var self $dequeuedNodeValue */
$dequeuedNodeValue = $queue->dequeue();
$dequeuedNodeValue->run();
}
}
public function then($onFulfilled = null, $onRejected = null) public function then($onFulfilled = null, $onRejected = null)
{ {
return $this->promise->then($onFulfilled, $onRejected); return $this->promise->then($onFulfilled, $onRejected);

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

@ -74,7 +74,7 @@ class Executor
* execution are collected in `$result->errors`. * execution are collected in `$result->errors`.
* *
* @param mixed|null $rootValue * @param mixed|null $rootValue
* @param mixed[]|null $contextValue * @param mixed|null $contextValue
* @param mixed[]|ArrayAccess|null $variableValues * @param mixed[]|ArrayAccess|null $variableValues
* @param string|null $operationName * @param string|null $operationName
* *
@ -119,8 +119,8 @@ class Executor
* *
* Useful for async PHP platforms. * Useful for async PHP platforms.
* *
* @param mixed[]|null $rootValue * @param mixed|null $rootValue
* @param mixed[]|null $contextValue * @param mixed|null $contextValue
* @param mixed[]|null $variableValues * @param mixed[]|null $variableValues
* @param string|null $operationName * @param string|null $operationName
* *
@ -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

@ -115,8 +115,8 @@ class ReferenceExecutor implements ExecutorImplementation
* Constructs an ExecutionContext object from the arguments passed to * Constructs an ExecutionContext object from the arguments passed to
* execute, which we will pass throughout the other execution methods. * execute, which we will pass throughout the other execution methods.
* *
* @param mixed[] $rootValue * @param mixed $rootValue
* @param mixed[] $contextValue * @param mixed $contextValue
* @param mixed[]|Traversable $rawVariableValues * @param mixed[]|Traversable $rawVariableValues
* @param string|null $operationName * @param string|null $operationName
* *
@ -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,14 +147,16 @@ class CoroutineExecutor implements Runtime, ExecutorImplementation
if ($emptyObjectAsStdClass && empty($array)) { if ($emptyObjectAsStdClass && empty($array)) {
return new stdClass(); return new stdClass();
} }
return $array; return $array;
} }
if (is_array($value)) { if (is_array($value)) {
$array = []; $array = [];
foreach ($value as $item) { foreach ($value as $key => $item) {
$array[] = 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

@ -40,7 +40,7 @@ abstract class Node
public $loc; public $loc;
/** /**
* @param (string|NameNode|NodeList|SelectionSetNode|Location|null)[] $vars * @param (NameNode|NodeList|SelectionSetNode|Location|string|int|bool|float|null)[] $vars
*/ */
public function __construct(array $vars) public function __construct(array $vars)
{ {
@ -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
@ -46,6 +47,12 @@ class OperationParams
*/ */
public $variables; public $variables;
/**
* @api
* @var mixed[]|null
*/
public $extensions;
/** @var mixed[] */ /** @var mixed[] */
private $originalInput; private $originalInput;
@ -56,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();
@ -76,24 +80,38 @@ class OperationParams
'id' => null, // alias to queryid 'id' => null, // alias to queryid
'operationname' => null, 'operationname' => null,
'variables' => null, 'variables' => null,
'extensions' => null,
]; ];
if ($params['variables'] === '') { if ($params['variables'] === '') {
$params['variables'] = null; $params['variables'] = null;
} }
if (is_string($params['variables'])) { // Some parameters could be provided as serialized JSON.
$tmp = json_decode($params['variables'], true); foreach (['extensions', 'variables'] as $param) {
if (! json_last_error()) { if (! is_string($params[$param])) {
$params['variables'] = $tmp; continue;
} }
$tmp = json_decode($params[$param], true);
if (json_last_error() !== JSON_ERROR_NONE) {
continue;
}
$params[$param] = $tmp;
} }
$instance->query = $params['query']; $instance->query = $params['query'];
$instance->queryId = $params['queryid'] ?: $params['documentid'] ?: $params['id']; $instance->queryId = $params['queryid'] ?: $params['documentid'] ?: $params['id'];
$instance->operation = $params['operationname']; $instance->operation = $params['operationname'];
$instance->variables = $params['variables']; $instance->variables = $params['variables'];
$instance->readOnly = (bool) $readonly; $instance->extensions = $params['extensions'];
$instance->readOnly = $readonly;
// Apollo server/client compatibility: look for the queryid in extensions
if (isset($instance->extensions['persistedQuery']['sha256Hash']) && empty($instance->query) && empty($instance->queryId)) {
$instance->queryId = $instance->extensions['persistedQuery']['sha256Hash'];
}
return $instance; return $instance;
} }

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,10 +19,7 @@ use function is_int;
use function is_string; use function is_string;
use function sprintf; use function sprintf;
/** class EnumType extends Type implements InputType, OutputType, LeafType, NullableType, NamedType
* Class EnumType
*/
class EnumType extends Type implements InputType, OutputType, LeafType, NamedType
{ {
/** @var EnumTypeDefinitionNode|null */ /** @var EnumTypeDefinitionNode|null */
public $astNode; public $astNode;
@ -33,7 +30,7 @@ class EnumType extends Type implements InputType, OutputType, LeafType, NamedTyp
/** @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, NamedTyp
} }
/** /**
* @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,10 +15,7 @@ use function is_callable;
use function is_string; use function is_string;
use function sprintf; use function sprintf;
/** class InputObjectType extends Type implements InputType, NullableType, NamedType
* Class InputObjectType
*/
class InputObjectType extends Type implements InputType, NamedType
{ {
/** @var InputObjectTypeDefinitionNode|null */ /** @var InputObjectTypeDefinitionNode|null */
public $astNode; public $astNode;
@ -72,7 +69,9 @@ class InputObjectType extends Type implements InputType, 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,10 +12,7 @@ use function is_callable;
use function is_string; use function is_string;
use function sprintf; use function sprintf;
/** class InterfaceType extends Type implements AbstractType, OutputType, CompositeType, NullableType, NamedType
* Class InterfaceType
*/
class InterfaceType extends Type implements AbstractType, OutputType, CompositeType, NamedType
{ {
/** @var InterfaceTypeDefinitionNode|null */ /** @var InterfaceTypeDefinitionNode|null */
public $astNode; public $astNode;
@ -107,7 +104,7 @@ class InterfaceType extends Type implements AbstractType, OutputType, CompositeT
* @param object $objectValue * @param object $objectValue
* @param mixed[] $context * @param mixed[] $context
* *
* @return callable|null * @return Type|null
*/ */
public function resolveType($objectValue, $context, ResolveInfo $info) public function resolveType($objectValue, $context, ResolveInfo $info)
{ {

View file

@ -4,10 +4,7 @@ declare(strict_types=1);
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
/** class ListOfType extends Type implements WrappingType, OutputType, NullableType, InputType
* Class ListOfType
*/
class ListOfType extends Type implements WrappingType, OutputType, InputType
{ {
/** @var ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType */ /** @var ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType */
public $ofType; public $ofType;
@ -20,15 +17,9 @@ class ListOfType extends Type implements WrappingType, OutputType, InputType
$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, 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

@ -4,22 +4,15 @@ declare(strict_types=1);
namespace GraphQL\Type\Definition; namespace GraphQL\Type\Definition;
use Exception;
use GraphQL\Error\InvariantViolation;
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 ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType */ /** @var NullableType */
private $ofType; private $ofType;
/** /**
* @param callable|Type $type * @param NullableType $type
*
* @throws Exception
*/ */
public function __construct($type) public function __construct($type)
{ {
@ -29,7 +22,7 @@ class NonNull extends Type implements WrappingType, OutputType, InputType
/** /**
* @param mixed $type * @param mixed $type
* *
* @return ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType * @return NullableType
*/ */
public static function assertNullableType($type) public static function assertNullableType($type)
{ {
@ -67,14 +60,14 @@ class NonNull extends Type implements WrappingType, OutputType, InputType
/** /**
* @param bool $recurse * @param bool $recurse
* *
* @return ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType * @return Type
*
* @throws InvariantViolation
*/ */
public function getWrappedType($recurse = false) public function getWrappedType($recurse = false)
{ {
$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

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace GraphQL\Type\Definition;
/*
export type GraphQLNullableType =
| GraphQLScalarType
| GraphQLObjectType
| GraphQLInterfaceType
| GraphQLUnionType
| GraphQLEnumType
| GraphQLInputObjectType
| GraphQLList<any>;
*/
interface NullableType
{
}

View file

@ -54,7 +54,7 @@ use function sprintf;
* } * }
* ]); * ]);
*/ */
class ObjectType extends Type implements OutputType, CompositeType, NamedType class ObjectType extends Type implements OutputType, CompositeType, NullableType, NamedType
{ {
/** @var ObjectTypeDefinitionNode|null */ /** @var ObjectTypeDefinitionNode|null */
public $astNode; public $astNode;
@ -168,7 +168,7 @@ class ObjectType extends Type implements OutputType, CompositeType, NamedType
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, NamedType
{ {
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, NamedType
} }
/** /**
* @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,15 +15,16 @@ 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|null * @var string
*/ */
public $fieldName; public $fieldName;
@ -31,12 +32,12 @@ class ResolveInfo
* AST of all nodes referencing this field in the query. * AST of all nodes referencing this field in the query.
* *
* @api * @api
* @var FieldNode[]|null * @var FieldNode[]
*/ */
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,47 +45,47 @@ class ResolveInfo
public $returnType; public $returnType;
/** /**
* Parent type of the field being resolved * Parent type of the field being resolved.
* *
* @api * @api
* @var ObjectType|null * @var ObjectType
*/ */
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[][]
*/ */
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[]|null * @var FragmentDefinitionNode[]
*/ */
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,24 +93,35 @@ class ResolveInfo
public $operation; public $operation;
/** /**
* Array of variables passed to query execution * Array of variables passed to query execution.
* *
* @api * @api
* @var mixed[]|null * @var mixed[]
*/ */
public $variableValues; public $variableValues = [];
/** @var QueryPlan */
private $queryPlan;
/**
* @param FieldNode[] $fieldNodes
* @param ScalarType|ObjectType|InterfaceType|UnionType|EnumType|ListOfType|NonNull $returnType
* @param string[][] $path
* @param FragmentDefinitionNode[] $fragments
* @param mixed|null $rootValue
* @param mixed[] $variableValues
*/
public function __construct( public function __construct(
string $fieldName, string $fieldName,
$fieldNodes, iterable $fieldNodes,
$returnType, $returnType,
ObjectType $parentType, ObjectType $parentType,
$path, array $path,
Schema $schema, Schema $schema,
$fragments, array $fragments,
$rootValue, $rootValue,
?OperationDefinitionNode $operation, ?OperationDefinitionNode $operation,
$variableValues array $variableValues
) { ) {
$this->fieldName = $fieldName; $this->fieldName = $fieldName;
$this->fieldNodes = $fieldNodes; $this->fieldNodes = $fieldNodes;
@ -125,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{
@ -166,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[]
*/ */
@ -199,6 +230,7 @@ class ResolveInfo
); );
} }
} }
return $fields; return $fields;
} }
} }

View file

@ -27,7 +27,7 @@ use function is_string;
* } * }
* } * }
*/ */
abstract class ScalarType extends Type implements OutputType, InputType, LeafType, NamedType abstract class ScalarType extends Type implements OutputType, InputType, LeafType, NullableType, NamedType
{ {
/** @var ScalarTypeDefinitionNode|null */ /** @var ScalarTypeDefinitionNode|null */
public $astNode; public $astNode;

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

@ -137,7 +137,7 @@ abstract class Type implements JsonSerializable
} }
/** /**
* @param ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType $wrappedType * @param NullableType $wrappedType
* *
* @return NonNull * @return NonNull
* *
@ -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();
} }
@ -338,13 +339,15 @@ abstract class Type implements JsonSerializable
/** /**
* @param Type $type * @param Type $type
* *
* @return ObjectType|InterfaceType|UnionType|ScalarType|InputObjectType|EnumType|ListOfType * @return NullableType
* *
* @api * @api
*/ */
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,10 +14,7 @@ use function is_callable;
use function is_string; use function is_string;
use function sprintf; use function sprintf;
/** class UnionType extends Type implements AbstractType, OutputType, CompositeType, NullableType, NamedType
* Class UnionType
*/
class UnionType extends Type implements AbstractType, OutputType, CompositeType, NamedType
{ {
/** @var UnionTypeDefinitionNode */ /** @var UnionTypeDefinitionNode */
public $astNode; public $astNode;

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;
} }
@ -513,7 +518,14 @@ class SchemaExtender
$schemaExtensions[] = $def; $schemaExtensions[] = $def;
} elseif ($def instanceof TypeDefinitionNode) { } elseif ($def instanceof TypeDefinitionNode) {
$typeName = isset($def->name) ? $def->name->value : null; $typeName = isset($def->name) ? $def->name->value : null;
if ($schema->getType($typeName)) {
try {
$type = $schema->getType($typeName);
} catch (Error $error) {
$type = null;
}
if ($type) {
throw new Error('Type "' . $typeName . '" already exists in the schema. It cannot also be defined in this type definition.', [$def]); throw new Error('Type "' . $typeName . '" already exists in the schema. It cannot also be defined in this type definition.', [$def]);
} }
$typeDefinitionMap[$typeName] = $def; $typeDefinitionMap[$typeName] = $def;
@ -597,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;
} }
); );

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