Compare commits

...

392 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
Vladimir Razuvaev
9bb8e73277 v0.13.0 2018-11-27 17:23:58 +07:00
Vladimir Razuvaev
627cc786a5 Fixed codestyle in benchmarks 2018-11-27 17:22:29 +07:00
Vladimir Razuvaev
c45fa1a4b1 Default Error Formatting: Display error category under extensions key (#389) 2018-11-27 16:37:33 +07:00
Vladimir Razuvaev
54263d50c0 Added a note about experimental executor in the changelog 2018-11-27 15:51:53 +07:00
Vladimir Razuvaev
e1b4d438db Ability to override internal types (closes #401) 2018-11-27 15:39:20 +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
Vladimir Razuvaev
bdbb30c604 Schema validation improvements 2018-11-22 22:23:14 +07:00
Vladimir Razuvaev
89fa0c3e67 More composer scripts + related changes to CONTRIBUTING.md 2018-11-22 19:57:53 +07:00
Vladimir Razuvaev
9cb6b4e6f3 Additional tests for errors with extensions 2018-11-22 19:47:10 +07:00
Vladimir Razuvaev
d8d032f0f6 Merge branch 'markhuot-patch-1' 2018-11-22 18:44:22 +07:00
Vladimir Razuvaev
c0ae3ccdaf Fix possessive its vs contraction it's (resolving conflicts) 2018-11-22 18:43:35 +07:00
Vladimir Razuvaev
7c19777dff Ensure interface has at least 1 concrete type 2018-11-21 20:11:11 +07:00
Vladimir Razuvaev
779774b162 ReferenceExecutor: Remove excessive arguments 2018-11-21 18:54:11 +07:00
Vladimir Razuvaev
b4be42acdf Fix printing of ASTs of argument definitions with descriptions 2018-11-21 18:53:44 +07:00
Vladimir Razuvaev
25cfebbd37 Lexer: unify unexpected character errors 2018-11-21 18:09:57 +07:00
Vladimir Razuvaev
0f3ebaa20b Note about new executor in UPGRADE.md 2018-11-21 17:53:12 +07:00
Vladimir Razuvaev
21e0c830a6
Merge pull request #314 from jakubkulhan/compiler
Experimental executor with improved performance
2018-11-21 14:19:21 +07:00
Mark Huot
4811cd198f
Fixing test for possessive/contraction "its" change 2018-11-19 09:13:57 -05:00
Mark Huot
7249e2611a
Fix possessive its vs contraction it's 2018-11-11 07:59:45 -05:00
Jakub Kulhan
b5d3341995 Pluggable executor implementations; new faster executor using coroutines 2018-11-06 23:32:50 +01:00
Vladimir Razuvaev
98807286f7
Merge pull request #386 from nagledb/fix-definition-spelling
Fixed misspelling "defintion" in several places
2018-11-05 03:19:01 +07:00
David B. Nagle
f39f0f5517 Fixed misspelling "defintion" in several places 2018-11-03 15:35:18 -07:00
Vladimir Razuvaev
23169603fc Removed old experimental code 2018-11-03 23:25:23 +07:00
Vladimir Razuvaev
6f8aed800e
Merge pull request #369 from nagledb/nagledb-doc-default-field-resolver
Updated default field resolver code in documentation.
2018-11-03 16:13:57 +07:00
Vladimir Razuvaev
1a1bf17c6a Added test case for extended schema resolvers 2018-11-03 16:01:41 +07:00
Vladimir Razuvaev
cd38568aaa
Merge pull request #381 from jbtbnl/master
Fix resolveFn key for schema extensions
2018-11-03 15:56:57 +07:00
Vladimir Razuvaev
9ee62b014e
Merge pull request #384 from nagledb/restore-resolver-docs
Restore "Defining resolvers" documentation
2018-11-03 15:48:53 +07:00
David B. Nagle
4a096b14ec Restore "Defining resolvers" documentation
Content was inadvertently removed in d6add77540
2018-11-02 21:10:54 -07:00
jbtbnl
427ac1b5d5
Fix resolve Fn key 2018-10-26 12:09:14 +02:00
Vladimir Razuvaev
2bddfe2225
Merge pull request #375 from simPod/fix-build
Fixing build
2018-10-25 13:29:41 +02:00
Simon Podlipsky
0d954b6ecc Constrain PHPStan versions 2018-10-22 23:37:28 +02:00
Simon Podlipsky
644f97634b Little fixes 2018-10-22 23:37:28 +02:00
Vladimir Razuvaev
6cce6742eb
Merge pull request #367 from simPod/phpstan-strict
Use PHPStan strict rules
2018-10-22 17:28:59 +02:00
David B. Nagle
e32bbb726d Updated default field resolver code in documentation.
Updated code to match definition in GraphQL\Executor\Executor::defaultFieldResolver. Change is notable because previous version did not indicate that the closure receives the $info variable.
2018-10-10 20:05:21 -07:00
Vladimir Razuvaev
5fb970b3f1 Codestyle fix 2018-10-10 11:41:24 +02:00
Vladimir Razuvaev
0ea7ffa601 Node in UPGRADE.md about broken AST caches 2018-10-10 11:33:53 +02:00
Vladimir Razuvaev
4bc9dfc6f8 Reverted unneeded change in the defaultFieldResolver 2018-10-10 11:29:14 +02:00
Vladimir Razuvaev
c7fcd4eb48 Got rid of PHP_EOL usages (we never do or expect carriage returns) 2018-10-10 11:26:48 +02:00
Simon Podlipsky
90d0156291
Use PHPStan strict rules
Two rules excluded: missing type hints and empty() usage
2018-10-09 17:46:55 +02:00
Vladimir Razuvaev
0070cb4039
Merge pull request #366 from enumag/patch-1
Fix annotation
2018-10-09 16:18:36 +02:00
Jáchym Toušek
e4c743cf8c
Fix annotation 2018-10-09 14:46:41 +02:00
Torsten Blindert
e7de069bd5 FEATURE: SchemaExtender (#362)
FEATURE: SchemaExtender (#362)
2018-10-08 15:55:20 +02:00
Gilles Maes
7ff3e9399f Expand is_array check in defaultTypeResolver to allow for ArrayAccess… (#361)
Expand is_array check in defaultTypeResolver to allow for ArrayAccess objects as well
2018-10-08 15:53:07 +02:00
Vladimir Razuvaev
1417a43697
Merge pull request #363 from simPod/bump-cs
Bump cs
2018-10-05 12:04:42 +02:00
Simon Podlipsky
dd4a5076f6
Fix CS in src/Validator 2018-10-05 10:47:58 +02:00
Simon Podlipsky
e664c4455e
Fix CS in src/Utils 2018-10-05 10:47:58 +02:00
Simon Podlipsky
18a5639cb7
Fix CS in src/Type 2018-10-05 10:47:57 +02:00
Simon Podlipsky
7ba98ce773
Fix CS in src/Server 2018-10-05 10:47:57 +02:00
Simon Podlipsky
a95d2ad140
Fix CS in src/Language 2018-10-05 10:47:57 +02:00
Simon Podlipsky
bfebcd7bee
Fix CS in src/Executor 2018-10-05 10:47:57 +02:00
Simon Podlipsky
0063bd6c15
Fix CS in src/Error 2018-10-05 10:47:57 +02:00
Simon Podlipsky
73e75b6314
Fix CS in src 2018-10-05 10:47:57 +02:00
Simon Podlipsky
07c070d795
Fix CS in tests 2018-10-05 10:47:57 +02:00
Simon Podlipsky
af31ca7ad8
Bump Doctrine CS to v5 2018-10-05 10:24:10 +02:00
Vladimir Razuvaev
849c15dbf8
Merge pull request #354 from simPod/self
Use self:: in tests where appropriate
2018-10-02 15:12:05 +02:00
Vladimir Razuvaev
84f13650a2
Merge pull request #358 from simPod/php-bench
PHPBench
2018-10-02 15:11:50 +02:00
Vladimir Razuvaev
aed406eade
Merge pull request #360 from bytorsten/expand-extension
Add missing extensionASTNodes
2018-10-02 15:08:26 +02:00
Torsten Blindert
5f5c7d89be TASK: Code style #4 2018-10-02 09:58:44 +02:00
Torsten Blindert
5a1caf0549 TASK: Code style #2 2018-10-02 09:34:29 +02:00
Torsten Blindert
a3d050ff6b TASK: Code style 2018-10-02 09:09:12 +02:00
Torsten Blindert
0262f59a3f BUGFIX: Directives args have to be FieldArguments, not FieldDefinitions 2018-10-01 20:28:05 +02:00
Torsten Blindert
b65aa4e657 TASK: Change KnownDirectives rule to match reference
https://github.com/graphql/graphql-js/blob/master/src/validation/rules/KnownDirectives.js
2018-10-01 19:48:56 +02:00
Torsten Blindert
b1cd086177 TASK: Allow Schema extensions 2018-10-01 19:46:36 +02:00
Torsten Blindert
0c33cfa88f TASK: Allow UnionType extensions 2018-10-01 19:45:48 +02:00
Torsten Blindert
1dc2b939cb TASK: Allow ScalarType extensions 2018-10-01 19:45:36 +02:00
Torsten Blindert
b0ee36feb4 TASK: Allow InputObjectType extensions 2018-10-01 19:45:20 +02:00
Torsten Blindert
1d15ae7f3e TASK: Allow EnumType extensions 2018-10-01 19:44:55 +02:00
Simon Podlipsky
68bfde953d
PHPBench 2018-09-26 11:18:14 +02:00
Simon Podlipsky
c4e06ba528
Use self:: in tests where appropriate 2018-09-19 20:50:32 +02:00
Vladimir Razuvaev
4d4282b60f
Merge pull request #351 from simPod/fix-typo
Fix typo and cleanup
2018-09-10 14:21:33 +07:00
Simon Podlipsky
450a1932e5
Fix typo and cleanup 2018-09-09 17:32:53 +02:00
Vladimir Razuvaev
6bdead3fe3
Merge pull request #337 from spawnia/patch-1
Add Debug::RETHROW_UNSAFE_EXCEPTIONS flag
2018-09-06 12:09:05 +07:00
Vladimir Razuvaev
85f4c774a6
Merge pull request #349 from simPod/improve-stan-config
Improve PHPStan Configuration
2018-09-06 12:08:06 +07:00
Simon Podlipsky
e2e6d70ea8
Improve PHPStan Configuration 2018-09-05 00:09:22 +02:00
spawnia
0ddb7519bb More coding style -.-
Can this be done automatically?
2018-09-03 22:46:14 +02:00
spawnia
1ec4927f69 Add namespace to class 2018-09-03 22:13:05 +02:00
spawnia
c4fe304efe Fix coding style 2018-09-03 21:43:02 +02:00
Benedikt Franke
e7513e356a
Merge branch 'master' into patch-1 2018-09-03 21:25:36 +02:00
spawnia
326e0b4719 Add Debug::RETHROW_UNSAFE_EXCEPTIONS flag
This allows for a more granular default for which Exceptions are thrown, taking
the settings made through the ClientAware interface into account.

- Add docs
- Add a test case
- Differentiate clearly from other test
2018-09-03 21:18:04 +02:00
Vladimir Razuvaev
86503e2e35
Merge pull request #333 from mcg-web/static_analysis
Static analysis
2018-09-02 23:53:29 +07:00
Jeremiah VALERIE
a400f27dce
Ignore phpcs cache file 2018-09-02 18:26:35 +02:00
Jeremiah VALERIE
48c6f56640
Use php 7.1 build instead of 7.2 2018-09-02 18:26:35 +02:00
Jeremiah VALERIE
eed9cc7f1b
Give some love to PHP CS 2018-09-02 18:26:35 +02:00
Jeremiah VALERIE
2f63387864
Add CODING_STANDARD env var 2018-09-02 17:39:09 +02:00
Jeremiah VALERIE
be12d6f273
Sort gitignore alphabetic 2018-09-02 17:20:38 +02:00
Jeremiah VALERIE
21a7611820
fix script path 2018-09-02 17:20:38 +02:00
Jeremiah VALERIE
ff0733d013
Use .dist extension 2018-09-02 17:20:38 +02:00
Jeremiah VALERIE
7428cb8a31
Fix namespaces 2018-09-02 17:20:38 +02:00
Jeremiah VALERIE
62de403f27
Use right assert method 2018-09-02 17:20:38 +02:00
Jeremiah VALERIE
eb7ff7048d
Removed unused use for anonymous functions 2018-09-02 17:20:38 +02:00
Jeremiah VALERIE
b5b27c95b1
Also check tests folder 2018-09-02 17:20:38 +02:00
Jeremiah VALERIE
6a4c815b6d
Cleanup composer script 2018-09-02 17:19:54 +02:00
Jeremiah VALERIE
da70134c38
Fix travis globale install 2018-09-02 17:19:54 +02:00
Jeremiah VALERIE
6866779d26
Remove bin custom path 2018-09-02 17:19:53 +02:00
Jeremiah VALERIE
18954ea655
Fix typehint 2018-09-02 17:19:16 +02:00
Jeremiah VALERIE
05dbc0fb96
Fix sprintf call 2018-09-02 17:19:16 +02:00
Jeremiah VALERIE
31601d710b
Add default config file 2018-09-02 17:19:16 +02:00
Jeremiah VALERIE
d8f41e854f
Add static analysis tool 2018-09-02 17:19:15 +02:00
Vladimir Razuvaev
6c40fec35a
Merge pull request #341 from adactio/patch-1
Remove the word "simply"
2018-09-02 22:05:51 +07:00
Vladimir Razuvaev
c7d8cf4ea2
Merge pull request #348 from simPod/check-cs
Check CS in the whole project
2018-09-02 22:05:12 +07:00
Simon Podlipsky
7dbd72cebf
Check CS in the whole project
Closes #284
2018-09-02 16:46:58 +02:00
Vladimir Razuvaev
a4edb34deb
Merge pull request #343 from simPod/update-contibuting
Add it() explanation to Contribution guidelines
2018-09-02 21:36:59 +07:00
Vladimir Razuvaev
bfff27ef34
Merge pull request #347 from simPod/fix-cs-test
Fix CS in tests
2018-09-02 21:30:04 +07:00
Vladimir Razuvaev
ec43a2e01a
Merge pull request #346 from simPod/fix-cs-tests-validator
Fix CS in tests/Validator
2018-09-02 21:29:33 +07:00
Vladimir Razuvaev
d70c8e87e5
Merge pull request #345 from simPod/fix-cs-test-utils
Fix CS in tests/Utils
2018-09-02 21:27:39 +07:00
Vladimir Razuvaev
4e43a2cbcd
Merge pull request #344 from simPod/fix-cs-test-type
Fix CS in tests/Type
2018-09-02 21:26:11 +07:00
Vladimir Razuvaev
ce0272b447
Merge pull request #340 from simPod/cs-language-server
Fix CS in tests/Server
2018-09-02 21:23:47 +07:00
Vladimir Razuvaev
a7af4663b8
Merge pull request #339 from simPod/cs-language-test
Fix CS in test/Language
2018-09-02 21:22:39 +07:00
Vladimir Razuvaev
7f99bf478f
Merge pull request #338 from simPod/cs-executor-test
Fix CS in tests/Executor
2018-09-02 21:20:54 +07:00
Vladimir Razuvaev
9327e75a16
Merge pull request #342 from simPod/cs-src
Fix CS in src
2018-09-02 21:17:07 +07:00
Simon Podlipsky
d398e59ced
Fix CS in tests 2018-09-02 13:39:30 +02:00
Simon Podlipsky
737da333fb
Fix CS in tests/Utils 2018-09-02 13:10:47 +02:00
Simon Podlipsky
caa50d6db9
Fix CS in tests/Validator 2018-09-02 13:08:49 +02:00
Simon Podlipsky
b886742968
Fix CS in tests/Type 2018-09-02 12:55:40 +02:00
Simon Podlipsky
046bd02d6c
Add it() explanation to Contribution guidelines 2018-09-02 12:07:37 +02:00
Simon Podlipsky
76e1c33b68
Fix CS in src 2018-09-02 10:39:03 +02:00
Simon Podlipsky
bd8722652a
Fix CS in tests/Server 2018-09-02 10:18:45 +02:00
Jeremy Keith
33cb80335e
Remove the word "simply" 2018-09-01 22:32:02 +01:00
Simon Podlipsky
d1f49bedbd
Fix CS in test/Language 2018-09-01 22:16:02 +02:00
Simon Podlipsky
b02d25e62c
Fix CS in tests/Executor 2018-09-01 20:05:38 +02:00
Benedikt Franke
503ac4619a
Check if an exception is internal before rethrowing 2018-08-31 17:45:41 +02:00
Vladimir Razuvaev
ec54d6152b
Merge pull request #335 from simPod/test-void
Add void return typehint to test methods
2018-08-31 19:57:04 +07:00
Simon Podlipsky
715146cdd1
Add void return typehint to test methods 2018-08-31 14:41:18 +02:00
Vladimir Razuvaev
573a77ce0c
Merge pull request #334 from simPod/replace-it
Replace misleading @it by @see annotation
2018-08-31 18:51:00 +07:00
Simon Podlipsky
a30b3104a0
Replace misleading @it by @see annotation 2018-08-31 10:55:14 +02:00
Vladimir Razuvaev
cf4cefc2bc
Merge pull request #332 from simPod/cs-tests-error
Fix CS in tests/Error
2018-08-28 17:24:55 +07:00
Vladimir Razuvaev
ef93557a5d
Merge pull request #331 from simPod/cs-type
Fix CS in src/Type
2018-08-28 17:23:42 +07:00
Simon Podlipsky
3170951620
Fix CS in /tests/Error 2018-08-28 11:09:46 +02:00
Simon Podlipsky
00d547dc06
Fix CS in src/Type 2018-08-27 18:14:07 +02:00
Vladimir Razuvaev
8817e8e010 Throws descriptive error when non-type used instead of interface 2018-08-22 18:53:25 +07:00
Vladimir Razuvaev
ea6a21a13b Cleanup variables test 2018-08-22 18:40:15 +07:00
Vladimir Razuvaev
672ff0b7d6 Add nested nullable test in VariablesInAllowedPosition 2018-08-22 17:55:42 +07:00
Vladimir Razuvaev
804daa188e Add more BuildSchema tests + cleanup 2018-08-22 16:14:47 +07:00
Vladimir Razuvaev
f123e5c954 Remove redundancy in schema printer tests 2018-08-22 15:41:26 +07:00
Vladimir Razuvaev
227f0b867d Serial execution should support sync execution (+tests for the serial execution) 2018-08-22 15:10:10 +07:00
Vladimir Razuvaev
d87c1aba5c Additional printer test 2018-08-22 12:28:13 +07:00
Vladimir Razuvaev
6e7cf27579 Fixed composer lint command 2018-08-22 12:07:40 +07:00
Vladimir Razuvaev
90623f68d7 Fixed code style after recent changes 2018-08-22 12:07:21 +07:00
Vladimir Razuvaev
00caed811b Fixed SchemaPrinter (broken after the recent merge) 2018-08-22 11:45:54 +07:00
Vladimir Razuvaev
9ae8b9f26e Merge branch 'master' of https://github.com/webonyx/graphql-php
# Conflicts:
#	src/Utils/AST.php
2018-08-22 11:43:30 +07:00
Vladimir Razuvaev
7d326c44d8
Merge pull request #328 from simPod/utils-cs
Fix CS in Utils
2018-08-22 11:33:55 +07:00
Simon Podlipsky
49f34d3243
Fix CS in Utils 2018-08-22 02:51:55 +02:00
Vladimir Razuvaev
d44ec9e809 Merge branch 'master' of https://github.com/webonyx/graphql-php
# Conflicts:
#	src/Utils/AST.php
2018-08-21 22:10:50 +07:00
Vladimir Razuvaev
a3ef1be1ab
Merge pull request #323 from simPod/language-cs
Fix CS in src/Language
2018-08-21 22:00:01 +07:00
Simon Podlipsky
ad8693cb8a
Fix CS in src/Language 2018-08-20 20:26:21 +02:00
Vladimir Razuvaev
dbeaf46631
Merge pull request #324 from bkonetzny/docs-scalar-method-signature
Fixed method signature in custom scalar docs
2018-08-20 17:40:06 +07:00
Vladimir Razuvaev
0caaa1fa3b
Merge pull request #325 from Smolevich/fixes-in-benchmarks
Remove use instance of deprecated class GraphQL\Schema
2018-08-20 17:39:44 +07:00
Vladimir Razuvaev
23fce6385f
Merge pull request #319 from jrots/master
Fix for fatal php error: Call to a member function getLocation() on null, before normal error handling
2018-08-20 16:27:10 +07:00
Stanislav Shupilkin
189d474633 Remove use instance of deprecated class GraphQL\Schema 2018-08-17 19:19:33 +03:00
Bastian Konetzny
86fa8b1301
Fixed method signature in custom scalar docs 2018-08-16 09:43:15 +02:00
Vladimir Razuvaev
cc39b3ecbf
Merge pull request #321 from simPod/validator-cs
Fix CS in Validator folder
2018-08-12 01:08:05 +07:00
Vladimir Razuvaev
64c463e889 Schema: getTypeMap() should include input types defined in directive arguments 2018-08-08 15:47:42 +07:00
Simon Podlipsky
4c327a6c16
Fix CS in Validator folder 2018-08-08 10:44:05 +02:00
Vladimir Razuvaev
0d93d190f8 Updated introspection test 2018-08-08 15:11:33 +07:00
Vladimir Razuvaev
56e91d008e Update printSchema for recent SDL change to implements 2018-08-08 15:07:08 +07:00
Vladimir Razuvaev
fcb9c24bb5 Fix astFromValue to correctly handle integers and strings 2018-08-08 14:57:44 +07:00
Vladimir Razuvaev
49ec89b28f Fixed broken description printing 2018-08-08 01:46:32 +07:00
Vladimir Razuvaev
03d7d1851c NonNull test: fixed naming for consistency 2018-08-08 01:43:23 +07:00
Vladimir Razuvaev
d9aee43129 Printer: created special function to add descriptions 2018-08-08 01:28:34 +07:00
Vladimir Razuvaev
ccb9486d21 SchemaParser: additional test case 2018-08-08 01:18:59 +07:00
Vladimir Razuvaev
a19fc3d208 RFC: SDL - Separate multiple inherited interfaces with & 2018-08-08 01:11:47 +07:00
Vladimir Razuvaev
8e02fdc537 Parser: allowLegacySDLEmptyFields option + minor naming tweaks 2018-08-08 00:41:20 +07:00
Vladimir Razuvaev
ea13c9edbf Updated PHPUnit to the latest version 2018-08-08 00:13:21 +07:00
Vladimir Razuvaev
c3d69c7c2b Fixed tests for PHPUnit 7+ 2018-08-07 23:59:48 +07:00
Vladimir Razuvaev
4227404aee Added test for useful error when returning invalid value from resolveType 2018-08-07 23:33:20 +07:00
Vladimir Razuvaev
f4008f0fb2 Error formatting: display error extensions under extensions key 2018-08-07 23:33:20 +07:00
Vladimir Razuvaev
39df711eac More definition tests (for type validation) 2018-08-07 23:32:26 +07:00
Jayme Rotsaert
942d4995c5 Fix: fatal Call to a member function getLocation(), before normal error is thrown 2018-08-01 18:16:48 +02:00
Vladimir Razuvaev
392b567f23
Merge pull request #317 from simPod/upgrade-phpunit
Upgrade PHPUnit
2018-07-31 01:15:46 +07:00
Vladimir Razuvaev
48b44fbdc5
Merge pull request #318 from robbieaverill/patch-1
FIX Ensure that __toString return value is always casted as a string
2018-07-30 13:29:34 +07:00
Robbie Averill
c962afc566
FIX Ensure that __toString return value is always casted as a string
`json_encode` can return false on failure, which causes PHP errors since `__toString` must return a string.
2018-07-30 16:36:09 +12:00
Vladimir Razuvaev
2f2b54a3d6 Perf: memoize collectSubfields 2018-07-30 00:36:10 +07:00
Simon Podlipsky
24b6b736b2
Upgrade PHPUnit 2018-07-29 19:01:39 +02:00
Vladimir Razuvaev
bedbd32fb7
Merge pull request #311 from Smolevich/update-complementary
Add link to GraphQL IDE in complementary-tools.md
2018-07-20 00:29:06 +07:00
Stanislav Shupilkin
34500bd8b2 Add link to GraphQL IDE in complementary-tools.md 2018-07-19 19:24:55 +03:00
Vladimir Razuvaev
0a6eaa1173
Merge pull request #306 from simPod/cs-server
Fixed code style for the /Server
2018-07-12 21:03:10 +07:00
Vladimir Razuvaev
c02c218c71
Merge pull request #304 from simPod/update-contibuting
Add CS info into Contributing docs
2018-07-12 20:56:34 +07:00
Simon Podlipsky
1b42de0658
@return self 2018-07-10 01:34:35 +03:00
Simon Podlipsky
ec2ff0d4bf
CS /Server 2018-07-10 01:34:35 +03:00
Simon Podlipsky
54456b1160
Add CS info into Contributing docs 2018-07-10 00:51:08 +03:00
Théo FIDRY
c1a62fdb05 Allow stringeable objects to be serialized by StringType
Closes #302

(cherry picked from commit c258109)
2018-07-08 04:44:10 +07:00
Vladimir Razuvaev
e515964a73
Merge pull request #295 from simPod/refactoring
Executor: fixed code style / minor refactoring
2018-07-07 21:58:54 +07:00
Vladimir Razuvaev
15672ab66c
Merge pull request #296 from danez/patch-2
fix: Correct namespace and link for DirectiveLocation in docs
2018-07-07 21:46:38 +07:00
Vladimir Razuvaev
a59b2803be
Merge pull request #298 from vasily-kartashov/master
Update complementary-tools.md
2018-07-07 21:46:04 +07:00
Vladimir Razuvaev
dbafdf849e
Merge pull request #300 from MySchoolManagement/support-multipart-formdata
Adds support for the multipart/form-data content type
2018-07-07 21:45:21 +07:00
Iain Mckay
750ce383ec Adds support for the multipart/form-data content type 2018-07-05 08:52:29 +02:00
Vasily Kartashov
ec77f439fb
Update complementary-tools.md
Adding reference to graphql-batch-processing library
2018-06-29 13:56:14 +04:00
Daniel Tschinder
1b6fb4c29c
fix: Correct namespace and link for DirectiveLocation in docs 2018-06-28 11:58:51 +02:00
Simon Podlipsky
cd1cc911e7
Refactoring 2018-06-26 14:37:19 +02:00
Vladimir Razuvaev
ddc3a01f09
Merge pull request #294 from danez/fix-block-printing
Use multi-line block for trailing quote
2018-06-25 23:45:29 +07:00
Daniel Tschinder
6e64983f82 Use multi-line block for trailing quote
ref: fdc10bb918 (diff-ebaed8492e8d884ee4f2255e39909568)
2018-06-25 14:26:53 +02:00
Vladimir Razuvaev
a032367e26 Disabled code style check of the whole project in Travis (for now) 2018-06-23 13:50:25 +07:00
Vladimir Razuvaev
5a90e9bd64
Merge pull request #288 from simPod/phpcs
RFC: PHP CS
2018-06-23 12:32:26 +07:00
Vladimir Razuvaev
89369fd8ec
Merge pull request #291 from danez/patch-1
Add one more breaking change in 0.12
2018-06-23 11:19:13 +07:00
Vladimir Razuvaev
6195029215
Merge pull request #292 from danez/patch-2
Fix wrong length being used in validator.
2018-06-23 11:18:07 +07:00
Daniel Tschinder
8ba146071d
Fix wrong length being used in validator. 2018-06-22 16:56:19 +02:00
Daniel Tschinder
3a4f520da7
Improve example 2018-06-22 15:46:42 +02:00
Daniel Tschinder
300b58093b
Add one more breaking change in 0.12 2018-06-22 15:45:21 +02:00
Simon Podlipsky
f44ff2cfe7
Add composer script for linting 2018-06-21 10:18:34 +02:00
Simon Podlipsky
4bc3b7885c
Fix CS in Error folder 2018-06-19 19:50:12 +02:00
Simon Podlipsky
fe5c3bdee5
Introduce PHPCS 2018-06-19 19:50:12 +02:00
Vladimir Razuvaev
e31947a452
Removed scrutinizer badge 2018-06-19 19:21:15 +07:00
Vladimir Razuvaev
f0c2f12222
Merge pull request #286 from simPod/cleanup-travis
Cleanup CI
2018-06-19 17:55:29 +07:00
Simon Podlipsky
4e9ad1fd75
Cleanup CI 2018-06-16 00:20:54 +02:00
Vladimir Razuvaev
82b1dbd836
Merge pull request #282 from han8gui/patch-1
Docs: fixed invalid port number in getting started guide
2018-05-27 20:04:42 +07:00
Vladimir Razuvaev
9452655fcd
Merge pull request #281 from icerockdev/master
Removing data elements from response if the error throwing
2018-05-27 20:02:14 +07:00
Ben Zhu
72e8607e47
fix port bug 2018-05-09 11:18:43 +08:00
Ilya Shaydullin
06490cae8b Fix is null condition 2018-05-07 17:12:21 +07:00
Ilya Shaydullin
c7f114d90b Removing data elements from response if the error throwing 2018-05-07 17:01:18 +07:00
380 changed files with 42521 additions and 28359 deletions

1
.gitattributes vendored
View file

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

8
.gitignore vendored
View file

@ -1,5 +1,7 @@
.idea/
composer.phar
.phpcs-cache
composer.lock
composer.phar
phpcs.xml
phpstan.neon
vendor/
bin/
/.idea

29
.scrutinizer.yml Normal file
View file

@ -0,0 +1,29 @@
build:
nodes:
analysis:
environment:
php:
version: 7.1
cache:
disabled: false
directories:
- ~/.composer/cache
project_setup:
override: true
tests:
override:
- php-scrutinizer-run
dependencies:
override:
- composer install --ignore-platform-reqs --no-interaction
tools:
external_code_coverage:
timeout: 900
build_failure_conditions:
- 'elements.rating(<= C).new.exists' # No new classes/methods with a rating of C or worse allowed
- 'issues.label("coding-style").new.exists' # No new coding style issues allowed
- 'issues.severity(>= MAJOR).new.exists' # New issues of major or higher severity
- 'project.metric_change("scrutinizer.test_coverage", < 0)' # Code Coverage decreased from previous inspection

View file

@ -1,33 +1,66 @@
dist: trusty
language: php
dist: trusty
php:
- 5.6
- 7.0
- 7.1
- 7.2
- nightly
- 7.1
- 7.2
- 7.3
- 7.4snapshot
- nightly
env:
matrix:
- EXECUTOR= DEPENDENCIES=--prefer-lowest
- EXECUTOR=coroutine DEPENDENCIES=--prefer-lowest
- EXECUTOR=
- EXECUTOR=coroutine
matrix:
allow_failures:
- php: nightly
cache:
directories:
- $HOME/.composer/cache
directories:
- $HOME/.composer/cache
before_install:
- if [[ "$TRAVIS_PHP_VERSION" != "5.6" ]]; then phpenv config-rm xdebug.ini || true; fi
- phpenv config-rm xdebug.ini || true
- composer selfupdate
- mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{,.disabled} || echo "xdebug not available"
- travis_retry composer self-update
install:
- composer install --dev --prefer-dist
- composer require react/promise:2.*
- composer require psr/http-message:1.*
install: travis_retry composer update --prefer-dist
script: if [ "$TRAVIS_PHP_VERSION" == "5.6" ]; then bin/phpunit --coverage-clover build/logs/clover.xml --group default,ReactPromise; else bin/phpunit --group default,ReactPromise; fi
script: ./vendor/bin/phpunit --group default,ReactPromise
jobs:
allow_failures:
- php: 7.4snapshot
- php: nightly
include:
- stage: Test
install:
- travis_retry composer update --prefer-dist {$DEPENDENCIES}
- stage: Test
env: COVERAGE
before_script:
- 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
script:
- ./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:
- ./vendor/bin/phpcov merge /tmp/coverage --clover /tmp/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
php: 7.1
env: CODING_STANDARD
install: travis_retry composer install --prefer-dist
script:
- ./vendor/bin/phpcs
- stage: Code Quality
php: 7.1
env: STATIC_ANALYSIS
install: travis_retry composer install --prefer-dist
script: composer static-analysis
after_success:
- if [ "$TRAVIS_PHP_VERSION" == "5.6" ]; then composer require "satooshi/php-coveralls:^1.0" && travis_retry php bin/coveralls -v; fi

View file

@ -1,4 +1,66 @@
# 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
This release brings several breaking changes. Please refer to [UPGRADE](UPGRADE.md) document for details.
New features and notable changes:
- PHP version required: 7.1+
- Spec compliance: error `category` and extensions are displayed under `extensions` key when using default formatting (#389)
- New experimental executor with improved performance (#314).<br>
It is a one-line switch: `GraphQL::useExperimentalExecutor()`.<br>
<br>
**Please try it and post your feedback at https://github.com/webonyx/graphql-php/issues/397**
(as it may become the default one in future)
<br>
<br>
- Ported `extendSchema` from the reference implementation under `GraphQL\Utils\SchemaExtender` (#362)
- Added ability to override standard types via `GraphQL::overrideStandardTypes(array $types)` (#401)
- Added flag `Debug::RETHROW_UNSAFE_EXCEPTIONS` which would only rethrow app-specific exceptions (#337)
- Several classes were renamed (see [UPGRADE.md](UPGRADE.md))
- Schema Validation improvements
#### v0.12.6
- Bugfix: Call to a member function getLocation() on null (#336)
- Fixed several errors discovered by static analysis (#329)
#### v0.12.5
- Execution performance optimization for lists
#### v0.12.4
- Allow stringeable objects to be serialized by StringType (#303)
#### v0.12.3
- StandardServer: add support for the multipart/form-data content type (#300)
#### v0.12.2
- SchemaPrinter: Use multi-line block for trailing quote (#294)
#### v0.12.1
- Fixed bug in validation rule OverlappingFieldsCanBeMerged (#292)
- Added one more breaking change note in UPGRADE.md (#291)
- Spec compliance: remove `data` entry from response on top-level error (#281)
## v0.12.0
- RFC: Block String (multi-line strings via triple-quote """string""")
- GraphQL Schema SDL: Descriptions as strings (including multi-line)

View file

@ -1,7 +1,6 @@
# Contributing to GraphQL PHP
## Workflow
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
a brief proposal and discuss it with us first.
@ -11,17 +10,46 @@ For smaller contributions just use this workflow:
* Fork the project.
* Add your features and or bug fixes.
* Add tests. Tests are important for us.
* Send a pull request
* Check your changes using `composer check-all`.
* Add an entry to the [Changelog's Unreleases section](CHANGELOG.md#unreleased).
* Send a pull request.
## Using GraphQL PHP from a Git checkout
```
$ git clone https://github.com/webonyx/graphql-php.git
$ cd graphql-php
$ composer install
## Setup the Development Environment
First, copy the URL of your fork and `git clone` it to your local machine.
```sh
cd graphql-php
composer install
```
## Running tests
From the project root:
```sh
./vendor/bin/phpunit
```
$ ./bin/phpunit
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
The coding standard of this project is based on [Doctrine CS](https://github.com/doctrine/coding-standard).
Run the inspections:
```sh
./vendor/bin/phpcs
```
Apply automatic code style fixes:
```sh
./vendor/bin/phpcbf
```
## Static analysis
Based on [PHPStan](https://github.com/phpstan/phpstan).
```sh
./vendor/bin/phpstan analyse --ansi --memory-limit 256M
```
## Running benchmarks
Benchmarks are run via [PHPBench](https://github.com/phpbench/phpbench).
```sh
./vendor/bin/phpbench run .
```

View file

@ -1,6 +1,6 @@
# graphql-php
[![Build Status](https://travis-ci.org/webonyx/graphql-php.svg?branch=master)](https://travis-ci.org/webonyx/graphql-php)
[![Coverage Status](https://coveralls.io/repos/github/webonyx/graphql-php/badge.svg)](https://coveralls.io/github/webonyx/graphql-php)
[![Code Coverage](https://scrutinizer-ci.com/g/webonyx/graphql-php/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/webonyx/graphql-php)
[![Latest Stable Version](https://poser.pugx.org/webonyx/graphql-php/version)](https://packagist.org/packages/webonyx/graphql-php)
[![License](https://poser.pugx.org/webonyx/graphql-php/license)](https://packagist.org/packages/webonyx/graphql-php)
@ -14,7 +14,7 @@ composer require webonyx/graphql-php
```
## 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.
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
README file per example.
## Contribute
Please refer to [CONTRIBUTING.md](CONTRIBUTING.md) for information on how to contribute.
## Contributors
## Old README.md
Here is a [link to the old README.md](https://github.com/webonyx/graphql-php/blob/v0.9.14/README.md).
This project exists thanks to [all the people](https://github.com/webonyx/graphql-php/graphs/contributors) who contribute. [[Contribute](CONTRIBUTING.md)].
Keep in mind that it relates to the version 0.9.x. It may contain outdated information for
newer versions (even though we try to preserve backwards compatibility).
## Backers
<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,92 @@
## Master
### Breaking (major): dropped deprecations
- dropped deprecated `GraphQL\Schema`. Use `GraphQL\Type\Schema`.
## Upgrade v0.12.x > v0.13.x
### Breaking (major): minimum supported version of PHP
New minimum required version of PHP is **7.1+**
### Breaking (major): default errors formatting changed according to spec
**Category** and extensions assigned to errors are shown under `extensions` key
```php
$e = new Error(
'msg',
null,
null,
null,
null,
null,
['foo' => 'bar']
);
```
Formatting before the change:
```
'errors' => [
[
'message' => 'msg',
'category' => 'graphql',
'foo' => 'bar'
]
]
```
After the change:
```
'errors' => [
[
'message' => 'msg',
'extensions' => [
'category' => 'graphql',
'foo' => 'bar',
],
]
]
```
Note: if error extensions contain `category` key - it has a priority over default category.
You can always switch to [custom error formatting](https://webonyx.github.io/graphql-php/error-handling/#custom-error-handling-and-formatting) to revert to the old format.
### Try it: Experimental Executor with improved performance
It is disabled by default. To enable it, do the following
```php
<?php
use GraphQL\Executor\Executor;
use GraphQL\Experimental\Executor\CoroutineExecutor;
Executor::setImplementationFactory([CoroutineExecutor::class, 'create']);
```
**Please post your feedback about new executor at https://github.com/webonyx/graphql-php/issues/397
Especially if you had issues (because it may become the default in one of the next releases)**
### Breaking: multiple interfaces separated with & in SDL
Before the change:
```graphql
type Foo implements Bar, Baz { field: Type }
```
After the change:
```graphql
type Foo implements Bar & Baz { field: Type }
```
To allow for an adaptive migration, use `allowLegacySDLImplementsInterfaces` option of parser:
```php
Parser::parse($source, [ 'allowLegacySDLImplementsInterfaces' => true])
```
### Breaking: several classes renamed
- `AbstractValidationRule` renamed to `ValidationRule` (NS `GraphQL\Validator\Rules`)
- `AbstractQuerySecurity` renamed to `QuerySecurityRule` (NS `GraphQL\Validator\Rules`)
- `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
### Breaking: Minimum supported version is PHP5.6
@ -10,6 +99,33 @@ Exception inside `parseLiteral()`, `parseValue()` and `serialize()`.
Returning null from any of these methods will now be treated as valid result.
### Breaking: Custom scalar types parseLiteral() declaration changed
A new parameter was added to `parseLiteral()`, which also needs to be added to any custom scalar type extending from `ScalarType`
Before:
```php
class MyType extends ScalarType {
...
public function parseLiteral($valueNode) {
//custom implementation
}
}
```
After:
```php
class MyType extends ScalarType {
...
public function parseLiteral($valueNode, array $variables = null) {
//custom implementation
}
}
```
### Breaking: Descriptions in comments are not used as descriptions by default anymore
Descriptions now need to be inside Strings or BlockStrings in order to be picked up as
description. If you want to keep the old behaviour you can supply the option `commentDescriptions`
@ -42,6 +158,10 @@ type Dog {
}
```
### Breaking: Cached AST of version 0.11.x is not compatible with 0.12.x.
That's because description in AST is now a separate node, not just a string.
Make sure to renew caches.
### Breaking: Most of previously deprecated classes and methods were removed
See deprecation notices for previous versions in details.

View file

@ -1,11 +1,11 @@
<?php
namespace GraphQL\Benchmarks;
use GraphQL\GraphQL;
use GraphQL\Schema;
use GraphQL\Benchmarks\Utils\QueryGenerator;
use GraphQL\Benchmarks\Utils\SchemaGenerator;
use GraphQL\Type\LazyResolution;
use GraphQL\GraphQL;
use GraphQL\Type\Schema;
use GraphQL\Type\SchemaConfig;
/**
* @BeforeMethods({"setUp"})
@ -16,18 +16,12 @@ use GraphQL\Type\LazyResolution;
*/
class HugeSchemaBench
{
/**
* @var SchemaGenerator
*/
/** @var SchemaGenerator */
private $schemaBuilder;
private $schema;
private $lazySchema;
/**
* @var string
*/
/** @var string */
private $smallQuery;
public function setUp()
@ -36,12 +30,12 @@ class HugeSchemaBench
'totalTypes' => 600,
'fieldsPerType' => 8,
'listFieldsPerType' => 2,
'nestingLevel' => 10
'nestingLevel' => 10,
]);
$this->schema = $this->schemaBuilder->buildSchema();
$queryBuilder = new QueryGenerator($this->schema, 0.05);
$queryBuilder = new QueryGenerator($this->schema, 0.05);
$this->smallQuery = $queryBuilder->buildQuery();
}
@ -49,8 +43,7 @@ class HugeSchemaBench
{
$this->schemaBuilder
->buildSchema()
->getTypeMap()
;
->getTypeMap();
}
public function benchSchemaLazy()
@ -60,21 +53,21 @@ class HugeSchemaBench
public function benchSmallQuery()
{
$result = GraphQL::execute($this->schema, $this->smallQuery);
$result = GraphQL::executeQuery($this->schema, $this->smallQuery);
}
public function benchSmallQueryLazy()
{
$schema = $this->createLazySchema();
$result = GraphQL::execute($schema, $this->smallQuery);
$result = GraphQL::executeQuery($schema, $this->smallQuery);
}
private function createLazySchema()
{
return new Schema(
\GraphQL\Type\SchemaConfig::create()
SchemaConfig::create()
->setQuery($this->schemaBuilder->buildQueryType())
->setTypeLoader(function($name) {
->setTypeLoader(function ($name) {
return $this->schemaBuilder->loadType($name);
})
);

View file

@ -1,5 +1,6 @@
<?php
namespace GraphQL\Benchmarks;
use GraphQL\GraphQL;
use GraphQL\Tests\StarWarsSchema;
use GraphQL\Type\Introspection;
@ -35,7 +36,7 @@ class StarWarsBench
}
';
GraphQL::execute(
GraphQL::executeQuery(
StarWarsSchema::build(),
$q
);
@ -57,7 +58,7 @@ class StarWarsBench
}
}
';
GraphQL::execute(
GraphQL::executeQuery(
StarWarsSchema::build(),
$q
);
@ -81,7 +82,7 @@ class StarWarsBench
}
';
GraphQL::execute(
GraphQL::executeQuery(
StarWarsSchema::build(),
$q
);
@ -89,7 +90,7 @@ class StarWarsBench
public function benchStarWarsIntrospectionQuery()
{
GraphQL::execute(
GraphQL::executeQuery(
StarWarsSchema::build(),
$this->introQuery
);

View file

@ -7,12 +7,15 @@ use GraphQL\Language\AST\NameNode;
use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Language\Printer;
use GraphQL\Schema;
use GraphQL\Type\Definition\FieldDefinition;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\WrappingType;
use GraphQL\Type\Schema;
use GraphQL\Utils\Utils;
use function count;
use function max;
use function round;
class QueryGenerator
{
@ -30,12 +33,14 @@ class QueryGenerator
$totalFields = 0;
foreach ($schema->getTypeMap() as $type) {
if ($type instanceof ObjectType) {
$totalFields += count($type->getFields());
if (! ($type instanceof ObjectType)) {
continue;
}
$totalFields += count($type->getFields());
}
$this->maxLeafFields = max(1, round($totalFields * $percentOfLeafFields));
$this->maxLeafFields = max(1, round($totalFields * $percentOfLeafFields));
$this->currentLeafFields = 0;
}
@ -44,13 +49,12 @@ class QueryGenerator
$qtype = $this->schema->getQueryType();
$ast = new DocumentNode([
'definitions' => [
new OperationDefinitionNode([
'name' => new NameNode(['value' => 'TestQuery']),
'operation' => 'query',
'selectionSet' => $this->buildSelectionSet($qtype->getFields())
])
]
'definitions' => [new OperationDefinitionNode([
'name' => new NameNode(['value' => 'TestQuery']),
'operation' => 'query',
'selectionSet' => $this->buildSelectionSet($qtype->getFields()),
]),
],
]);
return Printer::doPrint($ast);
@ -58,12 +62,13 @@ class QueryGenerator
/**
* @param FieldDefinition[] $fields
*
* @return SelectionSetNode
*/
public function buildSelectionSet($fields)
{
$selections[] = new FieldNode([
'name' => new NameNode(['value' => '__typename'])
'name' => new NameNode(['value' => '__typename']),
]);
$this->currentLeafFields++;
@ -87,12 +92,12 @@ class QueryGenerator
$selections[] = new FieldNode([
'name' => new NameNode(['value' => $field->name]),
'selectionSet' => $selectionSet
'selectionSet' => $selectionSet,
]);
}
$selectionSet = new SelectionSetNode([
'selections' => $selections
'selections' => $selections,
]);
return $selectionSet;

View file

@ -1,11 +1,11 @@
<?php
namespace GraphQL\Benchmarks\Utils;
use GraphQL\Schema;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\InputObjectType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
class SchemaGenerator
{
@ -152,7 +152,7 @@ class SchemaGenerator
];
}
public function resolveField($value, $args, $context, $resolveInfo)
public function resolveField($objectValue, $args, $context, $resolveInfo)
{
return $resolveInfo->fieldName . '-value';
}

View file

@ -9,21 +9,25 @@
"API"
],
"require": {
"php": ">=5.6",
"php": "^7.1||^8.0",
"ext-json": "*",
"ext-mbstring": "*"
},
"require-dev": {
"phpunit/phpunit": "^4.8",
"psr/http-message": "^1.0"
"doctrine/coding-standard": "^6.0",
"phpbench/phpbench": "^0.14.0",
"phpstan/phpstan": "^0.11.12",
"phpstan/phpstan-phpunit": "^0.11.2",
"phpstan/phpstan-strict-rules": "^0.11.1",
"phpunit/phpcov": "^5.0",
"phpunit/phpunit": "^7.2",
"psr/http-message": "^1.0",
"react/promise": "2.*"
},
"config": {
"bin-dir": "bin",
"platform": {
"php": "5.6.0"
},
"preferred-install": "dist",
"sort-packages": true
},
},
"autoload": {
"psr-4": {
"GraphQL\\": "src/"
@ -39,5 +43,14 @@
"suggest": {
"react/promise": "To leverage async resolving on React PHP platform",
"psr/http-message": "To use standard GraphQL server"
},
"scripts": {
"api-docs": "php tools/gendocs.php",
"bench": "phpbench run .",
"test": "phpunit",
"lint" : "phpcs",
"fix-style" : "phpcbf",
"static-analysis": "phpstan analyse --ansi --memory-limit 256M",
"check-all": "composer lint && composer static-analysis && composer test"
}
}

View file

@ -1,20 +1,26 @@
# Integrations
* [Integration with Relay](https://github.com/ivome/graphql-relay-php)
* [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)
* [Symfony Bundle](https://github.com/overblog/GraphQLBundle) by Overblog
* 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)
* [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/)).
* [Relay Library for graphql-php](https://github.com/ivome/graphql-relay-php) Helps construct Relay related schema definitions.
* [Lighthouse](https://github.com/nuwave/lighthouse) Laravel based, uses Schema Definition Language
* [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
* 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))
* [DataLoader PHP](https://github.com/overblog/dataloader-php) - as a ready implementation for [deferred resolvers](data-fetching.md#solving-n1-problem)
* [PSR 15 compliant middleware](https://github.com/phps-cans/psr7-middleware-graphql) for the Standard Server (experimental)
* [GraphQL Uploads](https://github.com/Ecodev/graphql-upload) for the Standard Server
* [GraphQLite](https://graphqlite.thecodingmachine.io) Define your complete schema with annotations
* [GraphQL Doctrine](https://github.com/Ecodev/graphql-doctrine) Define types with Doctrine ORM annotations
* [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) A PSR-15 middleware to support file uploads in GraphQL.
* [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
* [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)
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

View file

@ -103,23 +103,25 @@ for a field you simply override this default resolver.
**graphql-php** provides following default field resolver:
```php
<?php
function defaultFieldResolver($source, $args, $context, \GraphQL\Type\Definition\ResolveInfo $info)
{
$fieldName = $info->fieldName;
$property = null;
function defaultFieldResolver($objectValue, $args, $context, \GraphQL\Type\Definition\ResolveInfo $info)
{
$fieldName = $info->fieldName;
$property = null;
if (is_array($source) || $source instanceof \ArrayAccess) {
if (isset($source[$fieldName])) {
$property = $source[$fieldName];
}
} else if (is_object($source)) {
if (isset($source->{$fieldName})) {
$property = $source->{$fieldName};
if (is_array($objectValue) || $objectValue instanceof \ArrayAccess) {
if (isset($objectValue[$fieldName])) {
$property = $objectValue[$fieldName];
}
} elseif (is_object($objectValue)) {
if (isset($objectValue->{$fieldName})) {
$property = $objectValue->{$fieldName};
}
}
return $property instanceof Closure
? $property($objectValue, $args, $context, $info)
: $property;
}
return $property instanceof \Closure ? $property($source, $args, $context) : $property;
}
```
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
has precedence over **default field resolver**.
# Solving N+1 Problem
Since: 0.9.0
@ -270,4 +271,4 @@ Where **$promiseAdapter** is an instance of:
* Other platforms: write your own class implementing interface: <br>
[`GraphQL\Executor\Promise\PromiseAdapter`](reference.md#graphqlexecutorpromisepromiseadapter).
Then your **resolve** functions should return promises of your platform instead of `GraphQL\Deferred`s.
Then your **resolve** functions should return promises of your platform instead of `GraphQL\Deferred`s.

View file

@ -84,7 +84,7 @@ To change default **"Internal server error"** message to something else, use:
GraphQL\Error\FormattedError::setInternalErrorMessage("Unexpected error");
```
#Debugging tools
# Debugging tools
During development or debugging use `$result->toArray(true)` to add **debugMessage** key to
each formatted error entry. If you also want to add exception trace - pass flags instead:
@ -116,7 +116,7 @@ This will make each error entry to look like this:
];
```
If you prefer first resolver exception to be re-thrown, use following flags:
If you prefer the first resolver exception to be re-thrown, use following flags:
```php
<?php
use GraphQL\GraphQL;
@ -127,6 +127,9 @@ $debug = Debug::INCLUDE_DEBUG_MESSAGE | Debug::RETHROW_INTERNAL_EXCEPTIONS;
$result = GraphQL::executeQuery(/*args*/)->toArray($debug);
```
If you only want to re-throw Exceptions that are not marked as safe through the `ClientAware` interface, use
the flag `Debug::RETHROW_UNSAFE_EXCEPTIONS`.
# Custom Error Handling and Formatting
It is possible to define custom **formatter** and **handler** for result errors.

View file

@ -136,13 +136,13 @@ So for example following batch will require single DB request (if user field is
```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

@ -4,7 +4,7 @@ first learn about GraphQL on [the official website](http://graphql.org/learn/).
# Installation
Using [composer](https://getcomposer.org/doc/00-intro.md), simply run:
Using [composer](https://getcomposer.org/doc/00-intro.md), run:
```sh
composer require webonyx/graphql-php
@ -54,8 +54,8 @@ $queryType = new ObjectType([
'args' => [
'message' => Type::nonNull(Type::string()),
],
'resolve' => function ($root, $args) {
return $root['prefix'] . $args['message'];
'resolve' => function ($rootValue, $args) {
return $rootValue['prefix'] . $args['message'];
}
],
],
@ -106,7 +106,7 @@ echo json_encode($output);
Our example is finished. Try it by running:
```sh
php -S localhost:8000 graphql.php
php -S localhost:8080 graphql.php
curl http://localhost:8080 -d '{"query": "query { echo(message: \"Hello World\") }" }'
```

View file

@ -7,7 +7,7 @@
# About GraphQL
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
engineers. Various implementations of this specification were written
@ -44,8 +44,8 @@ existing PHP frameworks, add support for Relay, etc.
## Current Status
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
(including April 2016 add-ons) as well as some experimental features like
The current version supports all features described by GraphQL specification
as well as some experimental features like
[Schema Language parser](type-system/type-language.md) and
[Schema printer](reference.md#graphqlutilsschemaprinter).

File diff suppressed because it is too large Load diff

View file

@ -58,4 +58,4 @@ $trackDirective = new Directive([
```
See possible directive locations in
[`GraphQL\Type\Definition\DirectiveLocation`](../reference.md#graphqltypedefinitiondirectivelocation).
[`GraphQL\Language\DirectiveLocation`](../reference.md#graphqllanguagedirectivelocation).

View file

@ -158,7 +158,7 @@ $heroType = new ObjectType([
'args' => [
'episode' => Type::nonNull($enumType)
],
'resolve' => function($_value, $args) {
'resolve' => function($hero, $args) {
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
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)
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
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),
'args' => [
'filters' => [
'type' => Type::nonNull($filters),
'type' => $filters,
'defaultValue' => [
'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)
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.
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.
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)
@ -94,7 +94,7 @@ Option | Type | Notes
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)
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
Fields can be also defined in **shorthand** notation (with only **name** and **type** options):

View file

@ -96,10 +96,11 @@ class EmailType extends ScalarType
* }
*
* @param \GraphQL\Language\AST\Node $valueNode
* @param array|null $variables
* @return string
* @throws Error
*/
public function parseLiteral($valueNode)
public function parseLiteral($valueNode, array $variables = null)
{
// Note: throwing GraphQL\Error\Error vs \UnexpectedValueException to benefit from GraphQL
// error location in query:
@ -124,6 +125,6 @@ $emailType = new CustomScalarType([
'name' => 'Email',
'serialize' => function($value) {/* See function body above */},
'parseValue' => function($value) {/* See function body above */},
'parseLiteral' => function($valueNode) {/* See function body above */},
'parseLiteral' => function($valueNode, array $variables = null) {/* See function body above */},
]);
```

View file

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

View file

@ -38,6 +38,30 @@ By default, such schema is created without any resolvers.
We have to rely on [default field resolver](../data-fetching.md#default-field-resolver) and **root value** in
order to execute a query against this schema.
# Defining resolvers
Since 0.10.0
In order to enable **Interfaces**, **Unions** and custom field resolvers you can pass the second argument:
**type config decorator** to schema builder.
It accepts default type config produced by the builder and is expected to add missing options like
[**resolveType**](interfaces.md#configuration-options) for interface types or
[**resolveField**](object-types.md#configuration-options) for object types.
```php
<?php
use GraphQL\Utils\BuildSchema;
$typeConfigDecorator = function($typeConfig, $typeDefinitionNode) {
$name = $typeConfig['name'];
// ... add missing options to $typeConfig based on type $name
return $typeConfig;
};
$contents = file_get_contents('schema.graphql');
$schema = BuildSchema::build($contents, $typeConfigDecorator);
```
# Performance considerations
Since 0.10.0
@ -57,7 +81,7 @@ $cacheFilename = 'cached_schema.php';
if (!file_exists($cacheFilename)) {
$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 {
$document = AST::fromArray(require $cacheFilename); // fromArray() is a lazy operation as well
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -18,7 +18,7 @@ try {
$query = $input['query'];
$variableValues = isset($input['variables']) ? $input['variables'] : null;
$result = GraphQL::execute($schema, $query, $rootValue, null, $variableValues);
$result = GraphQL::executeQuery($schema, $query, $rootValue, null, $variableValues);
} catch (\Exception $e) {
$result = [
'error' => [

View file

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

View file

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

102
phpcs.xml.dist Normal file
View file

@ -0,0 +1,102 @@
<?xml version="1.0"?>
<ruleset>
<arg name="basepath" value="." />
<arg name="extensions" value="php" />
<arg name="parallel" value="80" />
<arg name="cache" value=".phpcs-cache" />
<arg name="colors" />
<!-- Ignore warnings, show progress of the run and show sniff names -->
<arg value="nps" />
<file>src</file>
<file>tests</file>
<rule ref="Doctrine">
<!-- Disable PHP7+ features that might cause BC breaks for now -->
<exclude name="SlevomatCodingStandard.Classes.ClassConstantVisibility.MissingConstantVisibility" />
<exclude name="SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingParameterTypeHint" />
<exclude name="SlevomatCodingStandard.TypeHints.TypeHintDeclaration.MissingReturnTypeHint" />
<!-- Enable when Slevomat starts supporting variadics, see https://github.com/slevomat/coding-standard/issues/251 -->
<exclude name="SlevomatCodingStandard.Namespaces.UnusedUses.UnusedUse" />
</rule>
<!--@api annotation is required for now -->
<rule ref="SlevomatCodingStandard.TypeHints.TypeHintDeclaration">
<properties>
<property
name="usefulAnnotations"
type="array"
value="
@after,
@afterClass,
@AfterMethods,
@api,
@Attribute,
@Attributes,
@before,
@beforeClass,
@BeforeMethods,
@covers,
@coversDefaultClass,
@coversNothing,
@dataProvider,
@depends,
@deprecated,
@doesNotPerformAssertions,
@Enum,
@expectedDeprecation,
@expectedException,
@expectedExceptionCode,
@expectedExceptionMessage,
@expectedExceptionMessageRegExp,
@group,
@Groups,
@IgnoreAnnotation,
@internal,
@Iterations,
@link,
@ODM\,
@ORM\,
@requires,
@Required,
@Revs,
@runInSeparateProcess,
@runTestsInSeparateProcesses,
@see,
@Target,
@test,
@throws,
@uses
"
/>
</properties>
</rule>
<rule ref="SlevomatCodingStandard.Commenting.ForbiddenAnnotations">
<properties>
<property
name="forbiddenAnnotations"
type="array"
value="
@author,
@category,
@copyright,
@created,
@license,
@package,
@since,
@subpackage,
@version
"
/>
</properties>
</rule>
<!-- IDEs sort by PSR12, Slevomat coding standard uses old sorting for BC -->
<rule ref="SlevomatCodingStandard.Namespaces.AlphabeticallySortedUses">
<properties>
<property name="psr12Compatible" type="bool" value="true" />
</properties>
</rule>
</ruleset>

17
phpstan.neon.dist Normal file
View file

@ -0,0 +1,17 @@
parameters:
level: 1
paths:
- %currentWorkingDirectory%/src
- %currentWorkingDirectory%/tests
ignoreErrors:
- "~Construct empty\\(\\) is not allowed\\. Use more strict comparison~"
- "~(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:
- vendor/phpstan/phpstan-phpunit/extension.neon
- vendor/phpstan/phpstan-phpunit/rules.neon
- vendor/phpstan/phpstan-strict-rules/rules.neon

View file

@ -1,16 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="utf-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="tests/bootstrap.php"
>
<php>
<ini name="error_reporting" value="E_ALL"/>
</php>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="vendor/autoload.php"
>
<testsuites>
<testsuite name="webonyx/graphql-php Test Suite">
<directory>./tests/</directory>
@ -26,21 +23,6 @@
<filter>
<whitelist>
<directory suffix=".php">./src</directory>
<exclude>
<directory>./bin</directory>
<directory>./docs</directory>
<directory>./build</directory>
<directory>./tests</directory>
<directory>./vendor</directory>
<directory>./examples</directory>
<directory>./benchmarks</directory>
<file>./src/deprecated.php</file>
</exclude>
</whitelist>
</filter>
<php>
<ini name="error_reporting" value="E_ALL"/>
</php>
</phpunit>

View file

@ -1,60 +1,64 @@
<?php
declare(strict_types=1);
namespace GraphQL;
use Exception;
use GraphQL\Executor\Promise\Adapter\SyncPromise;
use SplQueue;
use Throwable;
class Deferred
{
/**
* @var \SplQueue
*/
/** @var SplQueue|null */
private static $queue;
/**
* @var callable
*/
/** @var callable */
private $callback;
/**
* @var SyncPromise
*/
/** @var SyncPromise */
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)
{
$this->callback = $callback;
$this->promise = new SyncPromise();
$this->promise = new SyncPromise();
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)
{
return $this->promise->then($onFulfilled, $onRejected);
}
private function run()
public function run() : void
{
try {
$cb = $this->callback;
$this->promise->resolve($cb());
} catch (\Exception $e) {
} catch (Exception $e) {
$this->promise->reject($e);
} catch (\Throwable $e) {
} catch (Throwable $e) {
$this->promise->reject($e);
}
}

View file

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Error;
/**
@ -14,8 +17,9 @@ interface ClientAware
/**
* Returns true when exception message is safe to be displayed to a client.
*
* @api
* @return bool
*
* @api
*/
public function isClientSafe();
@ -24,8 +28,9 @@ interface ClientAware
*
* Value "graphql" is reserved for errors produced by query parsing or validation, do not use it.
*
* @api
* @return string
*
* @api
*/
public function getCategory();
}

View file

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Error;
/**
@ -6,7 +9,8 @@ namespace GraphQL\Error;
*/
class Debug
{
const INCLUDE_DEBUG_MESSAGE = 1;
const INCLUDE_TRACE = 2;
const INCLUDE_DEBUG_MESSAGE = 1;
const INCLUDE_TRACE = 2;
const RETHROW_INTERNAL_EXCEPTIONS = 4;
const RETHROW_UNSAFE_EXCEPTIONS = 8;
}

View file

@ -1,10 +1,22 @@
<?php
declare(strict_types=1);
namespace GraphQL\Error;
use Exception;
use GraphQL\Language\AST\Node;
use GraphQL\Language\Source;
use GraphQL\Language\SourceLocation;
use GraphQL\Utils\Utils;
use JsonSerializable;
use Throwable;
use Traversable;
use function array_filter;
use function array_map;
use function array_values;
use function is_array;
use function iterator_to_array;
/**
* Describes an Error found during the parse, validate, or
@ -20,9 +32,9 @@ use GraphQL\Utils\Utils;
* Class extends standard PHP `\Exception`, so all standard methods of base `\Exception` class
* are available in addition to those listed below.
*/
class Error extends \Exception implements \JsonSerializable, ClientAware
class Error extends Exception implements JsonSerializable, ClientAware
{
const CATEGORY_GRAPHQL = 'graphql';
const CATEGORY_GRAPHQL = 'graphql';
const CATEGORY_INTERNAL = 'internal';
/**
@ -32,23 +44,21 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
*/
public $message;
/**
* @var SourceLocation[]
*/
/** @var SourceLocation[] */
private $locations;
/**
* An array describing the JSON-path into the execution response which
* corresponds to this error. Only included for errors during execution.
*
* @var array
* @var mixed[]|null
*/
public $path;
/**
* An array of GraphQL AST Nodes corresponding to this error.
*
* @var array
* @var Node[]|null
*/
public $nodes;
@ -62,34 +72,75 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
*/
private $source;
/**
* @var array
*/
/** @var int[]|null */
private $positions;
/**
* @var bool
*/
/** @var bool */
private $isClientSafe;
/**
* @var string
*/
/** @var string */
protected $category;
/**
* @var array
*/
/** @var mixed[]|null */
protected $extensions;
/**
* @param string $message
* @param Node|Node[]|Traversable|null $nodes
* @param mixed[]|null $positions
* @param mixed[]|null $path
* @param Throwable $previous
* @param mixed[] $extensions
*/
public function __construct(
$message,
$nodes = null,
?Source $source = null,
$positions = null,
$path = null,
$previous = null,
array $extensions = []
) {
parent::__construct($message, 0, $previous);
// Compute list of blame nodes.
if ($nodes instanceof Traversable) {
$nodes = iterator_to_array($nodes);
} elseif ($nodes && ! is_array($nodes)) {
$nodes = [$nodes];
}
$this->nodes = $nodes;
$this->source = $source;
$this->positions = $positions;
$this->path = $path;
$this->extensions = $extensions ?: (
$previous && $previous instanceof self
? $previous->extensions
: []
);
if ($previous instanceof ClientAware) {
$this->isClientSafe = $previous->isClientSafe();
$this->category = $previous->getCategory() ?: self::CATEGORY_INTERNAL;
} elseif ($previous) {
$this->isClientSafe = false;
$this->category = self::CATEGORY_INTERNAL;
} else {
$this->isClientSafe = true;
$this->category = self::CATEGORY_GRAPHQL;
}
}
/**
* Given an arbitrary Error, presumably thrown while attempting to execute a
* GraphQL operation, produce a new GraphQLError aware of the location in the
* document responsible for the original Error.
*
* @param $error
* @param array|null $nodes
* @param array|null $path
* @param mixed $error
* @param Node[]|null $nodes
* @param mixed[]|null $path
*
* @return Error
*/
public static function createLocatedError($error, $nodes = null, $path = null)
@ -97,24 +148,24 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
if ($error instanceof self) {
if ($error->path && $error->nodes) {
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;
$extensions = [];
if ($error instanceof self) {
$message = $error->getMessage();
$message = $error->getMessage();
$originalError = $error;
$nodes = $error->nodes ?: $nodes;
$source = $error->source;
$positions = $error->positions;
$extensions = $error->extensions;
} else if ($error instanceof \Exception || $error instanceof \Throwable) {
$message = $error->getMessage();
$nodes = $error->nodes ?: $nodes;
$source = $error->source;
$positions = $error->positions;
$extensions = $error->extensions;
} elseif ($error instanceof Exception || $error instanceof Throwable) {
$message = $error->getMessage();
$originalError = $error;
} else {
$message = (string) $error;
@ -131,66 +182,14 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
);
}
/**
* @param Error $error
* @return array
* @return mixed[]
*/
public static function formatError(Error $error)
{
return $error->toSerializableArray();
}
/**
* @param string $message
* @param array|Node|null $nodes
* @param Source $source
* @param array|null $positions
* @param array|null $path
* @param \Throwable $previous
* @param array $extensions
*/
public function __construct(
$message,
$nodes = null,
Source $source = null,
$positions = null,
$path = null,
$previous = null,
array $extensions = []
)
{
parent::__construct($message, 0, $previous);
// Compute list of blame nodes.
if ($nodes instanceof \Traversable) {
$nodes = iterator_to_array($nodes);
} else if ($nodes && !is_array($nodes)) {
$nodes = [$nodes];
}
$this->nodes = $nodes;
$this->source = $source;
$this->positions = $positions;
$this->path = $path;
$this->extensions = $extensions ?: (
$previous && $previous instanceof self
? $previous->extensions
: []
);
if ($previous instanceof ClientAware) {
$this->isClientSafe = $previous->isClientSafe();
$this->category = $previous->getCategory() ?: static::CATEGORY_INTERNAL;
} else if ($previous) {
$this->isClientSafe = false;
$this->category = static::CATEGORY_INTERNAL;
} else {
$this->isClientSafe = true;
$this->category = static::CATEGORY_GRAPHQL;
}
}
/**
* @inheritdoc
*/
@ -212,29 +211,38 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
*/
public function getSource()
{
if (null === $this->source) {
if (!empty($this->nodes[0]) && !empty($this->nodes[0]->loc)) {
if ($this->source === null) {
if (! empty($this->nodes[0]) && ! empty($this->nodes[0]->loc)) {
$this->source = $this->nodes[0]->loc->source;
}
}
return $this->source;
}
/**
* @return array
* @return int[]
*/
public function getPositions()
{
if (null === $this->positions) {
if (!empty($this->nodes)) {
$positions = array_map(function($node) {
if ($this->positions === null && ! empty($this->nodes)) {
$positions = array_map(
static function ($node) {
return isset($node->loc) ? $node->loc->start : null;
}, $this->nodes);
$this->positions = array_filter($positions, function($p) {
},
$this->nodes
);
$positions = array_filter(
$positions,
static function ($p) {
return $p !== null;
});
}
}
);
$this->positions = array_values($positions);
}
return $this->positions;
}
@ -249,26 +257,36 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
* point out to field mentioned in multiple fragments. Errors during execution include a
* single location, the field which produced the error.
*
* @api
* @return SourceLocation[]
*
* @api
*/
public function getLocations()
{
if (null === $this->locations) {
if ($this->locations === null) {
$positions = $this->getPositions();
$source = $this->getSource();
$nodes = $this->nodes;
$source = $this->getSource();
$nodes = $this->nodes;
if ($positions && $source) {
$this->locations = array_map(function ($pos) use ($source) {
return $source->getLocation($pos);
}, $positions);
} else if ($nodes) {
$this->locations = array_filter(array_map(function ($node) {
if ($node->loc) {
return $node->loc->source->getLocation($node->loc->start);
}
}, $nodes));
$this->locations = array_map(
static function ($pos) use ($source) {
return $source->getLocation($pos);
},
$positions
);
} elseif ($nodes) {
$locations = array_filter(
array_map(
static function ($node) {
if ($node->loc && $node->loc->source) {
return $node->loc->source->getLocation($node->loc->start);
}
},
$nodes
)
);
$this->locations = array_values($locations);
} else {
$this->locations = [];
}
@ -278,7 +296,7 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
}
/**
* @return array|Node[]|null
* @return Node[]|null
*/
public function getNodes()
{
@ -289,8 +307,9 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
* Returns an array describing the path from the root value to the field which produced this error.
* Only included for execution errors.
*
* @return mixed[]|null
*
* @api
* @return array|null
*/
public function getPath()
{
@ -298,7 +317,7 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
}
/**
* @return array
* @return mixed[]
*/
public function getExtensions()
{
@ -309,40 +328,44 @@ class Error extends \Exception implements \JsonSerializable, ClientAware
* Returns array representation of error suitable for serialization
*
* @deprecated Use FormattedError::createFromException() instead
* @return array
*
* @return mixed[]
*/
public function toSerializableArray()
{
$arr = [
'message' => $this->getMessage()
'message' => $this->getMessage(),
];
if ($this->getExtensions()) {
$arr = array_merge($this->getExtensions(), $arr);
}
$locations = Utils::map(
$this->getLocations(),
static function (SourceLocation $loc) {
return $loc->toSerializableArray();
}
);
$locations = Utils::map($this->getLocations(), function(SourceLocation $loc) {
return $loc->toSerializableArray();
});
if (!empty($locations)) {
if (! empty($locations)) {
$arr['locations'] = $locations;
}
if (!empty($this->path)) {
if (! empty($this->path)) {
$arr['path'] = $this->path;
}
if (! empty($this->extensions)) {
$arr['extensions'] = $this->extensions;
}
return $arr;
}
/**
* Specify data which should be serialized to JSON
*
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php
*
* @return mixed data which can be serialized by <b>json_encode</b>,
* which is a value of any type other than a resource.
* @since 5.4.0
*/
function jsonSerialize()
public function jsonSerialize()
{
return $this->toSerializableArray();
}

View file

@ -1,12 +1,39 @@
<?php
declare(strict_types=1);
namespace GraphQL\Error;
use Countable;
use ErrorException;
use Exception;
use GraphQL\Language\AST\Node;
use GraphQL\Language\Source;
use GraphQL\Language\SourceLocation;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\WrappingType;
use GraphQL\Utils\Utils;
use Throwable;
use function addcslashes;
use function array_filter;
use function array_intersect_key;
use function array_map;
use function array_merge;
use function array_shift;
use function count;
use function get_class;
use function gettype;
use function implode;
use function is_array;
use function is_bool;
use function is_object;
use function is_scalar;
use function is_string;
use function mb_strlen;
use function preg_split;
use function sprintf;
use function str_repeat;
use function strlen;
/**
* This class is used for [default error formatting](error-handling.md).
@ -15,14 +42,16 @@ use GraphQL\Utils\Utils;
*/
class FormattedError
{
/** @var string */
private static $internalErrorMessage = 'Internal server error';
/**
* Set default error message for internal errors formatted using createFormattedError().
* This value can be overridden by passing 3rd argument to `createFormattedError()`.
*
* @api
* @param string $msg
*
* @api
*/
public static function setInternalErrorMessage($msg)
{
@ -33,7 +62,6 @@ class FormattedError
* Prints a GraphQLError to a string, representing useful location information
* about the error's position in the source.
*
* @param Error $error
* @return string
*/
public static function printError(Error $error)
@ -41,63 +69,65 @@ class FormattedError
$printedLocations = [];
if ($error->nodes) {
/** @var Node $node */
foreach($error->nodes as $node) {
if ($node->loc) {
$printedLocations[] = self::highlightSourceAtLocation(
$node->loc->source,
$node->loc->source->getLocation($node->loc->start)
);
foreach ($error->nodes as $node) {
if (! $node->loc) {
continue;
}
if ($node->loc->source === null) {
continue;
}
$printedLocations[] = self::highlightSourceAtLocation(
$node->loc->source,
$node->loc->source->getLocation($node->loc->start)
);
}
} else if ($error->getSource() && $error->getLocations()) {
} elseif ($error->getSource() && $error->getLocations()) {
$source = $error->getSource();
foreach($error->getLocations() as $location) {
foreach ($error->getLocations() as $location) {
$printedLocations[] = self::highlightSourceAtLocation($source, $location);
}
}
return !$printedLocations
return ! $printedLocations
? $error->getMessage()
: join("\n\n", array_merge([$error->getMessage()], $printedLocations)) . "\n";
: implode("\n\n", array_merge([$error->getMessage()], $printedLocations)) . "\n";
}
/**
* Render a helpful description of the location of the error in the GraphQL
* Source document.
*
* @param Source $source
* @param SourceLocation $location
* @return string
*/
private static function highlightSourceAtLocation(Source $source, SourceLocation $location)
{
$line = $location->line;
$lineOffset = $source->locationOffset->line - 1;
$columnOffset = self::getColumnOffset($source, $location);
$contextLine = $line + $lineOffset;
$line = $location->line;
$lineOffset = $source->locationOffset->line - 1;
$columnOffset = self::getColumnOffset($source, $location);
$contextLine = $line + $lineOffset;
$contextColumn = $location->column + $columnOffset;
$prevLineNum = (string) ($contextLine - 1);
$lineNum = (string) $contextLine;
$nextLineNum = (string) ($contextLine + 1);
$padLen = strlen($nextLineNum);
$lines = preg_split('/\r\n|[\n\r]/', $source->body);
$prevLineNum = (string) ($contextLine - 1);
$lineNum = (string) $contextLine;
$nextLineNum = (string) ($contextLine + 1);
$padLen = strlen($nextLineNum);
$lines = preg_split('/\r\n|[\n\r]/', $source->body);
$lines[0] = self::whitespace($source->locationOffset->column - 1) . $lines[0];
$outputLines = [
"{$source->name} ($contextLine:$contextColumn)",
sprintf('%s (%s:%s)', $source->name, $contextLine, $contextColumn),
$line >= 2 ? (self::lpad($padLen, $prevLineNum) . ': ' . $lines[$line - 2]) : null,
self::lpad($padLen, $lineNum) . ': ' . $lines[$line - 1],
self::whitespace(2 + $padLen + $contextColumn - 1) . '^',
$line < count($lines)? self::lpad($padLen, $nextLineNum) . ': ' . $lines[$line] : null
$line < count($lines) ? self::lpad($padLen, $nextLineNum) . ': ' . $lines[$line] : null,
];
return join("\n", array_filter($outputLines));
return implode("\n", array_filter($outputLines));
}
/**
* @param Source $source
* @param SourceLocation $location
* @return int
*/
private static function getColumnOffset(Source $source, SourceLocation $location)
@ -107,17 +137,21 @@ class FormattedError
/**
* @param int $len
*
* @return string
*/
private static function whitespace($len) {
private static function whitespace($len)
{
return str_repeat(' ', $len);
}
/**
* @param int $len
*
* @return string
*/
private static function lpad($len, $str) {
private static function lpad($len, $str)
{
return self::whitespace($len - mb_strlen($str)) . $str;
}
@ -130,18 +164,21 @@ class FormattedError
*
* For a list of available debug flags see GraphQL\Error\Debug constants.
*
* @param Throwable $e
* @param bool|int $debug
* @param string $internalErrorMessage
*
* @return mixed[]
*
* @throws Throwable
*
* @api
* @param \Throwable $e
* @param bool|int $debug
* @param string $internalErrorMessage
* @return array
* @throws \Throwable
*/
public static function createFromException($e, $debug = false, $internalErrorMessage = null)
{
Utils::invariant(
$e instanceof \Exception || $e instanceof \Throwable,
"Expected exception, got %s",
$e instanceof Exception || $e instanceof Throwable,
'Expected exception, got %s',
Utils::getVariableType($e)
);
@ -149,31 +186,36 @@ class FormattedError
if ($e instanceof ClientAware) {
$formattedError = [
'message' => $e->isClientSafe() ? $e->getMessage() : $internalErrorMessage,
'category' => $e->getCategory()
'message' => $e->isClientSafe() ? $e->getMessage() : $internalErrorMessage,
'extensions' => [
'category' => $e->getCategory(),
],
];
} else {
$formattedError = [
'message' => $internalErrorMessage,
'category' => Error::CATEGORY_INTERNAL
'message' => $internalErrorMessage,
'extensions' => [
'category' => Error::CATEGORY_INTERNAL,
],
];
}
if ($e instanceof Error) {
if ($e->getExtensions()) {
$formattedError = array_merge($e->getExtensions(), $formattedError);
}
$locations = Utils::map($e->getLocations(), function(SourceLocation $loc) {
return $loc->toSerializableArray();
});
if (!empty($locations)) {
$locations = Utils::map(
$e->getLocations(),
static function (SourceLocation $loc) {
return $loc->toSerializableArray();
}
);
if (! empty($locations)) {
$formattedError['locations'] = $locations;
}
if (!empty($e->path)) {
if (! empty($e->path)) {
$formattedError['path'] = $e->path;
}
if (! empty($e->getExtensions())) {
$formattedError['extensions'] = $e->getExtensions() + $formattedError['extensions'];
}
}
if ($debug) {
@ -187,56 +229,67 @@ class FormattedError
* Decorates spec-compliant $formattedError with debug entries according to $debug flags
* (see GraphQL\Error\Debug for available flags)
*
* @param array $formattedError
* @param \Throwable $e
* @param bool $debug
* @return array
* @throws \Throwable
* @param mixed[] $formattedError
* @param Throwable $e
* @param bool|int $debug
*
* @return mixed[]
*
* @throws Throwable
*/
public static function addDebugEntries(array $formattedError, $e, $debug)
{
if (!$debug) {
if (! $debug) {
return $formattedError;
}
Utils::invariant(
$e instanceof \Exception || $e instanceof \Throwable,
"Expected exception, got %s",
$e instanceof Exception || $e instanceof Throwable,
'Expected exception, got %s',
Utils::getVariableType($e)
);
$debug = (int) $debug;
if ($debug & Debug::RETHROW_INTERNAL_EXCEPTIONS) {
if (!$e instanceof Error) {
if (! $e instanceof Error) {
throw $e;
} else if ($e->getPrevious()) {
}
if ($e->getPrevious()) {
throw $e->getPrevious();
}
}
$isInternal = !$e instanceof ClientAware || !$e->isClientSafe();
$isUnsafe = ! $e instanceof ClientAware || ! $e->isClientSafe();
if (($debug & Debug::INCLUDE_DEBUG_MESSAGE) && $isInternal) {
if (($debug & Debug::RETHROW_UNSAFE_EXCEPTIONS) && $isUnsafe) {
if ($e->getPrevious()) {
throw $e->getPrevious();
}
}
if (($debug & Debug::INCLUDE_DEBUG_MESSAGE) && $isUnsafe) {
// Displaying debugMessage as a first entry:
$formattedError = ['debugMessage' => $e->getMessage()] + $formattedError;
}
if ($debug & Debug::INCLUDE_TRACE) {
if ($e instanceof \ErrorException || $e instanceof \Error) {
if ($e instanceof ErrorException || $e instanceof \Error) {
$formattedError += [
'file' => $e->getFile(),
'line' => $e->getLine(),
];
}
$isTrivial = $e instanceof Error && !$e->getPrevious();
$isTrivial = $e instanceof Error && ! $e->getPrevious();
if (!$isTrivial) {
$debugging = $e->getPrevious() ?: $e;
if (! $isTrivial) {
$debugging = $e->getPrevious() ?: $e;
$formattedError['trace'] = static::toSafeTrace($debugging);
}
}
return $formattedError;
}
@ -244,67 +297,71 @@ class FormattedError
* Prepares final error formatter taking in account $debug flags.
* If initial formatter is not set, FormattedError::createFromException is used
*
* @param callable|null $formatter
* @param $debug
* @return callable|\Closure
* @param bool|int $debug
*
* @return callable|callable
*/
public static function prepareFormatter(callable $formatter = null, $debug)
public static function prepareFormatter(?callable $formatter = null, $debug)
{
$formatter = $formatter ?: function($e) {
$formatter = $formatter ?: static function ($e) {
return FormattedError::createFromException($e);
};
if ($debug) {
$formatter = function($e) use ($formatter, $debug) {
$formatter = static function ($e) use ($formatter, $debug) {
return FormattedError::addDebugEntries($formatter($e), $e, $debug);
};
}
return $formatter;
}
/**
* Returns error trace as serializable array
*
* @param Throwable $error
*
* @return mixed[]
*
* @api
* @param \Throwable $error
* @return array
*/
public static function toSafeTrace($error)
{
$trace = $error->getTrace();
// Remove invariant entries as they don't provide much value:
if (
isset($trace[0]['function']) && isset($trace[0]['class']) &&
('GraphQL\Utils\Utils::invariant' === $trace[0]['class'].'::'.$trace[0]['function'])) {
if (isset($trace[0]['function']) && isset($trace[0]['class']) &&
// Remove invariant entries as they don't provide much value:
($trace[0]['class'] . '::' . $trace[0]['function'] === 'GraphQL\Utils\Utils::invariant')) {
array_shift($trace);
} elseif (! isset($trace[0]['file'])) {
// Remove root call as it's likely error handler trace:
array_shift($trace);
}
// Remove root call as it's likely error handler trace:
else if (!isset($trace[0]['file'])) {
array_shift($trace);
}
return array_map(
static function ($err) {
$safeErr = array_intersect_key($err, ['file' => true, 'line' => true]);
return array_map(function($err) {
$safeErr = array_intersect_key($err, ['file' => true, 'line' => true]);
if (isset($err['function'])) {
$func = $err['function'];
$args = ! empty($err['args']) ? array_map([self::class, 'printVar'], $err['args']) : [];
$funcStr = $func . '(' . implode(', ', $args) . ')';
if (isset($err['function'])) {
$func = $err['function'];
$args = !empty($err['args']) ? array_map([__CLASS__, 'printVar'], $err['args']) : [];
$funcStr = $func . '(' . implode(", ", $args) . ')';
if (isset($err['class'])) {
$safeErr['call'] = $err['class'] . '::' . $funcStr;
} else {
$safeErr['function'] = $funcStr;
if (isset($err['class'])) {
$safeErr['call'] = $err['class'] . '::' . $funcStr;
} else {
$safeErr['function'] = $funcStr;
}
}
}
return $safeErr;
}, $trace);
return $safeErr;
},
$trace
);
}
/**
* @param $var
* @param mixed $var
*
* @return string
*/
public static function printVar($var)
@ -314,16 +371,17 @@ class FormattedError
if ($var instanceof WrappingType) {
$var = $var->getWrappedType(true);
}
return 'GraphQLType: ' . $var->name;
}
if (is_object($var)) {
return 'instance of ' . get_class($var) . ($var instanceof \Countable ? '(' . count($var) . ')' : '');
return 'instance of ' . get_class($var) . ($var instanceof Countable ? '(' . count($var) . ')' : '');
}
if (is_array($var)) {
return 'array(' . count($var) . ')';
}
if ('' === $var) {
if ($var === '') {
return '(empty string)';
}
if (is_string($var)) {
@ -335,42 +393,48 @@ class FormattedError
if (is_scalar($var)) {
return $var;
}
if (null === $var) {
if ($var === null) {
return 'null';
}
return gettype($var);
}
/**
* @deprecated as of v0.8.0
* @param $error
*
* @param string $error
* @param SourceLocation[] $locations
* @return array
*
* @return mixed[]
*/
public static function create($error, array $locations = [])
{
$formatted = [
'message' => $error
];
$formatted = ['message' => $error];
if (!empty($locations)) {
$formatted['locations'] = array_map(function($loc) { return $loc->toArray();}, $locations);
if (! empty($locations)) {
$formatted['locations'] = array_map(
static function ($loc) {
return $loc->toArray();
},
$locations
);
}
return $formatted;
}
/**
* @param \ErrorException $e
* @deprecated as of v0.10.0, use general purpose method createFromException() instead
* @return array
*
* @return mixed[]
*/
public static function createFromPHPError(\ErrorException $e)
public static function createFromPHPError(ErrorException $e)
{
return [
'message' => $e->getMessage(),
'message' => $e->getMessage(),
'severity' => $e->getSeverity(),
'trace' => self::toSafeTrace($e)
'trace' => self::toSafeTrace($e),
];
}
}

View file

@ -1,15 +1,16 @@
<?php
declare(strict_types=1);
namespace GraphQL\Error;
use LogicException;
/**
* Class InvariantVoilation
*
* Note:
* This exception should not inherit base Error exception as it is raised when there is an error somewhere in
* user-land code
*
* @package GraphQL\Error
*/
class InvariantViolation extends \LogicException
class InvariantViolation extends LogicException
{
}

View file

@ -1,19 +1,22 @@
<?php
declare(strict_types=1);
namespace GraphQL\Error;
use GraphQL\Language\Source;
use function sprintf;
class SyntaxError extends Error
{
/**
* @param Source $source
* @param int $position
* @param int $position
* @param string $description
*/
public function __construct(Source $source, $position, $description)
{
parent::__construct(
"Syntax Error: $description",
sprintf('Syntax Error: %s', $description),
null,
$source,
[$position]

View file

@ -1,14 +1,15 @@
<?php
declare(strict_types=1);
namespace GraphQL\Error;
use RuntimeException;
/**
* Class UserError
*
* Error caused by actions of GraphQL clients. Can be safely displayed to a client...
*
* @package GraphQL\Error
*/
class UserError extends \RuntimeException implements ClientAware
class UserError extends RuntimeException implements ClientAware
{
/**
* @return bool

View file

@ -1,6 +1,14 @@
<?php
declare(strict_types=1);
namespace GraphQL\Error;
use GraphQL\Exception\InvalidArgument;
use function is_int;
use function trigger_error;
use const E_USER_WARNING;
/**
* Encapsulates warnings produced by the library.
*
@ -9,27 +17,29 @@ namespace GraphQL\Error;
*/
final class Warning
{
const WARNING_ASSIGN = 2;
const WARNING_CONFIG = 4;
const WARNING_FULL_SCHEMA_SCAN = 8;
const WARNING_CONFIG_DEPRECATION = 16;
const WARNING_NOT_A_TYPE = 32;
const ALL = 63;
public const WARNING_ASSIGN = 2;
public const WARNING_CONFIG = 4;
public const WARNING_FULL_SCHEMA_SCAN = 8;
public const WARNING_CONFIG_DEPRECATION = 16;
public const WARNING_NOT_A_TYPE = 32;
public const ALL = 63;
static $enableWarnings = self::ALL;
/** @var int */
private static $enableWarnings = self::ALL;
static $warned = [];
/** @var mixed[] */
private static $warned = [];
static private $warningHandler;
/** @var callable|null */
private static $warningHandler;
/**
* Sets warning handler which can intercept all system warnings.
* When not set, trigger_error() is used to notify about warnings.
*
* @api
* @param callable|null $warningHandler
*/
public static function setWarningHandler(callable $warningHandler = null)
public static function setWarningHandler(?callable $warningHandler = null) : void
{
self::$warningHandler = $warningHandler;
}
@ -42,18 +52,20 @@ final class Warning
*
* When passing true - suppresses all warnings.
*
* @api
* @param bool|int $suppress
*
* @api
*/
static function suppress($suppress = true)
public static function suppress($suppress = true) : void
{
if (true === $suppress) {
if ($suppress === true) {
self::$enableWarnings = 0;
} else if (false === $suppress) {
} elseif ($suppress === false) {
self::$enableWarnings = self::ALL;
} else {
$suppress = (int) $suppress;
} elseif (is_int($suppress)) {
self::$enableWarnings &= ~$suppress;
} else {
throw InvalidArgument::fromExpectedTypeAndArgument('bool|int', $suppress);
}
}
@ -65,38 +77,40 @@ final class Warning
*
* When passing true - re-enables all warnings.
*
* @api
* @param bool|int $enable
*
* @api
*/
public static function enable($enable = true)
public static function enable($enable = true) : void
{
if (true === $enable) {
if ($enable === true) {
self::$enableWarnings = self::ALL;
} else if (false === $enable) {
} elseif ($enable === false) {
self::$enableWarnings = 0;
} else {
$enable = (int) $enable;
} elseif (is_int($enable)) {
self::$enableWarnings |= $enable;
} else {
throw InvalidArgument::fromExpectedTypeAndArgument('bool|int', $enable);
}
}
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($errorMessage, $warningId);
} else if ((self::$enableWarnings & $warningId) > 0 && !isset(self::$warned[$warningId])) {
} elseif ((self::$enableWarnings & $warningId) > 0 && ! isset(self::$warned[$warningId])) {
self::$warned[$warningId] = true;
trigger_error($errorMessage, $messageLevel ?: E_USER_WARNING);
}
}
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($errorMessage, $warningId);
} else if ((self::$enableWarnings & $warningId) > 0) {
} elseif ((self::$enableWarnings & $warningId) > 0) {
trigger_error($errorMessage, $messageLevel ?: E_USER_WARNING);
}
}

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

@ -1,7 +1,11 @@
<?php
declare(strict_types=1);
namespace GraphQL\Executor;
use GraphQL\Error\Error;
use GraphQL\Executor\Promise\PromiseAdapter;
use GraphQL\Language\AST\FragmentDefinitionNode;
use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Type\Schema;
@ -10,78 +14,65 @@ use GraphQL\Type\Schema;
* Data that must be available at all points during query execution.
*
* 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
*/
class ExecutionContext
{
/**
* @var Schema
*/
/** @var Schema */
public $schema;
/**
* @var FragmentDefinitionNode[]
*/
/** @var FragmentDefinitionNode[] */
public $fragments;
/**
* @var mixed
*/
/** @var mixed */
public $rootValue;
/**
* @var mixed
*/
/** @var mixed */
public $contextValue;
/**
* @var OperationDefinitionNode
*/
/** @var OperationDefinitionNode */
public $operation;
/**
* @var array
*/
/** @var mixed[] */
public $variableValues;
/**
* @var callable
*/
/** @var callable */
public $fieldResolver;
/**
* @var array
*/
/** @var Error[] */
public $errors;
/** @var PromiseAdapter */
public $promiseAdapter;
public function __construct(
$schema,
$fragments,
$root,
$rootValue,
$contextValue,
$operation,
$variables,
$variableValues,
$errors,
$fieldResolver,
$promiseAdapter
)
{
$this->schema = $schema;
$this->fragments = $fragments;
$this->rootValue = $root;
$this->contextValue = $contextValue;
$this->operation = $operation;
$this->variableValues = $variables;
$this->errors = $errors ?: [];
$this->fieldResolver = $fieldResolver;
$this->promises = $promiseAdapter;
) {
$this->schema = $schema;
$this->fragments = $fragments;
$this->rootValue = $rootValue;
$this->contextValue = $contextValue;
$this->operation = $operation;
$this->variableValues = $variableValues;
$this->errors = $errors ?: [];
$this->fieldResolver = $fieldResolver;
$this->promiseAdapter = $promiseAdapter;
}
public function addError(Error $error)
{
$this->errors[] = $error;
return $this;
}
}

View file

@ -1,7 +1,13 @@
<?php
declare(strict_types=1);
namespace GraphQL\Executor;
use GraphQL\Error\Error;
use GraphQL\Error\FormattedError;
use JsonSerializable;
use function array_map;
/**
* Returned after [query execution](executing-queries.md).
@ -11,13 +17,13 @@ use GraphQL\Error\FormattedError;
* Could be converted to [spec-compliant](https://facebook.github.io/graphql/#sec-Response-Format)
* serializable array using `toArray()`
*/
class ExecutionResult implements \JsonSerializable
class ExecutionResult implements JsonSerializable
{
/**
* Data collected from resolvers during query execution
*
* @api
* @var array
* @var mixed[]
*/
public $data;
@ -28,7 +34,7 @@ class ExecutionResult implements \JsonSerializable
* contain original exception.
*
* @api
* @var \GraphQL\Error\Error[]
* @var Error[]
*/
public $errors;
@ -37,29 +43,25 @@ class ExecutionResult implements \JsonSerializable
* Conforms to
*
* @api
* @var array
* @var mixed[]
*/
public $extensions;
/**
* @var callable
*/
/** @var callable */
private $errorFormatter;
/**
* @var callable
*/
/** @var callable */
private $errorsHandler;
/**
* @param array $data
* @param array $errors
* @param array $extensions
* @param mixed[] $data
* @param Error[] $errors
* @param mixed[] $extensions
*/
public function __construct(array $data = null, array $errors = [], array $extensions = [])
public function __construct($data = null, array $errors = [], array $extensions = [])
{
$this->data = $data;
$this->errors = $errors;
$this->data = $data;
$this->errors = $errors;
$this->extensions = $extensions;
}
@ -76,13 +78,14 @@ class ExecutionResult implements \JsonSerializable
* // ... other keys
* );
*
* @return self
*
* @api
* @param callable $errorFormatter
* @return $this
*/
public function setErrorFormatter(callable $errorFormatter)
{
$this->errorFormatter = $errorFormatter;
return $this;
}
@ -96,16 +99,25 @@ class ExecutionResult implements \JsonSerializable
* return array_map($formatter, $errors);
* }
*
* @return self
*
* @api
* @param callable $handler
* @return $this
*/
public function setErrorsHandler(callable $handler)
{
$this->errorsHandler = $handler;
return $this;
}
/**
* @return mixed[]
*/
public function jsonSerialize()
{
return $this->toArray();
}
/**
* Converts GraphQL query result to spec-compliant serializable array using provided
* errors handler and formatter.
@ -116,42 +128,35 @@ class ExecutionResult implements \JsonSerializable
* $debug argument must be either bool (only adds "debugMessage" to result) or sum of flags from
* GraphQL\Error\Debug
*
* @api
* @param bool|int $debug
* @return array
*
* @return mixed[]
*
* @api
*/
public function toArray($debug = false)
{
$result = [];
if (!empty($this->errors)) {
$errorsHandler = $this->errorsHandler ?: function(array $errors, callable $formatter) {
if (! empty($this->errors)) {
$errorsHandler = $this->errorsHandler ?: static function (array $errors, callable $formatter) {
return array_map($formatter, $errors);
};
$result['errors'] = $errorsHandler(
$this->errors,
FormattedError::prepareFormatter($this->errorFormatter, $debug)
);
}
if (null !== $this->data) {
if ($this->data !== null) {
$result['data'] = $this->data;
}
if (!empty($this->extensions)) {
$result['extensions'] = (array) $this->extensions;
if (! empty($this->extensions)) {
$result['extensions'] = $this->extensions;
}
return $result;
}
/**
* Part of \JsonSerializable interface
*
* @return array
*/
public function jsonSerialize()
{
return $this->toArray();
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace GraphQL\Executor;
use GraphQL\Executor\Promise\Promise;
interface ExecutorImplementation
{
/**
* Returns promise of {@link ExecutionResult}. Promise should always resolve, never reject.
*/
public function doExecute() : Promise;
}

View file

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Executor\Promise\Adapter;
use GraphQL\Executor\Promise\Promise;
@ -6,6 +9,9 @@ use GraphQL\Executor\Promise\PromiseAdapter;
use GraphQL\Utils\Utils;
use React\Promise\Promise as ReactPromise;
use React\Promise\PromiseInterface as ReactPromiseInterface;
use function React\Promise\all;
use function React\Promise\reject;
use function React\Promise\resolve;
class ReactPromiseAdapter implements PromiseAdapter
{
@ -28,10 +34,11 @@ class ReactPromiseAdapter implements PromiseAdapter
/**
* @inheritdoc
*/
public function then(Promise $promise, callable $onFulfilled = null, callable $onRejected = null)
public function then(Promise $promise, ?callable $onFulfilled = null, ?callable $onRejected = null)
{
/** @var $adoptedPromise ReactPromiseInterface */
/** @var ReactPromiseInterface $adoptedPromise */
$adoptedPromise = $promise->adoptedPromise;
return new Promise($adoptedPromise->then($onFulfilled, $onRejected), $this);
}
@ -41,6 +48,7 @@ class ReactPromiseAdapter implements PromiseAdapter
public function create(callable $resolver)
{
$promise = new ReactPromise($resolver);
return new Promise($promise, $this);
}
@ -49,7 +57,8 @@ class ReactPromiseAdapter implements PromiseAdapter
*/
public function createFulfilled($value = null)
{
$promise = \React\Promise\resolve($value);
$promise = resolve($value);
return new Promise($promise, $this);
}
@ -58,7 +67,8 @@ class ReactPromiseAdapter implements PromiseAdapter
*/
public function createRejected($reason)
{
$promise = \React\Promise\reject($reason);
$promise = reject($reason);
return new Promise($promise, $this);
}
@ -68,11 +78,14 @@ class ReactPromiseAdapter implements PromiseAdapter
public function all(array $promisesOrValues)
{
// TODO: rework with generators when PHP minimum required version is changed to 5.5+
$promisesOrValues = Utils::map($promisesOrValues, function ($item) {
return $item instanceof Promise ? $item->adoptedPromise : $item;
});
$promisesOrValues = Utils::map(
$promisesOrValues,
static function ($item) {
return $item instanceof Promise ? $item->adoptedPromise : $item;
}
);
$promise = \React\Promise\all($promisesOrValues)->then(function($values) use ($promisesOrValues) {
$promise = all($promisesOrValues)->then(static function ($values) use ($promisesOrValues) {
$orderedResults = [];
foreach ($promisesOrValues as $key => $value) {
@ -81,6 +94,7 @@ class ReactPromiseAdapter implements PromiseAdapter
return $orderedResults;
});
return new Promise($promise, $this);
}
}

View file

@ -1,117 +1,164 @@
<?php
declare(strict_types=1);
namespace GraphQL\Executor\Promise\Adapter;
use Exception;
use GraphQL\Executor\ExecutionResult;
use GraphQL\Utils\Utils;
use SplQueue;
use Throwable;
use function is_object;
use function method_exists;
/**
* Class SyncPromise
*
* Simplistic (yet full-featured) implementation of Promises A+ spec for regular PHP `sync` mode
* (using queue to defer promises execution)
*
* @package GraphQL\Executor\Promise\Adapter
*/
class SyncPromise
{
const PENDING = 'pending';
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
const REJECTED = 'rejected';
/**
* @var \SplQueue
*/
/** @var SplQueue */
public static $queue;
public static function getQueue()
{
return self::$queue ?: self::$queue = new \SplQueue();
}
/** @var string */
public $state = self::PENDING;
public static function runQueue()
/** @var ExecutionResult|Throwable */
public $result;
/**
* Promises created in `then` method of this promise and awaiting for resolution of this promise
*
* @var mixed[][]
*/
private $waiting = [];
public static function runQueue() : void
{
$q = self::$queue;
while ($q && !$q->isEmpty()) {
while ($q !== null && ! $q->isEmpty()) {
$task = $q->dequeue();
$task();
}
}
public $state = self::PENDING;
public $result;
/**
* Promises created in `then` method of this promise and awaiting for resolution of this promise
* @var array
*/
private $waiting = [];
public function reject($reason)
{
if (!$reason instanceof \Exception && !$reason instanceof \Throwable) {
throw new \Exception('SyncPromise::reject() has to be called with an instance of \Throwable');
}
switch ($this->state) {
case self::PENDING:
$this->state = self::REJECTED;
$this->result = $reason;
$this->enqueueWaitingPromises();
break;
case self::REJECTED:
if ($reason !== $this->result) {
throw new \Exception("Cannot change rejection reason");
}
break;
case self::FULFILLED:
throw new \Exception("Cannot reject fulfilled promise");
}
return $this;
}
public function resolve($value)
public function resolve($value) : self
{
switch ($this->state) {
case self::PENDING:
if ($value === $this) {
throw new \Exception("Cannot resolve promise with self");
throw new Exception('Cannot resolve promise with self');
}
if (is_object($value) && method_exists($value, 'then')) {
$value->then(
function($resolvedValue) {
function ($resolvedValue) {
$this->resolve($resolvedValue);
},
function($reason) {
function ($reason) {
$this->reject($reason);
}
);
return $this;
}
$this->state = self::FULFILLED;
$this->state = self::FULFILLED;
$this->result = $value;
$this->enqueueWaitingPromises();
break;
case self::FULFILLED:
if ($this->result !== $value) {
throw new \Exception("Cannot change value of fulfilled promise");
throw new Exception('Cannot change value of fulfilled promise');
}
break;
case self::REJECTED:
throw new \Exception("Cannot resolve rejected promise");
throw new Exception('Cannot resolve rejected promise');
}
return $this;
}
public function then(callable $onFulfilled = null, callable $onRejected = null)
public function reject($reason) : self
{
if ($this->state === self::REJECTED && !$onRejected) {
if (! $reason instanceof Exception && ! $reason instanceof Throwable) {
throw new Exception('SyncPromise::reject() has to be called with an instance of \Throwable');
}
switch ($this->state) {
case self::PENDING:
$this->state = self::REJECTED;
$this->result = $reason;
$this->enqueueWaitingPromises();
break;
case self::REJECTED:
if ($reason !== $this->result) {
throw new Exception('Cannot change rejection reason');
}
break;
case self::FULFILLED:
throw new Exception('Cannot reject fulfilled promise');
}
return $this;
}
private function enqueueWaitingPromises() : void
{
Utils::invariant(
$this->state !== self::PENDING,
'Cannot enqueue derived promises when parent is still pending'
);
foreach ($this->waiting as $descriptor) {
self::getQueue()->enqueue(function () use ($descriptor) {
/** @var self $promise */
[$promise, $onFulfilled, $onRejected] = $descriptor;
if ($this->state === self::FULFILLED) {
try {
$promise->resolve($onFulfilled === null ? $this->result : $onFulfilled($this->result));
} catch (Exception $e) {
$promise->reject($e);
} catch (Throwable $e) {
$promise->reject($e);
}
} elseif ($this->state === self::REJECTED) {
try {
if ($onRejected === null) {
$promise->reject($this->result);
} else {
$promise->resolve($onRejected($this->result));
}
} catch (Exception $e) {
$promise->reject($e);
} catch (Throwable $e) {
$promise->reject($e);
}
}
});
}
$this->waiting = [];
}
public static function getQueue() : SplQueue
{
return self::$queue ?: self::$queue = new SplQueue();
}
public function then(?callable $onFulfilled = null, ?callable $onRejected = null)
{
if ($this->state === self::REJECTED && $onRejected === null) {
return $this;
}
if ($this->state === self::FULFILLED && !$onFulfilled) {
if ($this->state === self::FULFILLED && $onFulfilled === null) {
return $this;
}
$tmp = new self();
$tmp = new self();
$this->waiting[] = [$tmp, $onFulfilled, $onRejected];
if ($this->state !== self::PENDING) {
@ -120,39 +167,4 @@ class SyncPromise
return $tmp;
}
private function enqueueWaitingPromises()
{
Utils::invariant($this->state !== self::PENDING, 'Cannot enqueue derived promises when parent is still pending');
foreach ($this->waiting as $descriptor) {
self::getQueue()->enqueue(function () use ($descriptor) {
/** @var $promise self */
list($promise, $onFulfilled, $onRejected) = $descriptor;
if ($this->state === self::FULFILLED) {
try {
$promise->resolve($onFulfilled ? $onFulfilled($this->result) : $this->result);
} catch (\Exception $e) {
$promise->reject($e);
} catch (\Throwable $e) {
$promise->reject($e);
}
} else if ($this->state === self::REJECTED) {
try {
if ($onRejected) {
$promise->resolve($onRejected($this->result));
} else {
$promise->reject($this->result);
}
} catch (\Exception $e) {
$promise->reject($e);
} catch (\Throwable $e) {
$promise->reject($e);
}
}
});
}
$this->waiting = [];
}
}

View file

@ -1,19 +1,22 @@
<?php
declare(strict_types=1);
namespace GraphQL\Executor\Promise\Adapter;
use Exception;
use GraphQL\Deferred;
use GraphQL\Error\InvariantViolation;
use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Promise\Promise;
use GraphQL\Executor\Promise\PromiseAdapter;
use GraphQL\Utils\Utils;
use Throwable;
use function count;
/**
* Class SyncPromiseAdapter
*
* Allows changing order of field resolution even in sync environments
* (by leveraging queue of deferreds and promises)
*
* @package GraphQL\Executor\Promise\Adapter
*/
class SyncPromiseAdapter implements PromiseAdapter
{
@ -30,20 +33,22 @@ class SyncPromiseAdapter implements PromiseAdapter
*/
public function convertThenable($thenable)
{
if (!$thenable instanceof Deferred) {
if (! $thenable instanceof Deferred) {
throw new InvariantViolation('Expected instance of GraphQL\Deferred, got ' . Utils::printSafe($thenable));
}
return new Promise($thenable->promise, $this);
}
/**
* @inheritdoc
*/
public function then(Promise $promise, callable $onFulfilled = null, callable $onRejected = null)
public function then(Promise $promise, ?callable $onFulfilled = null, ?callable $onRejected = null)
{
/** @var SyncPromise $promise */
$promise = $promise->adoptedPromise;
return new Promise($promise->then($onFulfilled, $onRejected), $this);
/** @var SyncPromise $adoptedPromise */
$adoptedPromise = $promise->adoptedPromise;
return new Promise($adoptedPromise->then($onFulfilled, $onRejected), $this);
}
/**
@ -55,12 +60,18 @@ class SyncPromiseAdapter implements PromiseAdapter
try {
$resolver(
[$promise, 'resolve'],
[$promise, 'reject']
[
$promise,
'resolve',
],
[
$promise,
'reject',
]
);
} catch (\Exception $e) {
} catch (Exception $e) {
$promise->reject($e);
} catch (\Throwable $e) {
} catch (Throwable $e) {
$promise->reject($e);
}
@ -73,6 +84,7 @@ class SyncPromiseAdapter implements PromiseAdapter
public function createFulfilled($value = null)
{
$promise = new SyncPromise();
return new Promise($promise->resolve($value), $this);
}
@ -82,6 +94,7 @@ class SyncPromiseAdapter implements PromiseAdapter
public function createRejected($reason)
{
$promise = new SyncPromise();
return new Promise($promise->reject($reason), $this);
}
@ -92,20 +105,22 @@ class SyncPromiseAdapter implements PromiseAdapter
{
$all = new SyncPromise();
$total = count($promisesOrValues);
$count = 0;
$total = count($promisesOrValues);
$count = 0;
$result = [];
foreach ($promisesOrValues as $index => $promiseOrValue) {
if ($promiseOrValue instanceof Promise) {
$result[$index] = null;
$promiseOrValue->then(
function($value) use ($index, &$count, $total, &$result, $all) {
static function ($value) use ($index, &$count, $total, &$result, $all) {
$result[$index] = $value;
$count++;
if ($count >= $total) {
$all->resolve($result);
if ($count < $total) {
return;
}
$all->resolve($result);
},
[$all, 'reject']
);
@ -117,24 +132,23 @@ class SyncPromiseAdapter implements PromiseAdapter
if ($count === $total) {
$all->resolve($result);
}
return new Promise($all, $this);
}
/**
* Synchronously wait when promise completes
*
* @param Promise $promise
* @return mixed
* @return ExecutionResult
*/
public function wait(Promise $promise)
{
$this->beforeWait($promise);
$dfdQueue = Deferred::getQueue();
$dfdQueue = Deferred::getQueue();
$promiseQueue = SyncPromise::getQueue();
while (
$promise->adoptedPromise->state === SyncPromise::PENDING &&
!($dfdQueue->isEmpty() && $promiseQueue->isEmpty())
while ($promise->adoptedPromise->state === SyncPromise::PENDING &&
! ($dfdQueue->isEmpty() && $promiseQueue->isEmpty())
) {
Deferred::runQueue();
SyncPromise::runQueue();
@ -146,17 +160,17 @@ class SyncPromiseAdapter implements PromiseAdapter
if ($syncPromise->state === SyncPromise::FULFILLED) {
return $syncPromise->result;
} else if ($syncPromise->state === SyncPromise::REJECTED) {
}
if ($syncPromise->state === SyncPromise::REJECTED) {
throw $syncPromise->result;
}
throw new InvariantViolation("Could not resolve promise");
throw new InvariantViolation('Could not resolve promise');
}
/**
* Execute just before starting to run promise completion
*
* @param Promise $promise
*/
protected function beforeWait(Promise $promise)
{
@ -164,8 +178,6 @@ class SyncPromiseAdapter implements PromiseAdapter
/**
* Execute while running promise completion
*
* @param Promise $promise
*/
protected function onWait(Promise $promise)
{

View file

@ -1,38 +1,39 @@
<?php
declare(strict_types=1);
namespace GraphQL\Executor\Promise;
use GraphQL\Executor\Promise\Adapter\SyncPromise;
use GraphQL\Utils\Utils;
use React\Promise\Promise as ReactPromise;
/**
* Convenience wrapper for promises represented by Promise Adapter
*/
class Promise
{
private $adapter;
/** @var SyncPromise|ReactPromise */
public $adoptedPromise;
/** @var PromiseAdapter */
private $adapter;
/**
* Promise constructor.
*
* @param mixed $adoptedPromise
* @param PromiseAdapter $adapter
*/
public function __construct($adoptedPromise, PromiseAdapter $adapter)
{
Utils::invariant(!$adoptedPromise instanceof self, 'Expecting promise from adapted system, got ' . __CLASS__);
Utils::invariant(! $adoptedPromise instanceof self, 'Expecting promise from adapted system, got ' . self::class);
$this->adapter = $adapter;
$this->adapter = $adapter;
$this->adoptedPromise = $adoptedPromise;
}
/**
* @param callable|null $onFulfilled
* @param callable|null $onRejected
*
* @return Promise
*/
public function then(callable $onFulfilled = null, callable $onRejected = null)
public function then(?callable $onFulfilled = null, ?callable $onRejected = null)
{
return $this->adapter->then($this, $onFulfilled, $onRejected);
}

View file

@ -1,6 +1,11 @@
<?php
declare(strict_types=1);
namespace GraphQL\Executor\Promise;
use Throwable;
/**
* Provides a means for integration of async PHP platforms ([related docs](data-fetching.md#async-php))
*/
@ -9,18 +14,22 @@ interface PromiseAdapter
/**
* Return true if the value is a promise or a deferred of the underlying platform
*
* @api
* @param mixed $value
*
* @return bool
*
* @api
*/
public function isThenable($value);
/**
* Converts thenable of the underlying platform into GraphQL\Executor\Promise\Promise instance
*
* @api
* @param object $thenable
*
* @return Promise
*
* @api
*/
public function convertThenable($thenable);
@ -28,14 +37,11 @@ interface PromiseAdapter
* Accepts our Promise wrapper, extracts adopted promise out of it and executes actual `then` logic described
* in Promises/A+ specs. Then returns new wrapped instance of GraphQL\Executor\Promise\Promise.
*
* @api
* @param Promise $promise
* @param callable|null $onFulfilled
* @param callable|null $onRejected
*
* @return Promise
*
* @api
*/
public function then(Promise $promise, callable $onFulfilled = null, callable $onRejected = null);
public function then(Promise $promise, ?callable $onFulfilled = null, ?callable $onRejected = null);
/**
* Creates a Promise
@ -43,18 +49,20 @@ interface PromiseAdapter
* Expected resolver signature:
* function(callable $resolve, callable $reject)
*
* @api
* @param callable $resolver
* @return Promise
*
* @api
*/
public function create(callable $resolver);
/**
* Creates a fulfilled Promise for a value if the value is not a promise.
*
* @api
* @param mixed $value
*
* @return Promise
*
* @api
*/
public function createFulfilled($value = null);
@ -62,9 +70,11 @@ interface PromiseAdapter
* Creates a rejected promise for a reason if the reason is not a promise. If
* the provided reason is a promise, then it is returned as-is.
*
* @api
* @param \Throwable $reason
* @param Throwable $reason
*
* @return Promise
*
* @api
*/
public function createRejected($reason);
@ -72,9 +82,11 @@ interface PromiseAdapter
* Given an array of promises (or values), returns a promise that is fulfilled when all the
* items in the array are fulfilled.
*
* @api
* @param array $promisesOrValues Promises or values.
* @param Promise[]|mixed[] $promisesOrValues Promises or values.
*
* @return Promise
*
* @api
*/
public function all(array $promisesOrValues);
}

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Executor;
use GraphQL\Error\Error;
@ -9,20 +12,27 @@ use GraphQL\Language\AST\FieldDefinitionNode;
use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\FragmentSpreadNode;
use GraphQL\Language\AST\InlineFragmentNode;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeList;
use GraphQL\Language\AST\VariableNode;
use GraphQL\Language\AST\ValueNode;
use GraphQL\Language\AST\VariableDefinitionNode;
use GraphQL\Language\AST\VariableNode;
use GraphQL\Language\Printer;
use GraphQL\Type\Schema;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\FieldDefinition;
use GraphQL\Type\Definition\InputType;
use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use GraphQL\Utils\AST;
use GraphQL\Utils\TypeInfo;
use GraphQL\Utils\Utils;
use GraphQL\Utils\Value;
use stdClass;
use Throwable;
use function array_key_exists;
use function array_map;
use function sprintf;
class Values
{
@ -31,46 +41,36 @@ class Values
* variable definitions and arbitrary input. If the input cannot be coerced
* to match the variable definitions, a Error will be thrown.
*
* @param Schema $schema
* @param VariableDefinitionNode[] $varDefNodes
* @param array $inputs
* @return array
* @param mixed[] $inputs
*
* @return mixed[]
*/
public static function getVariableValues(Schema $schema, $varDefNodes, array $inputs)
{
$errors = [];
$errors = [];
$coercedValues = [];
foreach ($varDefNodes as $varDefNode) {
$varName = $varDefNode->variable->name->value;
/** @var InputType|Type $varType */
$varType = TypeInfo::typeFromAST($schema, $varDefNode->type);
if (!Type::isInputType($varType)) {
$errors[] = new Error(
"Variable \"\$$varName\" expected value of type " .
'"' . Printer::doPrint($varDefNode->type) . '" which cannot be used as an input type.',
[$varDefNode->type]
);
} else {
if (!array_key_exists($varName, $inputs)) {
if ($varType instanceof NonNull) {
$errors[] = new Error(
"Variable \"\$$varName\" of required type " .
"\"{$varType}\" was not provided.",
[$varDefNode]
);
} else if ($varDefNode->defaultValue) {
$coercedValues[$varName] = AST::valueFromAST($varDefNode->defaultValue, $varType);
}
} else {
$value = $inputs[$varName];
if (Type::isInputType($varType)) {
if (array_key_exists($varName, $inputs)) {
$value = $inputs[$varName];
$coerced = Value::coerceValue($value, $varType, $varDefNode);
/** @var Error[] $coercionErrors */
$coercionErrors = $coerced['errors'];
if ($coercionErrors) {
$messagePrelude = "Variable \"\$$varName\" got invalid value " . Utils::printSafeJson($value) . '; ';
if (empty($coercionErrors)) {
$coercedValues[$varName] = $coerced['value'];
} else {
$messagePrelude = sprintf(
'Variable "$%s" got invalid value %s; ',
$varName,
Utils::printSafeJson($value)
);
foreach($coercionErrors as $error) {
foreach ($coercionErrors as $error) {
$errors[] = new Error(
$messagePrelude . $error->getMessage(),
$error->getNodes(),
@ -81,90 +81,38 @@ class Values
$error->getExtensions()
);
}
} else {
$coercedValues[$varName] = $coerced['value'];
}
} else {
if ($varType instanceof NonNull) {
$errors[] = new Error(
sprintf(
'Variable "$%s" of required type "%s" was not provided.',
$varName,
$varType
),
[$varDefNode]
);
} elseif ($varDefNode->defaultValue !== null) {
$coercedValues[$varName] = AST::valueFromAST($varDefNode->defaultValue, $varType);
}
}
}
}
return ['errors' => $errors, 'coerced' => $errors ? null : $coercedValues];
}
/**
* Prepares an object map of argument values given a list of argument
* definitions and list of argument AST nodes.
*
* @param FieldDefinition|Directive $def
* @param FieldNode|\GraphQL\Language\AST\DirectiveNode $node
* @param $variableValues
* @return array
* @throws Error
*/
public static function getArgumentValues($def, $node, $variableValues = null)
{
$argDefs = $def->args;
$argNodes = $node->arguments;
if (!$argDefs || null === $argNodes) {
return [];
}
$coercedValues = [];
/** @var ArgumentNode[] $argNodeMap */
$argNodeMap = $argNodes ? Utils::keyMap($argNodes, function (ArgumentNode $arg) {
return $arg->name->value;
}) : [];
foreach ($argDefs as $argDef) {
$name = $argDef->name;
$argType = $argDef->getType();
$argumentNode = isset($argNodeMap[$name]) ? $argNodeMap[$name] : null;
if (!$argumentNode) {
if ($argDef->defaultValueExists()) {
$coercedValues[$name] = $argDef->defaultValue;
} else if ($argType instanceof NonNull) {
throw new Error(
'Argument "' . $name . '" of required type ' .
'"' . Utils::printSafe($argType) . '" was not provided.',
[$node]
);
}
} else if ($argumentNode->value instanceof VariableNode) {
$variableName = $argumentNode->value->name->value;
if ($variableValues && array_key_exists($variableName, $variableValues)) {
// Note: this does not check that this variable value is correct.
// This assumes that this query has been validated and the variable
// usage here is of the correct type.
$coercedValues[$name] = $variableValues[$variableName];
} else if ($argDef->defaultValueExists()) {
$coercedValues[$name] = $argDef->defaultValue;
} else if ($argType instanceof NonNull) {
throw new Error(
'Argument "' . $name . '" of required type "' . Utils::printSafe($argType) . '" was ' .
'provided the variable "$' . $variableName . '" which was not provided ' .
'a runtime value.',
[ $argumentNode->value ]
);
}
} else {
$valueNode = $argumentNode->value;
$coercedValue = AST::valueFromAST($valueNode, $argType, $variableValues);
if (Utils::isInvalid($coercedValue)) {
// Note: ValuesOfCorrectType validation should catch this before
// execution. This is a runtime check to ensure execution does not
// continue with an invalid argument value.
throw new Error(
'Argument "' . $name . '" has invalid value ' . Printer::doPrint($valueNode) . '.',
[ $argumentNode->value ]
);
}
$coercedValues[$name] = $coercedValue;
$errors[] = new Error(
sprintf(
'Variable "$%s" expected value of type "%s" which cannot be used as an input type.',
$varName,
Printer::doPrint($varDefNode->type)
),
[$varDefNode->type]
);
}
}
return $coercedValues;
if (! empty($errors)) {
return [$errors, null];
}
return [null, $coercedValues];
}
/**
@ -174,50 +122,158 @@ class Values
*
* If the directive does not exist on the node, returns undefined.
*
* @param Directive $directiveDef
* @param FragmentSpreadNode | FieldNode | InlineFragmentNode | EnumValueDefinitionNode | FieldDefinitionNode $node
* @param array|null $variableValues
* @param FragmentSpreadNode|FieldNode|InlineFragmentNode|EnumValueDefinitionNode|FieldDefinitionNode $node
* @param mixed[]|null $variableValues
*
* @return array|null
* @return mixed[]|null
*/
public static function getDirectiveValues(Directive $directiveDef, $node, $variableValues = null)
{
if (isset($node->directives) && $node->directives instanceof NodeList) {
$directiveNode = Utils::find($node->directives, function(DirectiveNode $directive) use ($directiveDef) {
return $directive->name->value === $directiveDef->name;
});
$directiveNode = Utils::find(
$node->directives,
static function (DirectiveNode $directive) use ($directiveDef) {
return $directive->name->value === $directiveDef->name;
}
);
if ($directiveNode) {
if ($directiveNode !== null) {
return self::getArgumentValues($directiveDef, $directiveNode, $variableValues);
}
}
return null;
}
/**
* Prepares an object map of argument values given a list of argument
* definitions and list of argument AST nodes.
*
* @param FieldDefinition|Directive $def
* @param FieldNode|DirectiveNode $node
* @param mixed[] $variableValues
*
* @return mixed[]
*
* @throws Error
*/
public static function getArgumentValues($def, $node, $variableValues = null)
{
if (empty($def->args)) {
return [];
}
$argumentNodes = $node->arguments;
if (empty($argumentNodes)) {
return [];
}
$argumentValueMap = [];
foreach ($argumentNodes as $argumentNode) {
$argumentValueMap[$argumentNode->name->value] = $argumentNode->value;
}
return static::getArgumentValuesForMap($def, $argumentValueMap, $variableValues, $node);
}
/**
* @param FieldDefinition|Directive $fieldDefinition
* @param ArgumentNode[] $argumentValueMap
* @param mixed[] $variableValues
* @param Node|null $referenceNode
*
* @return mixed[]
*
* @throws Error
*/
public static function getArgumentValuesForMap($fieldDefinition, $argumentValueMap, $variableValues = null, $referenceNode = null)
{
$argumentDefinitions = $fieldDefinition->args;
$coercedValues = [];
foreach ($argumentDefinitions as $argumentDefinition) {
$name = $argumentDefinition->name;
$argType = $argumentDefinition->getType();
$argumentValueNode = $argumentValueMap[$name] ?? null;
if ($argumentValueNode === null) {
if ($argumentDefinition->defaultValueExists()) {
$coercedValues[$name] = $argumentDefinition->defaultValue;
} elseif ($argType instanceof NonNull) {
throw new Error(
'Argument "' . $name . '" of required type ' .
'"' . Utils::printSafe($argType) . '" was not provided.',
$referenceNode
);
}
} elseif ($argumentValueNode instanceof VariableNode) {
$variableName = $argumentValueNode->name->value;
if ($variableValues !== null && array_key_exists($variableName, $variableValues)) {
// Note: this does not check that this variable value is correct.
// This assumes that this query has been validated and the variable
// usage here is of the correct type.
$coercedValues[$name] = $variableValues[$variableName];
} elseif ($argumentDefinition->defaultValueExists()) {
$coercedValues[$name] = $argumentDefinition->defaultValue;
} elseif ($argType instanceof NonNull) {
throw new Error(
'Argument "' . $name . '" of required type "' . Utils::printSafe($argType) . '" was ' .
'provided the variable "$' . $variableName . '" which was not provided ' .
'a runtime value.',
[$argumentValueNode]
);
}
} else {
$valueNode = $argumentValueNode;
$coercedValue = AST::valueFromAST($valueNode, $argType, $variableValues);
if (Utils::isInvalid($coercedValue)) {
// Note: ValuesOfCorrectType validation should catch this before
// execution. This is a runtime check to ensure execution does not
// continue with an invalid argument value.
throw new Error(
'Argument "' . $name . '" has invalid value ' . Printer::doPrint($valueNode) . '.',
[$argumentValueNode]
);
}
$coercedValues[$name] = $coercedValue;
}
}
return $coercedValues;
}
/**
* @deprecated as of 8.0 (Moved to \GraphQL\Utils\AST::valueFromAST)
*
* @param $valueNode
* @param InputType $type
* @param null $variables
* @return array|null|\stdClass
* @param ValueNode $valueNode
* @param mixed[]|null $variables
*
* @return mixed[]|stdClass|null
*/
public static function valueFromAST($valueNode, InputType $type, $variables = null)
public static function valueFromAST($valueNode, InputType $type, ?array $variables = null)
{
return AST::valueFromAST($valueNode, $type, $variables);
}
/**
* @deprecated as of 0.12 (Use coerceValue() directly for richer information)
* @param $value
* @param InputType $type
* @return array
*
* @param mixed[] $value
*
* @return string[]
*/
public static function isValidPHPValue($value, InputType $type)
{
$errors = Value::coerceValue($value, $type)['errors'];
return $errors
? array_map(function(/*\Throwable */$error) { return $error->getMessage(); }, $errors)
? array_map(
static function (Throwable $error) {
return $error->getMessage();
},
$errors
)
: [];
}
}

View file

@ -0,0 +1,283 @@
<?php
declare(strict_types=1);
namespace GraphQL\Experimental\Executor;
use Generator;
use GraphQL\Error\Error;
use GraphQL\Language\AST\DefinitionNode;
use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\FragmentDefinitionNode;
use GraphQL\Language\AST\FragmentSpreadNode;
use GraphQL\Language\AST\InlineFragmentNode;
use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\NodeKind;
use GraphQL\Language\AST\OperationDefinitionNode;
use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Language\AST\ValueNode;
use GraphQL\Type\Definition\AbstractType;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Introspection;
use GraphQL\Type\Schema;
use function sprintf;
/**
* @internal
*/
class Collector
{
/** @var Schema */
private $schema;
/** @var Runtime */
private $runtime;
/** @var OperationDefinitionNode|null */
public $operation = null;
/** @var FragmentDefinitionNode[] */
public $fragments = [];
/** @var ObjectType|null */
public $rootType;
/** @var FieldNode[][] */
private $fields;
/** @var string[] */
private $visitedFragments;
public function __construct(Schema $schema, Runtime $runtime)
{
$this->schema = $schema;
$this->runtime = $runtime;
}
public function initialize(DocumentNode $documentNode, ?string $operationName = null)
{
$hasMultipleAssumedOperations = false;
foreach ($documentNode->definitions as $definitionNode) {
/** @var DefinitionNode|Node $definitionNode */
if ($definitionNode instanceof OperationDefinitionNode) {
/** @var OperationDefinitionNode $definitionNode */
if ($operationName === null && $this->operation !== null) {
$hasMultipleAssumedOperations = true;
}
if ($operationName === null ||
(isset($definitionNode->name) && $definitionNode->name->value === $operationName)
) {
$this->operation = $definitionNode;
}
} elseif ($definitionNode instanceof FragmentDefinitionNode) {
/** @var FragmentDefinitionNode $definitionNode */
$this->fragments[$definitionNode->name->value] = $definitionNode;
}
}
if ($this->operation === null) {
if ($operationName !== null) {
$this->runtime->addError(new Error(sprintf('Unknown operation named "%s".', $operationName)));
} else {
$this->runtime->addError(new Error('Must provide an operation.'));
}
return;
}
if ($hasMultipleAssumedOperations) {
$this->runtime->addError(new Error('Must provide operation name if query contains multiple operations.'));
return;
}
if ($this->operation->operation === 'query') {
$this->rootType = $this->schema->getQueryType();
} elseif ($this->operation->operation === 'mutation') {
$this->rootType = $this->schema->getMutationType();
} elseif ($this->operation->operation === 'subscription') {
$this->rootType = $this->schema->getSubscriptionType();
} else {
$this->runtime->addError(new Error(sprintf('Cannot initialize collector with operation type "%s".', $this->operation->operation)));
}
}
/**
* @return Generator
*/
public function collectFields(ObjectType $runtimeType, ?SelectionSetNode $selectionSet)
{
$this->fields = [];
$this->visitedFragments = [];
$this->doCollectFields($runtimeType, $selectionSet);
foreach ($this->fields as $resultName => $fieldNodes) {
$fieldNode = $fieldNodes[0];
$fieldName = $fieldNode->name->value;
$argumentValueMap = null;
if (! empty($fieldNode->arguments)) {
foreach ($fieldNode->arguments as $argumentNode) {
$argumentValueMap = $argumentValueMap ?? [];
$argumentValueMap[$argumentNode->name->value] = $argumentNode->value;
}
}
if ($fieldName !== Introspection::TYPE_NAME_FIELD_NAME &&
! ($runtimeType === $this->schema->getQueryType() && ($fieldName === Introspection::SCHEMA_FIELD_NAME || $fieldName === Introspection::TYPE_FIELD_NAME)) &&
! $runtimeType->hasField($fieldName)
) {
// do not emit error
continue;
}
yield new CoroutineContextShared($fieldNodes, $fieldName, $resultName, $argumentValueMap);
}
}
private function doCollectFields(ObjectType $runtimeType, ?SelectionSetNode $selectionSet)
{
if ($selectionSet === null) {
return;
}
foreach ($selectionSet->selections as $selection) {
/** @var FieldNode|FragmentSpreadNode|InlineFragmentNode $selection */
if (! empty($selection->directives)) {
foreach ($selection->directives as $directiveNode) {
if ($directiveNode->name->value === Directive::SKIP_NAME) {
/** @var ValueNode|null $condition */
$condition = null;
foreach ($directiveNode->arguments as $argumentNode) {
if ($argumentNode->name->value === Directive::IF_ARGUMENT_NAME) {
$condition = $argumentNode->value;
break;
}
}
if ($condition === null) {
$this->runtime->addError(new Error(
sprintf('@%s directive is missing "%s" argument.', Directive::SKIP_NAME, Directive::IF_ARGUMENT_NAME),
$selection
));
} else {
if ($this->runtime->evaluate($condition, Type::boolean()) === true) {
continue 2; // !!! advances outer loop
}
}
} elseif ($directiveNode->name->value === Directive::INCLUDE_NAME) {
/** @var ValueNode|null $condition */
$condition = null;
foreach ($directiveNode->arguments as $argumentNode) {
if ($argumentNode->name->value === Directive::IF_ARGUMENT_NAME) {
$condition = $argumentNode->value;
break;
}
}
if ($condition === null) {
$this->runtime->addError(new Error(
sprintf('@%s directive is missing "%s" argument.', Directive::INCLUDE_NAME, Directive::IF_ARGUMENT_NAME),
$selection
));
} else {
if ($this->runtime->evaluate($condition, Type::boolean()) !== true) {
continue 2; // !!! advances outer loop
}
}
}
}
}
if ($selection instanceof FieldNode) {
/** @var FieldNode $selection */
$resultName = $selection->alias === null ? $selection->name->value : $selection->alias->value;
if (! isset($this->fields[$resultName])) {
$this->fields[$resultName] = [];
}
$this->fields[$resultName][] = $selection;
} elseif ($selection instanceof FragmentSpreadNode) {
/** @var FragmentSpreadNode $selection */
$fragmentName = $selection->name->value;
if (isset($this->visitedFragments[$fragmentName])) {
continue;
}
if (! isset($this->fragments[$fragmentName])) {
$this->runtime->addError(new Error(
sprintf('Fragment "%s" does not exist.', $fragmentName),
$selection
));
continue;
}
$this->visitedFragments[$fragmentName] = true;
$fragmentDefinition = $this->fragments[$fragmentName];
$conditionTypeName = $fragmentDefinition->typeCondition->name->value;
if (! $this->schema->hasType($conditionTypeName)) {
$this->runtime->addError(new Error(
sprintf('Cannot spread fragment "%s", type "%s" does not exist.', $fragmentName, $conditionTypeName),
$selection
));
continue;
}
$conditionType = $this->schema->getType($conditionTypeName);
if ($conditionType instanceof ObjectType) {
if ($runtimeType->name !== $conditionType->name) {
continue;
}
} elseif ($conditionType instanceof AbstractType) {
if (! $this->schema->isPossibleType($conditionType, $runtimeType)) {
continue;
}
}
$this->doCollectFields($runtimeType, $fragmentDefinition->selectionSet);
} elseif ($selection instanceof InlineFragmentNode) {
/** @var InlineFragmentNode $selection */
if ($selection->typeCondition !== null) {
$conditionTypeName = $selection->typeCondition->name->value;
if (! $this->schema->hasType($conditionTypeName)) {
$this->runtime->addError(new Error(
sprintf('Cannot spread inline fragment, type "%s" does not exist.', $conditionTypeName),
$selection
));
continue;
}
$conditionType = $this->schema->getType($conditionTypeName);
if ($conditionType instanceof ObjectType) {
if ($runtimeType->name !== $conditionType->name) {
continue;
}
} elseif ($conditionType instanceof AbstractType) {
if (! $this->schema->isPossibleType($conditionType, $runtimeType)) {
continue;
}
}
}
$this->doCollectFields($runtimeType, $selection->selectionSet);
}
}
}
}

View file

@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace GraphQL\Experimental\Executor;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ResolveInfo;
/**
* @internal
*/
class CoroutineContext
{
/** @var CoroutineContextShared */
public $shared;
/** @var ObjectType */
public $type;
/** @var mixed */
public $value;
/** @var object */
public $result;
/** @var string[] */
public $path;
/** @var ResolveInfo|null */
public $resolveInfo;
/** @var string[]|null */
public $nullFence;
/**
* @param mixed $value
* @param object $result
* @param string[] $path
* @param string[]|null $nullFence
*/
public function __construct(
CoroutineContextShared $shared,
ObjectType $type,
$value,
$result,
array $path,
?array $nullFence = null
) {
$this->shared = $shared;
$this->type = $type;
$this->value = $value;
$this->result = $result;
$this->path = $path;
$this->nullFence = $nullFence;
}
}

View file

@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace GraphQL\Experimental\Executor;
use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Language\AST\ValueNode;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ResolveInfo;
/**
* @internal
*/
class CoroutineContextShared
{
/** @var FieldNode[] */
public $fieldNodes;
/** @var string */
public $fieldName;
/** @var string */
public $resultName;
/** @var ValueNode[]|null */
public $argumentValueMap;
/** @var SelectionSetNode|null */
public $mergedSelectionSet;
/** @var ObjectType|null */
public $typeGuard1;
/** @var callable|null */
public $resolveIfType1;
/** @var mixed */
public $argumentsIfType1;
/** @var ResolveInfo|null */
public $resolveInfoIfType1;
/** @var ObjectType|null */
public $typeGuard2;
/** @var CoroutineContext[]|null */
public $childContextsIfType2;
/**
* @param FieldNode[] $fieldNodes
* @param mixed[]|null $argumentValueMap
*/
public function __construct(array $fieldNodes, string $fieldName, string $resultName, ?array $argumentValueMap)
{
$this->fieldNodes = $fieldNodes;
$this->fieldName = $fieldName;
$this->resultName = $resultName;
$this->argumentValueMap = $argumentValueMap;
}
}

View file

@ -0,0 +1,952 @@
<?php
declare(strict_types=1);
namespace GraphQL\Experimental\Executor;
use Generator;
use GraphQL\Error\Error;
use GraphQL\Error\InvariantViolation;
use GraphQL\Error\Warning;
use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\ExecutorImplementation;
use GraphQL\Executor\Promise\Promise;
use GraphQL\Executor\Promise\PromiseAdapter;
use GraphQL\Executor\Values;
use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\AST\SelectionSetNode;
use GraphQL\Language\AST\ValueNode;
use GraphQL\Type\Definition\AbstractType;
use GraphQL\Type\Definition\CompositeType;
use GraphQL\Type\Definition\InputType;
use GraphQL\Type\Definition\InterfaceType;
use GraphQL\Type\Definition\LeafType;
use GraphQL\Type\Definition\ListOfType;
use GraphQL\Type\Definition\NonNull;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\UnionType;
use GraphQL\Type\Introspection;
use GraphQL\Type\Schema;
use GraphQL\Utils\AST;
use GraphQL\Utils\Utils;
use SplQueue;
use stdClass;
use Throwable;
use function is_array;
use function is_string;
use function sprintf;
class CoroutineExecutor implements Runtime, ExecutorImplementation
{
/** @var object */
private static $undefined;
/** @var Schema */
private $schema;
/** @var callable */
private $fieldResolver;
/** @var PromiseAdapter */
private $promiseAdapter;
/** @var mixed|null */
private $rootValue;
/** @var mixed|null */
private $contextValue;
/** @var mixed|null */
private $rawVariableValues;
/** @var mixed|null */
private $variableValues;
/** @var DocumentNode */
private $documentNode;
/** @var string|null */
private $operationName;
/** @var Collector */
private $collector;
/** @var Error[] */
private $errors;
/** @var SplQueue */
private $queue;
/** @var SplQueue */
private $schedule;
/** @var stdClass */
private $rootResult;
/** @var int */
private $pending;
/** @var callable */
private $doResolve;
public function __construct(
PromiseAdapter $promiseAdapter,
Schema $schema,
DocumentNode $documentNode,
$rootValue,
$contextValue,
$rawVariableValues,
?string $operationName,
callable $fieldResolver
) {
if (self::$undefined === null) {
self::$undefined = Utils::undefined();
}
$this->schema = $schema;
$this->fieldResolver = $fieldResolver;
$this->promiseAdapter = $promiseAdapter;
$this->rootValue = $rootValue;
$this->contextValue = $contextValue;
$this->rawVariableValues = $rawVariableValues;
$this->documentNode = $documentNode;
$this->operationName = $operationName;
}
public static function create(
PromiseAdapter $promiseAdapter,
Schema $schema,
DocumentNode $documentNode,
$rootValue,
$contextValue,
$variableValues,
?string $operationName,
callable $fieldResolver
) {
return new static(
$promiseAdapter,
$schema,
$documentNode,
$rootValue,
$contextValue,
$variableValues,
$operationName,
$fieldResolver
);
}
private static function resultToArray($value, $emptyObjectAsStdClass = true)
{
if ($value instanceof stdClass) {
$array = [];
foreach ($value as $propertyName => $propertyValue) {
$array[$propertyName] = self::resultToArray($propertyValue);
}
if ($emptyObjectAsStdClass && empty($array)) {
return new stdClass();
}
return $array;
}
if (is_array($value)) {
$array = [];
foreach ($value as $key => $item) {
$array[$key] = self::resultToArray($item);
}
return $array;
}
return $value;
}
public function doExecute() : Promise
{
$this->rootResult = new stdClass();
$this->errors = [];
$this->queue = new SplQueue();
$this->schedule = new SplQueue();
$this->pending = 0;
$this->collector = new Collector($this->schema, $this);
$this->collector->initialize($this->documentNode, $this->operationName);
if (! empty($this->errors)) {
return $this->promiseAdapter->createFulfilled($this->finishExecute(null, $this->errors));
}
[$errors, $coercedVariableValues] = Values::getVariableValues(
$this->schema,
$this->collector->operation->variableDefinitions ?: [],
$this->rawVariableValues ?: []
);
if (! empty($errors)) {
return $this->promiseAdapter->createFulfilled($this->finishExecute(null, $errors));
}
$this->variableValues = $coercedVariableValues;
foreach ($this->collector->collectFields($this->collector->rootType, $this->collector->operation->selectionSet) as $shared) {
/** @var CoroutineContextShared $shared */
// !!! assign to keep object keys sorted
$this->rootResult->{$shared->resultName} = null;
$ctx = new CoroutineContext(
$shared,
$this->collector->rootType,
$this->rootValue,
$this->rootResult,
[$shared->resultName]
);
$fieldDefinition = $this->findFieldDefinition($ctx);
if (! $fieldDefinition->getType() instanceof NonNull) {
$ctx->nullFence = [$shared->resultName];
}
if ($this->collector->operation->operation === 'mutation' && ! $this->queue->isEmpty()) {
$this->schedule->enqueue($ctx);
} else {
$this->queue->enqueue(new Strand($this->spawn($ctx)));
}
}
$this->run();
if ($this->pending > 0) {
return $this->promiseAdapter->create(function (callable $resolve) {
$this->doResolve = $resolve;
});
}
return $this->promiseAdapter->createFulfilled($this->finishExecute($this->rootResult, $this->errors));
}
/**
* @param object|null $value
* @param Error[] $errors
*/
private function finishExecute($value, array $errors) : ExecutionResult
{
$this->rootResult = null;
$this->errors = null;
$this->queue = null;
$this->schedule = null;
$this->pending = null;
$this->collector = null;
$this->variableValues = null;
if ($value !== null) {
$value = self::resultToArray($value, false);
}
return new ExecutionResult($value, $errors);
}
/**
* @internal
*/
public function evaluate(ValueNode $valueNode, InputType $type)
{
return AST::valueFromAST($valueNode, $type, $this->variableValues);
}
/**
* @internal
*/
public function addError($error)
{
$this->errors[] = $error;
}
private function run()
{
RUN:
while (! $this->queue->isEmpty()) {
/** @var Strand $strand */
$strand = $this->queue->dequeue();
try {
if ($strand->success !== null) {
RESUME:
if ($strand->success) {
$strand->current->send($strand->value);
} else {
$strand->current->throw($strand->value);
}
$strand->success = null;
$strand->value = null;
}
START:
if ($strand->current->valid()) {
$value = $strand->current->current();
if ($value instanceof Generator) {
$strand->stack[$strand->depth++] = $strand->current;
$strand->current = $value;
goto START;
} elseif ($this->isPromise($value)) {
// !!! increment pending before calling ->then() as it may invoke the callback right away
++$this->pending;
if (! $value instanceof Promise) {
$value = $this->promiseAdapter->convertThenable($value);
}
$this->promiseAdapter
->then(
$value,
function ($value) use ($strand) {
$strand->success = true;
$strand->value = $value;
$this->queue->enqueue($strand);
$this->done();
},
function (Throwable $throwable) use ($strand) {
$strand->success = false;
$strand->value = $throwable;
$this->queue->enqueue($strand);
$this->done();
}
);
continue;
} else {
$strand->success = true;
$strand->value = $value;
goto RESUME;
}
}
$strand->success = true;
$strand->value = $strand->current->getReturn();
} catch (Throwable $reason) {
$strand->success = false;
$strand->value = $reason;
}
if ($strand->depth <= 0) {
continue;
}
$current = &$strand->stack[--$strand->depth];
$strand->current = $current;
$current = null;
goto RESUME;
}
if ($this->pending > 0 || $this->schedule->isEmpty()) {
return;
}
/** @var CoroutineContext $ctx */
$ctx = $this->schedule->dequeue();
$this->queue->enqueue(new Strand($this->spawn($ctx)));
goto RUN;
}
private function done()
{
--$this->pending;
$this->run();
if ($this->pending > 0) {
return;
}
$doResolve = $this->doResolve;
$doResolve($this->finishExecute($this->rootResult, $this->errors));
}
private function spawn(CoroutineContext $ctx)
{
// short-circuit evaluation for __typename
if ($ctx->shared->fieldName === Introspection::TYPE_NAME_FIELD_NAME) {
$ctx->result->{$ctx->shared->resultName} = $ctx->type->name;
return;
}
try {
if ($ctx->shared->typeGuard1 === $ctx->type) {
$resolve = $ctx->shared->resolveIfType1;
$ctx->resolveInfo = clone $ctx->shared->resolveInfoIfType1;
$ctx->resolveInfo->path = $ctx->path;
$arguments = $ctx->shared->argumentsIfType1;
$returnType = $ctx->resolveInfo->returnType;
} else {
$fieldDefinition = $this->findFieldDefinition($ctx);
if ($fieldDefinition->resolveFn !== null) {
$resolve = $fieldDefinition->resolveFn;
} elseif ($ctx->type->resolveFieldFn !== null) {
$resolve = $ctx->type->resolveFieldFn;
} else {
$resolve = $this->fieldResolver;
}
$returnType = $fieldDefinition->getType();
$ctx->resolveInfo = new ResolveInfo(
$ctx->shared->fieldName,
$ctx->shared->fieldNodes,
$returnType,
$ctx->type,
$ctx->path,
$this->schema,
$this->collector->fragments,
$this->rootValue,
$this->collector->operation,
$this->variableValues
);
$arguments = Values::getArgumentValuesForMap(
$fieldDefinition,
$ctx->shared->argumentValueMap,
$this->variableValues
);
// !!! assign only in batch when no exception can be thrown in-between
$ctx->shared->typeGuard1 = $ctx->type;
$ctx->shared->resolveIfType1 = $resolve;
$ctx->shared->argumentsIfType1 = $arguments;
$ctx->shared->resolveInfoIfType1 = $ctx->resolveInfo;
}
$value = $resolve($ctx->value, $arguments, $this->contextValue, $ctx->resolveInfo);
if (! $this->completeValueFast($ctx, $returnType, $value, $ctx->path, $returnValue)) {
$returnValue = yield $this->completeValue(
$ctx,
$returnType,
$value,
$ctx->path,
$ctx->nullFence
);
}
} catch (Throwable $reason) {
$this->addError(Error::createLocatedError(
$reason,
$ctx->shared->fieldNodes,
$ctx->path
));
$returnValue = self::$undefined;
}
if ($returnValue !== self::$undefined) {
$ctx->result->{$ctx->shared->resultName} = $returnValue;
} elseif ($ctx->resolveInfo !== null && $ctx->resolveInfo->returnType instanceof NonNull) { // !!! $ctx->resolveInfo might not have been initialized yet
$result =& $this->rootResult;
foreach ($ctx->nullFence ?? [] as $key) {
if (is_string($key)) {
$result =& $result->{$key};
} else {
$result =& $result[$key];
}
}
$result = null;
}
}
private function findFieldDefinition(CoroutineContext $ctx)
{
if ($ctx->shared->fieldName === Introspection::SCHEMA_FIELD_NAME && $ctx->type === $this->schema->getQueryType()) {
return Introspection::schemaMetaFieldDef();
}
if ($ctx->shared->fieldName === Introspection::TYPE_FIELD_NAME && $ctx->type === $this->schema->getQueryType()) {
return Introspection::typeMetaFieldDef();
}
if ($ctx->shared->fieldName === Introspection::TYPE_NAME_FIELD_NAME) {
return Introspection::typeNameMetaFieldDef();
}
return $ctx->type->getField($ctx->shared->fieldName);
}
/**
* @param mixed $value
* @param string[] $path
* @param mixed $returnValue
*/
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
if ($this->isPromise($value) || $value instanceof Throwable) {
return false;
}
$nonNull = false;
if ($type instanceof NonNull) {
$nonNull = true;
$type = $type->getWrappedType();
}
if (! $type instanceof LeafType) {
return false;
}
if ($type !== $this->schema->getType($type->name)) {
$hint = '';
if ($this->schema->getConfig()->typeLoader !== null) {
$hint = sprintf(
'Make sure that type loader returns the same instance as defined in %s.%s',
$ctx->type,
$ctx->shared->fieldName
);
}
$this->addError(Error::createLocatedError(
new InvariantViolation(
sprintf(
'Schema must contain unique named types but contains multiple types named "%s". %s ' .
'(see http://webonyx.github.io/graphql-php/type-system/#type-registry).',
$type->name,
$hint
)
),
$ctx->shared->fieldNodes,
$path
));
$value = null;
}
if ($value === null) {
$returnValue = null;
} else {
try {
$returnValue = $type->serialize($value);
} catch (Throwable $error) {
$this->addError(Error::createLocatedError(
new InvariantViolation(
'Expected a value of type "' . Utils::printSafe($type) . '" but received: ' . Utils::printSafe($value),
0,
$error
),
$ctx->shared->fieldNodes,
$path
));
$returnValue = null;
}
}
if ($nonNull && $returnValue === null) {
$this->addError(Error::createLocatedError(
new InvariantViolation(sprintf(
'Cannot return null for non-nullable field %s.%s.',
$ctx->type->name,
$ctx->shared->fieldName
)),
$ctx->shared->fieldNodes,
$path
));
$returnValue = self::$undefined;
}
return true;
}
/**
* @param mixed $value
* @param string[] $path
* @param string[]|null $nullFence
*
* @return mixed
*/
private function completeValue(CoroutineContext $ctx, Type $type, $value, array $path, ?array $nullFence)
{
$nonNull = false;
$returnValue = null;
if ($type instanceof NonNull) {
$nonNull = true;
$type = $type->getWrappedType();
} else {
$nullFence = $path;
}
// !!! $value might be promise, yield to resolve
try {
if ($this->isPromise($value)) {
$value = yield $value;
}
} catch (Throwable $reason) {
$this->addError(Error::createLocatedError(
$reason,
$ctx->shared->fieldNodes,
$path
));
if ($nonNull) {
$returnValue = self::$undefined;
} else {
$returnValue = null;
}
goto CHECKED_RETURN;
}
if ($value === null) {
$returnValue = $value;
goto CHECKED_RETURN;
} elseif ($value instanceof Throwable) {
// special handling of Throwable inherited from JS reference implementation, but makes no sense in this PHP
$this->addError(Error::createLocatedError(
$value,
$ctx->shared->fieldNodes,
$path
));
if ($nonNull) {
$returnValue = self::$undefined;
} else {
$returnValue = null;
}
goto CHECKED_RETURN;
}
if ($type instanceof ListOfType) {
$returnValue = [];
$index = -1;
$itemType = $type->getWrappedType();
foreach ($value as $itemValue) {
++$index;
$itemPath = $path;
$itemPath[] = $index; // !!! use arrays COW semantics
$ctx->resolveInfo->path = $itemPath;
try {
if (! $this->completeValueFast($ctx, $itemType, $itemValue, $itemPath, $itemReturnValue)) {
$itemReturnValue = yield $this->completeValue($ctx, $itemType, $itemValue, $itemPath, $nullFence);
}
} catch (Throwable $reason) {
$this->addError(Error::createLocatedError(
$reason,
$ctx->shared->fieldNodes,
$itemPath
));
$itemReturnValue = null;
}
if ($itemReturnValue === self::$undefined) {
$returnValue = self::$undefined;
goto CHECKED_RETURN;
}
$returnValue[$index] = $itemReturnValue;
}
goto CHECKED_RETURN;
} else {
if ($type !== $this->schema->getType($type->name)) {
$hint = '';
if ($this->schema->getConfig()->typeLoader !== null) {
$hint = sprintf(
'Make sure that type loader returns the same instance as defined in %s.%s',
$ctx->type,
$ctx->shared->fieldName
);
}
$this->addError(Error::createLocatedError(
new InvariantViolation(
sprintf(
'Schema must contain unique named types but contains multiple types named "%s". %s ' .
'(see http://webonyx.github.io/graphql-php/type-system/#type-registry).',
$type->name,
$hint
)
),
$ctx->shared->fieldNodes,
$path
));
$returnValue = null;
goto CHECKED_RETURN;
}
if ($type instanceof LeafType) {
try {
$returnValue = $type->serialize($value);
} catch (Throwable $error) {
$this->addError(Error::createLocatedError(
new InvariantViolation(
'Expected a value of type "' . Utils::printSafe($type) . '" but received: ' . Utils::printSafe($value),
0,
$error
),
$ctx->shared->fieldNodes,
$path
));
$returnValue = null;
}
goto CHECKED_RETURN;
} elseif ($type instanceof CompositeType) {
/** @var ObjectType|null $objectType */
$objectType = null;
if ($type instanceof InterfaceType || $type instanceof UnionType) {
$objectType = $type->resolveType($value, $this->contextValue, $ctx->resolveInfo);
if ($objectType === null) {
$objectType = yield $this->resolveTypeSlow($ctx, $value, $type);
}
// !!! $objectType->resolveType() might return promise, yield to resolve
$objectType = yield $objectType;
if (is_string($objectType)) {
$objectType = $this->schema->getType($objectType);
}
if ($objectType === null) {
$this->addError(Error::createLocatedError(
sprintf(
'Composite type "%s" did not resolve concrete object type for value: %s.',
$type->name,
Utils::printSafe($value)
),
$ctx->shared->fieldNodes,
$path
));
$returnValue = self::$undefined;
goto CHECKED_RETURN;
} elseif (! $objectType instanceof ObjectType) {
$this->addError(Error::createLocatedError(
new InvariantViolation(sprintf(
'Abstract type %s must resolve to an Object type at ' .
'runtime for field %s.%s with value "%s", received "%s". ' .
'Either the %s type should provide a "resolveType" ' .
'function or each possible type should provide an "isTypeOf" function.',
$type,
$ctx->resolveInfo->parentType,
$ctx->resolveInfo->fieldName,
Utils::printSafe($value),
Utils::printSafe($objectType),
$type
)),
$ctx->shared->fieldNodes,
$path
));
$returnValue = null;
goto CHECKED_RETURN;
} elseif (! $this->schema->isPossibleType($type, $objectType)) {
$this->addError(Error::createLocatedError(
new InvariantViolation(sprintf(
'Runtime Object type "%s" is not a possible type for "%s".',
$objectType,
$type
)),
$ctx->shared->fieldNodes,
$path
));
$returnValue = null;
goto CHECKED_RETURN;
} elseif ($objectType !== $this->schema->getType($objectType->name)) {
$this->addError(Error::createLocatedError(
new InvariantViolation(
sprintf(
'Schema must contain unique named types but contains multiple types named "%s". ' .
'Make sure that `resolveType` function of abstract type "%s" returns the same ' .
'type instance as referenced anywhere else within the schema ' .
'(see http://webonyx.github.io/graphql-php/type-system/#type-registry).',
$objectType,
$type
)
),
$ctx->shared->fieldNodes,
$path
));
$returnValue = null;
goto CHECKED_RETURN;
}
} elseif ($type instanceof ObjectType) {
$objectType = $type;
} else {
$this->addError(Error::createLocatedError(
sprintf(
'Unexpected field type "%s".',
Utils::printSafe($type)
),
$ctx->shared->fieldNodes,
$path
));
$returnValue = self::$undefined;
goto CHECKED_RETURN;
}
$typeCheck = $objectType->isTypeOf($value, $this->contextValue, $ctx->resolveInfo);
if ($typeCheck !== null) {
// !!! $objectType->isTypeOf() might return promise, yield to resolve
$typeCheck = yield $typeCheck;
if (! $typeCheck) {
$this->addError(Error::createLocatedError(
sprintf('Expected value of type "%s" but got: %s.', $type->name, Utils::printSafe($value)),
$ctx->shared->fieldNodes,
$path
));
$returnValue = null;
goto CHECKED_RETURN;
}
}
$returnValue = new stdClass();
if ($ctx->shared->typeGuard2 === $objectType) {
foreach ($ctx->shared->childContextsIfType2 as $childCtx) {
$childCtx = clone $childCtx;
$childCtx->type = $objectType;
$childCtx->value = $value;
$childCtx->result = $returnValue;
$childCtx->path = $path;
$childCtx->path[] = $childCtx->shared->resultName; // !!! uses array COW semantics
$childCtx->nullFence = $nullFence;
$childCtx->resolveInfo = null;
$this->queue->enqueue(new Strand($this->spawn($childCtx)));
// !!! assign null to keep object keys sorted
$returnValue->{$childCtx->shared->resultName} = null;
}
} else {
$childContexts = [];
foreach ($this->collector->collectFields(
$objectType,
$ctx->shared->mergedSelectionSet ?? $this->mergeSelectionSets($ctx)
) as $childShared) {
/** @var CoroutineContextShared $childShared */
$childPath = $path;
$childPath[] = $childShared->resultName; // !!! uses array COW semantics
$childCtx = new CoroutineContext(
$childShared,
$objectType,
$value,
$returnValue,
$childPath,
$nullFence
);
$childContexts[] = $childCtx;
$this->queue->enqueue(new Strand($this->spawn($childCtx)));
// !!! assign null to keep object keys sorted
$returnValue->{$childShared->resultName} = null;
}
$ctx->shared->typeGuard2 = $objectType;
$ctx->shared->childContextsIfType2 = $childContexts;
}
goto CHECKED_RETURN;
} else {
$this->addError(Error::createLocatedError(
sprintf('Unhandled type "%s".', Utils::printSafe($type)),
$ctx->shared->fieldNodes,
$path
));
$returnValue = null;
goto CHECKED_RETURN;
}
}
CHECKED_RETURN:
if ($nonNull && $returnValue === null) {
$this->addError(Error::createLocatedError(
new InvariantViolation(sprintf(
'Cannot return null for non-nullable field %s.%s.',
$ctx->type->name,
$ctx->shared->fieldName
)),
$ctx->shared->fieldNodes,
$path
));
return self::$undefined;
}
return $returnValue;
}
private function mergeSelectionSets(CoroutineContext $ctx)
{
$selections = [];
foreach ($ctx->shared->fieldNodes as $fieldNode) {
if ($fieldNode->selectionSet === null) {
continue;
}
foreach ($fieldNode->selectionSet->selections as $selection) {
$selections[] = $selection;
}
}
return $ctx->shared->mergedSelectionSet = new SelectionSetNode(['selections' => $selections]);
}
private function resolveTypeSlow(CoroutineContext $ctx, $value, AbstractType $abstractType)
{
if ($value !== null &&
is_array($value) &&
isset($value['__typename']) &&
is_string($value['__typename'])
) {
return $this->schema->getType($value['__typename']);
}
if ($abstractType instanceof InterfaceType && $this->schema->getConfig()->typeLoader !== null) {
Warning::warnOnce(
sprintf(
'GraphQL Interface Type `%s` returned `null` from its `resolveType` function ' .
'for value: %s. Switching to slow resolution method using `isTypeOf` ' .
'of all possible implementations. It requires full schema scan and degrades query performance significantly. ' .
' Make sure your `resolveType` always returns valid implementation or throws.',
$abstractType->name,
Utils::printSafe($value)
),
Warning::WARNING_FULL_SCHEMA_SCAN
);
}
$possibleTypes = $this->schema->getPossibleTypes($abstractType);
// to be backward-compatible with old executor, ->isTypeOf() is called for all possible types,
// it cannot short-circuit when the match is found
$selectedType = null;
foreach ($possibleTypes as $type) {
$typeCheck = yield $type->isTypeOf($value, $this->contextValue, $ctx->resolveInfo);
if ($selectedType !== null || $typeCheck !== true) {
continue;
}
$selectedType = $type;
}
return $selectedType;
}
/**
* @param mixed $value
*
* @return bool
*/
private function isPromise($value)
{
return $value instanceof Promise || $this->promiseAdapter->isThenable($value);
}
}

View file

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace GraphQL\Experimental\Executor;
use GraphQL\Language\AST\ValueNode;
use GraphQL\Type\Definition\InputType;
/**
* @internal
*/
interface Runtime
{
public function evaluate(ValueNode $valueNode, InputType $type);
public function addError($error);
}

View file

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace GraphQL\Experimental\Executor;
use Generator;
/**
* @internal
*/
class Strand
{
/** @var Generator */
public $current;
/** @var Generator[] */
public $stack;
/** @var int */
public $depth;
/** @var bool|null */
public $success;
/** @var mixed */
public $value;
public function __construct(Generator $coroutine)
{
$this->current = $coroutine;
$this->stack = [];
$this->depth = 0;
}
}

View file

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL;
use GraphQL\Error\Error;
@ -6,15 +9,21 @@ use GraphQL\Executor\ExecutionResult;
use GraphQL\Executor\Executor;
use GraphQL\Executor\Promise\Adapter\SyncPromiseAdapter;
use GraphQL\Executor\Promise\Promise;
use GraphQL\Executor\Promise\PromiseAdapter;
use GraphQL\Executor\ReferenceExecutor;
use GraphQL\Experimental\Executor\CoroutineExecutor;
use GraphQL\Language\AST\DocumentNode;
use GraphQL\Language\Parser;
use GraphQL\Language\Source;
use GraphQL\Executor\Promise\PromiseAdapter;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema as SchemaType;
use GraphQL\Validator\DocumentValidator;
use GraphQL\Validator\Rules\AbstractValidationRule;
use GraphQL\Validator\Rules\QueryComplexity;
use GraphQL\Validator\Rules\ValidationRule;
use function array_values;
use function trigger_error;
use const E_USER_DEPRECATED;
/**
* This is the primary facade for fulfilling GraphQL operations.
@ -57,33 +66,37 @@ class GraphQL
* Empty array would allow to skip query validation (may be convenient for persisted
* queries which are validated before persisting and assumed valid during execution)
*
* @api
* @param \GraphQL\Type\Schema $schema
* @param string|DocumentNode $source
* @param mixed $rootValue
* @param mixed $context
* @param array|null $variableValues
* @param string|null $operationName
* @param callable $fieldResolver
* @param array $validationRules
* @param mixed $rootValue
* @param mixed $context
* @param mixed[]|null $variableValues
* @param ValidationRule[] $validationRules
*
* @return ExecutionResult
* @api
*/
public static function executeQuery(
\GraphQL\Type\Schema $schema,
SchemaType $schema,
$source,
$rootValue = null,
$context = null,
$variableValues = null,
$operationName = null,
callable $fieldResolver = null,
array $validationRules = null
)
{
?string $operationName = null,
?callable $fieldResolver = null,
?array $validationRules = null
) : ExecutionResult {
$promiseAdapter = new SyncPromiseAdapter();
$promise = self::promiseToExecute($promiseAdapter, $schema, $source, $rootValue, $context,
$variableValues, $operationName, $fieldResolver, $validationRules);
$promise = self::promiseToExecute(
$promiseAdapter,
$schema,
$source,
$rootValue,
$context,
$variableValues,
$operationName,
$fieldResolver,
$validationRules
);
return $promiseAdapter->wait($promise);
}
@ -92,31 +105,25 @@ class GraphQL
* Same as executeQuery(), but requires PromiseAdapter and always returns a Promise.
* Useful for Async PHP platforms.
*
* @api
* @param PromiseAdapter $promiseAdapter
* @param \GraphQL\Type\Schema $schema
* @param string|DocumentNode $source
* @param mixed $rootValue
* @param mixed $context
* @param array|null $variableValues
* @param string|null $operationName
* @param callable $fieldResolver
* @param array $validationRules
* @param string|DocumentNode $source
* @param mixed $rootValue
* @param mixed $context
* @param mixed[]|null $variableValues
* @param ValidationRule[]|null $validationRules
*
* @return Promise
* @api
*/
public static function promiseToExecute(
PromiseAdapter $promiseAdapter,
\GraphQL\Type\Schema $schema,
SchemaType $schema,
$source,
$rootValue = null,
$context = null,
$variableValues = null,
$operationName = null,
callable $fieldResolver = null,
array $validationRules = null
)
{
?string $operationName = null,
?callable $fieldResolver = null,
?array $validationRules = null
) : Promise {
try {
if ($source instanceof DocumentNode) {
$documentNode = $source;
@ -125,36 +132,38 @@ class GraphQL
}
// FIXME
if (!empty($validationRules)) {
foreach ($validationRules as $rule) {
if ($rule instanceof QueryComplexity) {
$rule->setRawVariableValues($variableValues);
}
}
} else {
if (empty($validationRules)) {
/** @var QueryComplexity $queryComplexity */
$queryComplexity = DocumentValidator::getRule(QueryComplexity::class);
$queryComplexity->setRawVariableValues($variableValues);
} else {
foreach ($validationRules as $rule) {
if (! ($rule instanceof QueryComplexity)) {
continue;
}
$rule->setRawVariableValues($variableValues);
}
}
$validationErrors = DocumentValidator::validate($schema, $documentNode, $validationRules);
if (!empty($validationErrors)) {
if (! empty($validationErrors)) {
return $promiseAdapter->createFulfilled(
new ExecutionResult(null, $validationErrors)
);
} else {
return Executor::promiseToExecute(
$promiseAdapter,
$schema,
$documentNode,
$rootValue,
$context,
$variableValues,
$operationName,
$fieldResolver
);
}
return Executor::promiseToExecute(
$promiseAdapter,
$schema,
$documentNode,
$rootValue,
$context,
$variableValues,
$operationName,
$fieldResolver
);
} catch (Error $e) {
return $promiseAdapter->createFulfilled(
new ExecutionResult(null, [$e])
@ -165,29 +174,29 @@ class GraphQL
/**
* @deprecated Use executeQuery()->toArray() instead
*
* @param \GraphQL\Type\Schema $schema
* @param string|DocumentNode $source
* @param mixed $rootValue
* @param mixed $contextValue
* @param array|null $variableValues
* @param string|null $operationName
* @return Promise|array
* @param mixed $rootValue
* @param mixed $contextValue
* @param mixed[]|null $variableValues
*
* @return Promise|mixed[]
*/
public static function execute(
\GraphQL\Type\Schema $schema,
SchemaType $schema,
$source,
$rootValue = null,
$contextValue = null,
$variableValues = null,
$operationName = null
)
{
?string $operationName = null
) {
trigger_error(
__METHOD__ . ' is deprecated, use GraphQL::executeQuery()->toArray() as a quick replacement',
E_USER_DEPRECATED
);
$result = self::promiseToExecute(
$promiseAdapter = Executor::getPromiseAdapter(),
$promiseAdapter = Executor::getPromiseAdapter();
$result = self::promiseToExecute(
$promiseAdapter,
$schema,
$source,
$rootValue,
@ -199,40 +208,40 @@ class GraphQL
if ($promiseAdapter instanceof SyncPromiseAdapter) {
$result = $promiseAdapter->wait($result)->toArray();
} else {
$result = $result->then(function(ExecutionResult $r) {
$result = $result->then(static function (ExecutionResult $r) {
return $r->toArray();
});
}
return $result;
}
/**
* @deprecated renamed to executeQuery()
*
* @param \GraphQL\Type\Schema $schema
* @param string|DocumentNode $source
* @param mixed $rootValue
* @param mixed $contextValue
* @param array|null $variableValues
* @param string|null $operationName
* @param mixed $rootValue
* @param mixed $contextValue
* @param mixed[]|null $variableValues
*
* @return ExecutionResult|Promise
*/
public static function executeAndReturnResult(
\GraphQL\Type\Schema $schema,
SchemaType $schema,
$source,
$rootValue = null,
$contextValue = null,
$variableValues = null,
$operationName = null
)
{
?string $operationName = null
) {
trigger_error(
__METHOD__ . ' is deprecated, use GraphQL::executeQuery() as a quick replacement',
E_USER_DEPRECATED
);
$result = self::promiseToExecute(
$promiseAdapter = Executor::getPromiseAdapter(),
$promiseAdapter = Executor::getPromiseAdapter();
$result = self::promiseToExecute(
$promiseAdapter,
$schema,
$source,
$rootValue,
@ -240,19 +249,22 @@ class GraphQL
$variableValues,
$operationName
);
if ($promiseAdapter instanceof SyncPromiseAdapter) {
$result = $promiseAdapter->wait($result);
}
return $result;
}
/**
* Returns directives defined in GraphQL spec
*
* @api
* @return Directive[]
*
* @api
*/
public static function getStandardDirectives()
public static function getStandardDirectives() : array
{
return array_values(Directive::getInternalDirectives());
}
@ -260,48 +272,79 @@ class GraphQL
/**
* Returns types defined in GraphQL spec
*
* @api
* @return Type[]
*
* @api
*/
public static function getStandardTypes()
public static function getStandardTypes() : array
{
return array_values(Type::getInternalTypes());
return array_values(Type::getStandardTypes());
}
/**
* Replaces standard types with types from this list (matching by name)
* Standard types not listed here remain untouched.
*
* @param Type[] $types
*
* @api
*/
public static function overrideStandardTypes(array $types)
{
Type::overrideStandardTypes($types);
}
/**
* Returns standard validation rules implementing GraphQL spec
*
* @return ValidationRule[]
*
* @api
* @return AbstractValidationRule[]
*/
public static function getStandardValidationRules()
public static function getStandardValidationRules() : array
{
return array_values(DocumentValidator::defaultRules());
}
/**
* @param callable $fn
* Set default resolver implementation
*
* @api
*/
public static function setDefaultFieldResolver(callable $fn)
public static function setDefaultFieldResolver(callable $fn) : void
{
Executor::setDefaultFieldResolver($fn);
}
/**
* @param PromiseAdapter|null $promiseAdapter
*/
public static function setPromiseAdapter(PromiseAdapter $promiseAdapter = null)
public static function setPromiseAdapter(?PromiseAdapter $promiseAdapter = null) : void
{
Executor::setPromiseAdapter($promiseAdapter);
}
/**
* Experimental: Switch to the new executor
*/
public static function useExperimentalExecutor()
{
Executor::setImplementationFactory([CoroutineExecutor::class, 'create']);
}
/**
* Experimental: Switch back to the default executor
*/
public static function useReferenceExecutor()
{
Executor::setImplementationFactory([ReferenceExecutor::class, 'create']);
}
/**
* Returns directives defined in GraphQL spec
*
* @deprecated Renamed to getStandardDirectives
*
* @return Directive[]
*/
public static function getInternalDirectives()
public static function getInternalDirectives() : array
{
return self::getStandardDirectives();
}

View file

@ -1,17 +1,17 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class ArgumentNode extends Node
{
/** @var string */
public $kind = NodeKind::ARGUMENT;
/**
* @var ValueNode
*/
/** @var ValueNode */
public $value;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
}

View file

@ -1,13 +1,14 @@
<?php
namespace GraphQL\Language\AST;
declare(strict_types=1);
namespace GraphQL\Language\AST;
class BooleanValueNode extends Node implements ValueNode
{
/** @var string */
public $kind = NodeKind::BOOLEAN;
/**
* @var string
*/
/** @var bool */
public $value;
}

View file

@ -1,11 +1,14 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
interface DefinitionNode
{
/**
* export type DefinitionNode =
* | ExecutableDefinitionNode
* | TypeSystemDefinitionNode; // experimental non-spec addition.
*/
interface DefinitionNode
{
}

View file

@ -1,30 +1,23 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class DirectiveDefinitionNode extends Node implements TypeSystemDefinitionNode
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::DIRECTIVE_DEFINITION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var ArgumentNode[]
*/
/** @var ArgumentNode[] */
public $arguments;
/**
* @var NameNode[]
*/
/** @var NameNode[] */
public $locations;
/**
* @var StringValueNode|null
*/
/** @var StringValueNode|null */
public $description;
}

View file

@ -1,17 +1,17 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class DirectiveNode extends Node
{
/** @var string */
public $kind = NodeKind::DIRECTIVE;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var ArgumentNode[]
*/
/** @var ArgumentNode[] */
public $arguments;
}

View file

@ -1,12 +1,14 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class DocumentNode extends Node
{
/** @var string */
public $kind = NodeKind::DOCUMENT;
/**
* @var DefinitionNode[]
*/
/** @var NodeList|DefinitionNode[] */
public $definitions;
}

View file

@ -1,30 +1,23 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class EnumTypeDefinitionNode extends Node implements TypeDefinitionNode
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::ENUM_TYPE_DEFINITION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var DirectiveNode[]
*/
/** @var DirectiveNode[] */
public $directives;
/**
* @var EnumValueDefinitionNode[]|null|NodeList
*/
/** @var EnumValueDefinitionNode[]|NodeList|null */
public $values;
/**
* @var StringValueNode|null
*/
/** @var StringValueNode|null */
public $description;
}

View file

@ -1,25 +1,20 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class EnumTypeExtensionNode extends Node implements TypeExtensionNode
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::ENUM_TYPE_EXTENSION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var DirectiveNode[]|null
*/
/** @var DirectiveNode[]|null */
public $directives;
/**
* @var EnumValueDefinitionNode[]|null
*/
/** @var EnumValueDefinitionNode[]|null */
public $values;
}

View file

@ -1,25 +1,20 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class EnumValueDefinitionNode extends Node
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::ENUM_VALUE_DEFINITION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var DirectiveNode[]
*/
/** @var DirectiveNode[] */
public $directives;
/**
* @var StringValueNode|null
*/
/** @var StringValueNode|null */
public $description;
}

View file

@ -1,12 +1,14 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class EnumValueNode extends Node implements ValueNode
{
/** @var string */
public $kind = NodeKind::ENUM;
/**
* @var string
*/
/** @var string */
public $value;
}

View file

@ -1,11 +1,14 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
interface ExecutableDefinitionNode extends DefinitionNode
{
/**
* export type ExecutableDefinitionNode =
* | OperationDefinitionNode
* | FragmentDefinitionNode;
*/
interface ExecutableDefinitionNode extends DefinitionNode
{
}

View file

@ -1,35 +1,26 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class FieldDefinitionNode extends Node
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::FIELD_DEFINITION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var InputValueDefinitionNode[]|NodeList
*/
/** @var InputValueDefinitionNode[]|NodeList */
public $arguments;
/**
* @var TypeNode
*/
/** @var TypeNode */
public $type;
/**
* @var DirectiveNode[]|NodeList
*/
/** @var DirectiveNode[]|NodeList */
public $directives;
/**
* @var StringValueNode|null
*/
/** @var StringValueNode|null */
public $description;
}

View file

@ -1,32 +1,26 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class FieldNode extends Node implements SelectionNode
{
/** @var string */
public $kind = NodeKind::FIELD;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var NameNode|null
*/
/** @var NameNode|null */
public $alias;
/**
* @var ArgumentNode[]|null
*/
/** @var ArgumentNode[]|null */
public $arguments;
/**
* @var DirectiveNode[]|null
*/
/** @var DirectiveNode[]|null */
public $directives;
/**
* @var SelectionSetNode|null
*/
/** @var SelectionSetNode|null */
public $selectionSet;
}

View file

@ -1,12 +1,14 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class FloatValueNode extends Node implements ValueNode
{
/** @var string */
public $kind = NodeKind::FLOAT;
/**
* @var string
*/
/** @var string */
public $value;
}

View file

@ -1,13 +1,15 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class FragmentDefinitionNode extends Node implements ExecutableDefinitionNode, HasSelectionSet
{
/** @var string */
public $kind = NodeKind::FRAGMENT_DEFINITION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
@ -18,18 +20,12 @@ class FragmentDefinitionNode extends Node implements ExecutableDefinitionNode, H
*/
public $variableDefinitions;
/**
* @var NamedTypeNode
*/
/** @var NamedTypeNode */
public $typeCondition;
/**
* @var DirectiveNode[]|NodeList
*/
/** @var DirectiveNode[]|NodeList */
public $directives;
/**
* @var SelectionSetNode
*/
/** @var SelectionSetNode */
public $selectionSet;
}

View file

@ -1,17 +1,17 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class FragmentSpreadNode extends Node implements SelectionNode
{
/** @var string */
public $kind = NodeKind::FRAGMENT_SPREAD;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var DirectiveNode[]
*/
/** @var DirectiveNode[] */
public $directives;
}

View file

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
interface HasSelectionSet

View file

@ -1,22 +1,20 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class InlineFragmentNode extends Node implements SelectionNode
{
/** @var string */
public $kind = NodeKind::INLINE_FRAGMENT;
/**
* @var NamedTypeNode
*/
/** @var NamedTypeNode */
public $typeCondition;
/**
* @var DirectiveNode[]|null
*/
/** @var DirectiveNode[]|null */
public $directives;
/**
* @var SelectionSetNode
*/
/** @var SelectionSetNode */
public $selectionSet;
}

View file

@ -1,30 +1,23 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class InputObjectTypeDefinitionNode extends Node implements TypeDefinitionNode
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::INPUT_OBJECT_TYPE_DEFINITION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var DirectiveNode[]|null
*/
/** @var DirectiveNode[]|null */
public $directives;
/**
* @var InputValueDefinitionNode[]|null
*/
/** @var InputValueDefinitionNode[]|null */
public $fields;
/**
* @var StringValueNode|null
*/
/** @var StringValueNode|null */
public $description;
}

View file

@ -1,25 +1,20 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class InputObjectTypeExtensionNode extends Node implements TypeExtensionNode
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::INPUT_OBJECT_TYPE_EXTENSION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var DirectiveNode[]|null
*/
/** @var DirectiveNode[]|null */
public $directives;
/**
* @var InputValueDefinitionNode[]|null
*/
/** @var InputValueDefinitionNode[]|null */
public $fields;
}

View file

@ -1,35 +1,26 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class InputValueDefinitionNode extends Node
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::INPUT_VALUE_DEFINITION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var TypeNode
*/
/** @var TypeNode */
public $type;
/**
* @var ValueNode
*/
/** @var ValueNode */
public $defaultValue;
/**
* @var DirectiveNode[]
*/
/** @var DirectiveNode[] */
public $directives;
/**
* @var StringValueNode|null
*/
/** @var StringValueNode|null */
public $description;
}

View file

@ -1,12 +1,14 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class IntValueNode extends Node implements ValueNode
{
/** @var string */
public $kind = NodeKind::INT;
/**
* @var string
*/
/** @var string */
public $value;
}

View file

@ -1,30 +1,23 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class InterfaceTypeDefinitionNode extends Node implements TypeDefinitionNode
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::INTERFACE_TYPE_DEFINITION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var DirectiveNode[]|null
*/
/** @var DirectiveNode[]|null */
public $directives;
/**
* @var FieldDefinitionNode[]|null
*/
/** @var FieldDefinitionNode[]|null */
public $fields;
/**
* @var StringValueNode|null
*/
/** @var StringValueNode|null */
public $description;
}

View file

@ -1,25 +1,20 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class InterfaceTypeExtensionNode extends Node implements TypeExtensionNode
{
/**
* @var string
*/
/** @var string */
public $kind = NodeKind::INTERFACE_TYPE_EXTENSION;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
/**
* @var DirectiveNode[]|null
*/
/** @var DirectiveNode[]|null */
public $directives;
/**
* @var FieldDefinitionNode[]|null
*/
/** @var FieldDefinitionNode[]|null */
public $fields;
}

View file

@ -1,12 +1,14 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class ListTypeNode extends Node implements TypeNode
{
/** @var string */
public $kind = NodeKind::LIST_TYPE;
/**
* @var Node
*/
/** @var TypeNode */
public $type;
}

View file

@ -1,13 +1,14 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class ListValueNode extends Node implements ValueNode
{
/** @var string */
public $kind = NodeKind::LST;
/**
* @var ValueNode[]|NodeList
*/
/** @var ValueNode[]|NodeList */
public $values;
}

View file

@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
use GraphQL\Language\Source;
@ -46,27 +49,31 @@ class Location
public $source;
/**
* @param $start
* @param $end
* @param int $start
* @param int $end
*
* @return static
*/
public static function create($start, $end)
{
$tmp = new static();
$tmp = new static();
$tmp->start = $start;
$tmp->end = $end;
$tmp->end = $end;
return $tmp;
}
public function __construct(Token $startToken = null, Token $endToken = null, Source $source = null)
public function __construct(?Token $startToken = null, ?Token $endToken = null, ?Source $source = null)
{
$this->startToken = $startToken;
$this->endToken = $endToken;
$this->source = $source;
$this->endToken = $endToken;
$this->source = $source;
if ($startToken && $endToken) {
$this->start = $startToken->start;
$this->end = $endToken->end;
if ($startToken === null || $endToken === null) {
return;
}
$this->start = $startToken->start;
$this->end = $endToken->end;
}
}

View file

@ -1,12 +1,14 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class NameNode extends Node implements TypeNode
{
/** @var string */
public $kind = NodeKind::NAME;
/**
* @var string
*/
/** @var string */
public $value;
}

View file

@ -1,12 +1,14 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class NamedTypeNode extends Node implements TypeNode
{
/** @var string */
public $kind = NodeKind::NAMED_TYPE;
/**
* @var NameNode
*/
/** @var NameNode */
public $name;
}

View file

@ -1,54 +1,58 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
use GraphQL\Utils\Utils;
use function get_object_vars;
use function is_array;
use function is_scalar;
use function json_encode;
/**
* type Node = NameNode
* | DocumentNode
* | OperationDefinitionNode
* | VariableDefinitionNode
* | VariableNode
* | SelectionSetNode
* | FieldNode
* | ArgumentNode
* | FragmentSpreadNode
* | InlineFragmentNode
* | FragmentDefinitionNode
* | IntValueNode
* | FloatValueNode
* | StringValueNode
* | BooleanValueNode
* | EnumValueNode
* | ListValueNode
* | ObjectValueNode
* | ObjectFieldNode
* | DirectiveNode
* | ListTypeNode
* | NonNullTypeNode
*/
abstract class Node
{
/**
type Node = NameNode
| DocumentNode
| OperationDefinitionNode
| VariableDefinitionNode
| VariableNode
| SelectionSetNode
| FieldNode
| ArgumentNode
| FragmentSpreadNode
| InlineFragmentNode
| FragmentDefinitionNode
| IntValueNode
| FloatValueNode
| StringValueNode
| BooleanValueNode
| EnumValueNode
| ListValueNode
| ObjectValueNode
| ObjectFieldNode
| DirectiveNode
| ListTypeNode
| NonNullTypeNode
*/
public $kind;
/**
* @var Location
*/
/** @var Location */
public $loc;
/**
* @param array $vars
* @param (NameNode|NodeList|SelectionSetNode|Location|string|int|bool|float|null)[] $vars
*/
public function __construct(array $vars)
{
if (!empty($vars)) {
Utils::assign($this, $vars);
if (empty($vars)) {
return;
}
Utils::assign($this, $vars);
}
/**
* @return $this
* @return self
*/
public function cloneDeep()
{
@ -56,8 +60,9 @@ abstract class Node
}
/**
* @param $value
* @return array|Node
* @param string|NodeList|Location|Node|(Node|NodeList|Location)[] $value
*
* @return string|NodeList|Location|Node
*/
private function cloneValue($value)
{
@ -66,7 +71,7 @@ abstract class Node
foreach ($value as $key => $arrValue) {
$cloned[$key] = $this->cloneValue($arrValue);
}
} else if ($value instanceof Node) {
} elseif ($value instanceof self) {
$cloned = clone $value;
foreach (get_object_vars($cloned) as $prop => $propValue) {
$cloned->{$prop} = $this->cloneValue($propValue);
@ -84,34 +89,35 @@ abstract class Node
public function __toString()
{
$tmp = $this->toArray(true);
return json_encode($tmp);
return (string) json_encode($tmp);
}
/**
* @param bool $recursive
* @return array
*
* @return mixed[]
*/
public function toArray($recursive = false)
{
if ($recursive) {
return $this->recursiveToArray($this);
} else {
$tmp = (array) $this;
if ($this->loc) {
$tmp['loc'] = [
'start' => $this->loc->start,
'end' => $this->loc->end
];
}
return $tmp;
}
$tmp = (array) $this;
if ($this->loc !== null) {
$tmp['loc'] = [
'start' => $this->loc->start,
'end' => $this->loc->end,
];
}
return $tmp;
}
/**
* @param Node $node
* @return array
* @return mixed[]
*/
private function recursiveToArray(Node $node)
{
@ -119,28 +125,30 @@ abstract class Node
'kind' => $node->kind,
];
if ($node->loc) {
if ($node->loc !== null) {
$result['loc'] = [
'start' => $node->loc->start,
'end' => $node->loc->end
'end' => $node->loc->end,
];
}
foreach (get_object_vars($node) as $prop => $propValue) {
if (isset($result[$prop]))
if (isset($result[$prop])) {
continue;
}
if ($propValue === null)
if ($propValue === null) {
continue;
}
if (is_array($propValue) || $propValue instanceof NodeList) {
$tmp = [];
foreach ($propValue as $tmp1) {
$tmp[] = $tmp1 instanceof Node ? $this->recursiveToArray($tmp1) : (array) $tmp1;
}
} else if ($propValue instanceof Node) {
} elseif ($propValue instanceof Node) {
$tmp = $this->recursiveToArray($propValue);
} else if (is_scalar($propValue) || null === $propValue) {
} elseif (is_scalar($propValue) || $propValue === null) {
$tmp = $propValue;
} else {
$tmp = null;
@ -148,6 +156,7 @@ abstract class Node
$result[$prop] = $tmp;
}
return $result;
}
}

View file

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class NodeKind
@ -7,139 +9,130 @@ class NodeKind
// constants from language/kinds.js:
const NAME = 'Name';
// Document
const DOCUMENT = 'Document';
const DOCUMENT = 'Document';
const OPERATION_DEFINITION = 'OperationDefinition';
const VARIABLE_DEFINITION = 'VariableDefinition';
const VARIABLE = 'Variable';
const SELECTION_SET = 'SelectionSet';
const FIELD = 'Field';
const ARGUMENT = 'Argument';
const VARIABLE_DEFINITION = 'VariableDefinition';
const VARIABLE = 'Variable';
const SELECTION_SET = 'SelectionSet';
const FIELD = 'Field';
const ARGUMENT = 'Argument';
// Fragments
const FRAGMENT_SPREAD = 'FragmentSpread';
const INLINE_FRAGMENT = 'InlineFragment';
const FRAGMENT_SPREAD = 'FragmentSpread';
const INLINE_FRAGMENT = 'InlineFragment';
const FRAGMENT_DEFINITION = 'FragmentDefinition';
// Values
const INT = 'IntValue';
const FLOAT = 'FloatValue';
const STRING = 'StringValue';
const BOOLEAN = 'BooleanValue';
const ENUM = 'EnumValue';
const NULL = 'NullValue';
const LST = 'ListValue';
const OBJECT = 'ObjectValue';
const INT = 'IntValue';
const FLOAT = 'FloatValue';
const STRING = 'StringValue';
const BOOLEAN = 'BooleanValue';
const ENUM = 'EnumValue';
const NULL = 'NullValue';
const LST = 'ListValue';
const OBJECT = 'ObjectValue';
const OBJECT_FIELD = 'ObjectField';
// Directives
const DIRECTIVE = 'Directive';
// Types
const NAMED_TYPE = 'NamedType';
const LIST_TYPE = 'ListType';
const NAMED_TYPE = 'NamedType';
const LIST_TYPE = 'ListType';
const NON_NULL_TYPE = 'NonNullType';
// Type System Definitions
const SCHEMA_DEFINITION = 'SchemaDefinition';
const SCHEMA_DEFINITION = 'SchemaDefinition';
const OPERATION_TYPE_DEFINITION = 'OperationTypeDefinition';
// Type Definitions
const SCALAR_TYPE_DEFINITION = 'ScalarTypeDefinition';
const OBJECT_TYPE_DEFINITION = 'ObjectTypeDefinition';
const FIELD_DEFINITION = 'FieldDefinition';
const INPUT_VALUE_DEFINITION = 'InputValueDefinition';
const INTERFACE_TYPE_DEFINITION = 'InterfaceTypeDefinition';
const UNION_TYPE_DEFINITION = 'UnionTypeDefinition';
const ENUM_TYPE_DEFINITION = 'EnumTypeDefinition';
const ENUM_VALUE_DEFINITION = 'EnumValueDefinition';
const SCALAR_TYPE_DEFINITION = 'ScalarTypeDefinition';
const OBJECT_TYPE_DEFINITION = 'ObjectTypeDefinition';
const FIELD_DEFINITION = 'FieldDefinition';
const INPUT_VALUE_DEFINITION = 'InputValueDefinition';
const INTERFACE_TYPE_DEFINITION = 'InterfaceTypeDefinition';
const UNION_TYPE_DEFINITION = 'UnionTypeDefinition';
const ENUM_TYPE_DEFINITION = 'EnumTypeDefinition';
const ENUM_VALUE_DEFINITION = 'EnumValueDefinition';
const INPUT_OBJECT_TYPE_DEFINITION = 'InputObjectTypeDefinition';
// Type Extensions
const SCALAR_TYPE_EXTENSION = 'ScalarTypeExtension';
const OBJECT_TYPE_EXTENSION = 'ObjectTypeExtension';
const INTERFACE_TYPE_EXTENSION = 'InterfaceTypeExtension';
const UNION_TYPE_EXTENSION = 'UnionTypeExtension';
const ENUM_TYPE_EXTENSION = 'EnumTypeExtension';
const SCALAR_TYPE_EXTENSION = 'ScalarTypeExtension';
const OBJECT_TYPE_EXTENSION = 'ObjectTypeExtension';
const INTERFACE_TYPE_EXTENSION = 'InterfaceTypeExtension';
const UNION_TYPE_EXTENSION = 'UnionTypeExtension';
const ENUM_TYPE_EXTENSION = 'EnumTypeExtension';
const INPUT_OBJECT_TYPE_EXTENSION = 'InputObjectTypeExtension';
// Directive Definitions
const DIRECTIVE_DEFINITION = 'DirectiveDefinition';
/**
* @todo conver to const array when moving to PHP5.6
* @var array
*/
// Type System Extensions
const SCHEMA_EXTENSION = 'SchemaExtension';
/** @var string[] */
public static $classMap = [
NodeKind::NAME => NameNode::class,
self::NAME => NameNode::class,
// Document
NodeKind::DOCUMENT => DocumentNode::class,
NodeKind::OPERATION_DEFINITION => OperationDefinitionNode::class,
NodeKind::VARIABLE_DEFINITION => VariableDefinitionNode::class,
NodeKind::VARIABLE => VariableNode::class,
NodeKind::SELECTION_SET => SelectionSetNode::class,
NodeKind::FIELD => FieldNode::class,
NodeKind::ARGUMENT => ArgumentNode::class,
self::DOCUMENT => DocumentNode::class,
self::OPERATION_DEFINITION => OperationDefinitionNode::class,
self::VARIABLE_DEFINITION => VariableDefinitionNode::class,
self::VARIABLE => VariableNode::class,
self::SELECTION_SET => SelectionSetNode::class,
self::FIELD => FieldNode::class,
self::ARGUMENT => ArgumentNode::class,
// Fragments
NodeKind::FRAGMENT_SPREAD => FragmentSpreadNode::class,
NodeKind::INLINE_FRAGMENT => InlineFragmentNode::class,
NodeKind::FRAGMENT_DEFINITION => FragmentDefinitionNode::class,
self::FRAGMENT_SPREAD => FragmentSpreadNode::class,
self::INLINE_FRAGMENT => InlineFragmentNode::class,
self::FRAGMENT_DEFINITION => FragmentDefinitionNode::class,
// Values
NodeKind::INT => IntValueNode::class,
NodeKind::FLOAT => FloatValueNode::class,
NodeKind::STRING => StringValueNode::class,
NodeKind::BOOLEAN => BooleanValueNode::class,
NodeKind::ENUM => EnumValueNode::class,
NodeKind::NULL => NullValueNode::class,
NodeKind::LST => ListValueNode::class,
NodeKind::OBJECT => ObjectValueNode::class,
NodeKind::OBJECT_FIELD => ObjectFieldNode::class,
self::INT => IntValueNode::class,
self::FLOAT => FloatValueNode::class,
self::STRING => StringValueNode::class,
self::BOOLEAN => BooleanValueNode::class,
self::ENUM => EnumValueNode::class,
self::NULL => NullValueNode::class,
self::LST => ListValueNode::class,
self::OBJECT => ObjectValueNode::class,
self::OBJECT_FIELD => ObjectFieldNode::class,
// Directives
NodeKind::DIRECTIVE => DirectiveNode::class,
self::DIRECTIVE => DirectiveNode::class,
// Types
NodeKind::NAMED_TYPE => NamedTypeNode::class,
NodeKind::LIST_TYPE => ListTypeNode::class,
NodeKind::NON_NULL_TYPE => NonNullTypeNode::class,
self::NAMED_TYPE => NamedTypeNode::class,
self::LIST_TYPE => ListTypeNode::class,
self::NON_NULL_TYPE => NonNullTypeNode::class,
// Type System Definitions
NodeKind::SCHEMA_DEFINITION => SchemaDefinitionNode::class,
NodeKind::OPERATION_TYPE_DEFINITION => OperationTypeDefinitionNode::class,
self::SCHEMA_DEFINITION => SchemaDefinitionNode::class,
self::OPERATION_TYPE_DEFINITION => OperationTypeDefinitionNode::class,
// Type Definitions
NodeKind::SCALAR_TYPE_DEFINITION => ScalarTypeDefinitionNode::class,
NodeKind::OBJECT_TYPE_DEFINITION => ObjectTypeDefinitionNode::class,
NodeKind::FIELD_DEFINITION => FieldDefinitionNode::class,
NodeKind::INPUT_VALUE_DEFINITION => InputValueDefinitionNode::class,
NodeKind::INTERFACE_TYPE_DEFINITION => InterfaceTypeDefinitionNode::class,
NodeKind::UNION_TYPE_DEFINITION => UnionTypeDefinitionNode::class,
NodeKind::ENUM_TYPE_DEFINITION => EnumTypeDefinitionNode::class,
NodeKind::ENUM_VALUE_DEFINITION => EnumValueDefinitionNode::class,
NodeKind::INPUT_OBJECT_TYPE_DEFINITION =>InputObjectTypeDefinitionNode::class,
self::SCALAR_TYPE_DEFINITION => ScalarTypeDefinitionNode::class,
self::OBJECT_TYPE_DEFINITION => ObjectTypeDefinitionNode::class,
self::FIELD_DEFINITION => FieldDefinitionNode::class,
self::INPUT_VALUE_DEFINITION => InputValueDefinitionNode::class,
self::INTERFACE_TYPE_DEFINITION => InterfaceTypeDefinitionNode::class,
self::UNION_TYPE_DEFINITION => UnionTypeDefinitionNode::class,
self::ENUM_TYPE_DEFINITION => EnumTypeDefinitionNode::class,
self::ENUM_VALUE_DEFINITION => EnumValueDefinitionNode::class,
self::INPUT_OBJECT_TYPE_DEFINITION => InputObjectTypeDefinitionNode::class,
// Type Extensions
NodeKind::SCALAR_TYPE_EXTENSION => ScalarTypeExtensionNode::class,
NodeKind::OBJECT_TYPE_EXTENSION => ObjectTypeExtensionNode::class,
NodeKind::INTERFACE_TYPE_EXTENSION => InterfaceTypeExtensionNode::class,
NodeKind::UNION_TYPE_EXTENSION => UnionTypeExtensionNode::class,
NodeKind::ENUM_TYPE_EXTENSION => EnumTypeExtensionNode::class,
NodeKind::INPUT_OBJECT_TYPE_EXTENSION => InputObjectTypeExtensionNode::class,
self::SCALAR_TYPE_EXTENSION => ScalarTypeExtensionNode::class,
self::OBJECT_TYPE_EXTENSION => ObjectTypeExtensionNode::class,
self::INTERFACE_TYPE_EXTENSION => InterfaceTypeExtensionNode::class,
self::UNION_TYPE_EXTENSION => UnionTypeExtensionNode::class,
self::ENUM_TYPE_EXTENSION => EnumTypeExtensionNode::class,
self::INPUT_OBJECT_TYPE_EXTENSION => InputObjectTypeExtensionNode::class,
// Directive Definitions
NodeKind::DIRECTIVE_DEFINITION => DirectiveDefinitionNode::class
self::DIRECTIVE_DEFINITION => DirectiveDefinitionNode::class,
];
}

View file

@ -1,22 +1,27 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
use ArrayAccess;
use Countable;
use Generator;
use GraphQL\Utils\AST;
use IteratorAggregate;
use function array_merge;
use function array_splice;
use function count;
use function is_array;
/**
* Class NodeList
*
* @package GraphQL\Utils
*/
class NodeList implements \ArrayAccess, \IteratorAggregate, \Countable
class NodeList implements ArrayAccess, IteratorAggregate, Countable
{
/**
* @var array
*/
/** @var Node[]|mixed[] */
private $nodes;
/**
* @param array $nodes
* @param Node[]|mixed[] $nodes
*
* @return static
*/
public static function create(array $nodes)
@ -25,8 +30,7 @@ class NodeList implements \ArrayAccess, \IteratorAggregate, \Countable
}
/**
* NodeList constructor.
* @param array $nodes
* @param Node[]|mixed[] $nodes
*/
public function __construct(array $nodes)
{
@ -35,6 +39,7 @@ class NodeList implements \ArrayAccess, \IteratorAggregate, \Countable
/**
* @param mixed $offset
*
* @return bool
*/
public function offsetExists($offset)
@ -44,6 +49,7 @@ class NodeList implements \ArrayAccess, \IteratorAggregate, \Countable
/**
* @param mixed $offset
*
* @return mixed
*/
public function offsetGet($offset)
@ -78,9 +84,10 @@ class NodeList implements \ArrayAccess, \IteratorAggregate, \Countable
}
/**
* @param int $offset
* @param int $length
* @param int $offset
* @param int $length
* @param mixed $replacement
*
* @return NodeList
*/
public function splice($offset, $length, $replacement = null)
@ -89,25 +96,26 @@ class NodeList implements \ArrayAccess, \IteratorAggregate, \Countable
}
/**
* @param $list
* @param NodeList|Node[] $list
*
* @return NodeList
*/
public function merge($list)
{
if ($list instanceof NodeList) {
if ($list instanceof self) {
$list = $list->nodes;
}
return new NodeList(array_merge($this->nodes, $list));
}
/**
* @return \Generator
* @return Generator
*/
public function getIterator()
{
$count = count($this->nodes);
for ($i = 0; $i < $count; $i++) {
yield $this->offsetGet($i);
foreach ($this->nodes as $key => $_) {
yield $this->offsetGet($key);
}
}

View file

@ -1,12 +1,14 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class NonNullTypeNode extends Node implements TypeNode
{
/** @var string */
public $kind = NodeKind::NON_NULL_TYPE;
/**
* @var NameNode | ListTypeNode
*/
/** @var NamedTypeNode | ListTypeNode */
public $type;
}

View file

@ -1,7 +1,11 @@
<?php
declare(strict_types=1);
namespace GraphQL\Language\AST;
class NullValueNode extends Node implements ValueNode
{
/** @var string */
public $kind = NodeKind::NULL;
}

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